@arthurreira/analytics 0.9.0 → 0.11.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.js CHANGED
@@ -135,9 +135,19 @@ function connectPresence(apiKey, sessionId, wsUrl = DEFAULT_WS_URL) {
135
135
  let attempts = 0;
136
136
  let intentionalClose = false;
137
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
+ }
138
148
  function connect() {
139
149
  try {
140
- ws = new WebSocket(wsUrl);
150
+ ws = new WebSocket(targetUrl);
141
151
  } catch {
142
152
  return;
143
153
  }
@@ -191,21 +201,28 @@ function useAnalytics(apiUrl, apiKey, options) {
191
201
  const pendingEvents = useRef([]);
192
202
  const pathname = useRef(typeof window !== "undefined" ? window?.location?.pathname ?? "" : "");
193
203
  const presence = useRef(null);
204
+ const hasSentEnd = useRef(false);
194
205
  useEffect(() => {
195
206
  if (typeof window === "undefined") return;
196
207
  const visitor_id = localStorage.getItem("af_analytics_visitor_id") || crypto.randomUUID();
197
208
  localStorage.setItem("af_analytics_visitor_id", visitor_id);
209
+ let cancelled = false;
198
210
  getOrCreateSession(apiUrl, apiKey, visitor_id).then((id) => {
211
+ if (cancelled) return;
199
212
  sessionId.current = id;
200
213
  presence.current = connectPresence(apiKey, id, options?.wsUrl);
201
214
  pendingEvents.current.forEach((fn) => fn());
202
215
  pendingEvents.current = [];
203
216
  });
217
+ return () => {
218
+ cancelled = true;
219
+ };
204
220
  }, []);
205
221
  useEffect(() => {
206
222
  if (typeof window === "undefined") return;
207
223
  const sendEnd = () => {
208
- if (!sessionId.current) return;
224
+ if (!sessionId.current || hasSentEnd.current) return;
225
+ hasSentEnd.current = true;
209
226
  presence.current?.disconnect();
210
227
  presence.current = null;
211
228
  navigator.sendBeacon(`${apiUrl}/sessions/${sessionId.current}/end`);
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
- export { createSession, trackCTA, trackClick, trackCopy, trackError, trackPageview, trackScroll, trackSearch };
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
@@ -1,5 +1,5 @@
1
1
  { "name": "@arthurreira/analytics",
2
- "version": "0.9.0",
2
+ "version": "0.11.0",
3
3
  "type": "module",
4
4
  "scripts": {
5
5
  "build": "tsup",