pishoniq-tracker 0.0.0-stage → 2.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [2.0.0] - 2026-09-05
6
+
7
+ ### Added
8
+
9
+ - Published browser SDK on npm as `pishoniq`, with a CDN-ready `dist/browser.global.js` bundle at jsDelivr and unpkg.
10
+ - Configurable auto-capture (`autoCapture`) to enable/disable clicks, page views, scroll depth, and sessions individually.
11
+ - Configurable event sampling (`sampleRate`).
12
+ - Configurable network behavior (`flushIntervalMs`, `maxBatchSize`, `maxQueueSize`).
13
+ - In-memory session cache with throttled `localStorage` writes.
14
+ - Queue bound that drops oldest events past `maxQueueSize` during outages.
15
+
16
+ ### Changed
17
+
18
+ - Unload/visibility flushes now use `fetch(..., { keepalive: true })` with the `Authorization` header instead of `navigator.sendBeacon`, so the API key no longer travels in the request body.
19
+ - Session and network state are cleaned up on `_reset()`.
20
+ - `identify`, `session_start`, and `session_end` events are exempt from sampling.
21
+
22
+ ### Fixed
23
+
24
+ - `flush()` no longer hardcodes the session storage key.
25
+ - React provider no longer reads stale init options on re-render.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PishonIQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,453 @@
1
- # Temporary Holding Version
1
+ # pishoniq
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Browser analytics SDK for PishonIQ. Track user events, sessions, and behavior in your web applications.
4
+
5
+ ## Features
6
+
7
+ - **Auto-capture**: Page views, clicks, scroll depth, sessions
8
+ - **Event batching**: Efficient network usage with 2-second flush intervals
9
+ - **Retry logic**: Exponential backoff for failed requests
10
+ - **React support**: Provider component with hooks
11
+ - **TypeScript**: Full type definitions included
12
+ - **SPA support**: History API patching for React Router
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ### npm
19
+
20
+ ```bash
21
+ npm install pishoniq
22
+ ```
23
+
24
+ Or using yarn:
25
+
26
+ ```bash
27
+ yarn add pishoniq
28
+ ```
29
+
30
+ ### CDN script tag
31
+
32
+ ```html
33
+ <script src="https://cdn.jsdelivr.net/npm/pishoniq@2.0.0/dist/browser.global.js"></script>
34
+ ```
35
+
36
+ The script exposes `window.PishonIQ`. Use `https://unpkg.com/pishoniq@2.0.0/dist/browser.global.js` as an alternative CDN.
37
+
38
+ ---
39
+
40
+ ## Quick Start
41
+
42
+ ### Option 1: Vanilla JavaScript (Any Framework)
43
+
44
+ ```javascript
45
+ import PishonIQ from 'pishoniq';
46
+
47
+ // Initialize with your API key and backend host
48
+ PishonIQ.init({
49
+ apiKey: 'pk_live_xxxxxxxxxxxx',
50
+ host: 'https://your-pishoniq-backend.com'
51
+ });
52
+
53
+ // Track custom events
54
+ PishonIQ.track('button_clicked', { button: 'signup', location: 'hero' });
55
+ PishonIQ.track('purchase_completed', { amount: 99.99, currency: 'USD' });
56
+
57
+ // Identify users
58
+ PishonIQ.identify('user_123', { email: 'user@example.com', plan: 'pro' });
59
+
60
+ // Track page views manually
61
+ PishonIQ.page('Checkout');
62
+ ```
63
+
64
+ ### Option 2: Script Tag (No Build Step)
65
+
66
+ ```html
67
+ <!-- Add before </body> -->
68
+ <script src="https://cdn.jsdelivr.net/npm/pishoniq@2.0.0/dist/browser.global.js"></script>
69
+ <script>
70
+ window.PishonIQ.init({
71
+ apiKey: 'pk_live_xxxxxxxxxxxx',
72
+ host: 'https://your-pishoniq-backend.com'
73
+ });
74
+
75
+ window.PishonIQ.track('sdk_installed');
76
+ </script>
77
+ ```
78
+
79
+ If you point the script at `defer`, initialize inside `DOMContentLoaded` instead of immediately:
80
+
81
+ ```html
82
+ <script src="https://cdn.jsdelivr.net/npm/pishoniq@2.0.0/dist/browser.global.js" defer></script>
83
+ <script>
84
+ window.addEventListener('DOMContentLoaded', function () {
85
+ window.PishonIQ.init({ apiKey: 'pk_live_xxxxxxxxxxxx', host: 'https://your-pishoniq-backend.com' });
86
+ });
87
+ </script>
88
+ ```
89
+
90
+ Self-hosted fallback: `https://your-pishoniq-backend.com/sdk/pishoniq.sdk.js`.
91
+
92
+ ---
93
+
94
+ ## React Setup (Recommended)
95
+
96
+ ### Step 1: Wrap Your App with Provider
97
+
98
+ ```tsx
99
+ // main.jsx or App.jsx
100
+ import { BrowserRouter } from 'react-router-dom';
101
+ import { PishonIQProvider } from 'pishoniq/react';
102
+ import App from './App';
103
+
104
+ function Root() {
105
+ return (
106
+ <PishonIQProvider
107
+ apiKey="pk_live_xxxxxxxxxxxx"
108
+ host="https://your-pishoniq-backend.com"
109
+ >
110
+ <BrowserRouter>
111
+ <App />
112
+ </BrowserRouter>
113
+ </PishonIQProvider>
114
+ );
115
+ }
116
+
117
+ export default Root;
118
+ ```
119
+
120
+ ### Step 2: Track Events in Components
121
+
122
+ ```tsx
123
+ // components/SignupButton.jsx
124
+ import { usePishonIQ } from 'pishoniq/react';
125
+
126
+ function SignupButton() {
127
+ const { track } = usePishonIQ();
128
+
129
+ const handleClick = () => {
130
+ track('signup_clicked', { source: 'homepage' });
131
+ // ... your signup logic
132
+ };
133
+
134
+ return <button onClick={handleClick}>Sign Up</button>;
135
+ }
136
+ ```
137
+
138
+ ### Step 3: Identify Users After Login
139
+
140
+ ```tsx
141
+ // contexts/AuthContext.jsx
142
+ import { useEffect } from 'react';
143
+ import { usePishonIQ } from 'pishoniq/react';
144
+ import { useAuth } from './useAuth';
145
+
146
+ function AuthProvider({ children }) {
147
+ const { user } = useAuth();
148
+ const { identify } = usePishonIQ();
149
+
150
+ useEffect(() => {
151
+ if (user) {
152
+ identify(user.id, {
153
+ email: user.email,
154
+ name: user.name,
155
+ plan: user.plan,
156
+ createdAt: user.createdAt
157
+ });
158
+ }
159
+ }, [user, identify]);
160
+
161
+ return <>{children}</>;
162
+ }
163
+ ```
164
+
165
+ ### Step 4: Track Page Views (React Router v6)
166
+
167
+ The `usePishonIQ` hook automatically tracks page views on route changes:
168
+
169
+ ```tsx
170
+ // Already automatic with React Router v6
171
+ // No additional code needed!
172
+
173
+ function App() {
174
+ return (
175
+ <Routes>
176
+ <Route path="/" element={<Home />} />
177
+ <Route path="/products/:id" element={<ProductPage />} />
178
+ <Route path="/checkout" element={<Checkout />} />
179
+ </Routes>
180
+ );
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## API Reference
187
+
188
+ ### PishonIQ.init(options)
189
+
190
+ Initialize the SDK. Must be called before any other methods.
191
+
192
+ ```typescript
193
+ PishonIQ.init({
194
+ apiKey: string; // Required: Your SDK API key from PishonIQ dashboard
195
+ host: string; // Required: Your PishonIQ backend URL
196
+ debug?: boolean; // Optional: Enable debug logging (default: false)
197
+ });
198
+ ```
199
+
200
+ | Option | Type | Required | Description |
201
+ |--------|------|----------|-------------|
202
+ | `apiKey` | string | Yes | Your project SDK key (starts with `pk_live_` or `pk_test_`) |
203
+ | `host` | string | Yes | Full URL of your PishonIQ backend (e.g., `https://analytics.example.com`) |
204
+ | `debug` | boolean | No | Log events and network activity to console |
205
+
206
+ ---
207
+
208
+ ### PishonIQ.track(event, properties?)
209
+
210
+ Track a custom event.
211
+
212
+ ```typescript
213
+ PishonIQ.track('purchase_completed', {
214
+ amount: 149.99,
215
+ currency: 'USD',
216
+ items: 3,
217
+ discount: true
218
+ });
219
+ ```
220
+
221
+ | Parameter | Type | Required | Description |
222
+ |-----------|------|----------|-------------|
223
+ | `event` | string | Yes | Event name (e.g., `purchase_completed`, `form_submitted`) |
224
+ | `properties` | object | No | Custom properties to attach to the event |
225
+
226
+ **Best Practices for Event Names:**
227
+ - Use lowercase with underscores: `signup_clicked` not `SignupClicked`
228
+ - Be specific: `checkout_step_2` not `step`
229
+ - Use past tense for completed actions: `purchase_completed` not `purchase`
230
+
231
+ ---
232
+
233
+ ### PishonIQ.identify(userId, traits?)
234
+
235
+ Identify a user and attach traits.
236
+
237
+ ```typescript
238
+ PishonIQ.identify('user_12345', {
239
+ email: 'john@example.com',
240
+ name: 'John Doe',
241
+ plan: 'pro',
242
+ company: 'Acme Inc',
243
+ createdAt: '2024-01-15'
244
+ });
245
+ ```
246
+
247
+ | Parameter | Type | Required | Description |
248
+ |-----------|------|----------|-------------|
249
+ | `userId` | string | Yes | Unique identifier for the user |
250
+ | `traits` | object | No | User properties (email, name, plan, etc.) |
251
+
252
+ ---
253
+
254
+ ### PishonIQ.page(pageName?)
255
+
256
+ Track a page view manually.
257
+
258
+ ```typescript
259
+ // Track a product page view
260
+ PishonIQ.page('Product Page');
261
+
262
+ // Track a virtual page
263
+ PishonIQ.page('/checkout/step-2');
264
+ ```
265
+
266
+ | Parameter | Type | Required | Description |
267
+ |-----------|------|----------|-------------|
268
+ | `pageName` | string | No | Page name or path (defaults to `document.title`) |
269
+
270
+ > **Note:** In React with `PishonIQProvider`, page views are tracked automatically on route changes. Call this method only for custom page tracking.
271
+
272
+ ---
273
+
274
+ ## Auto-Captured Events
275
+
276
+ The SDK automatically captures these events:
277
+
278
+ | Event | Trigger | Properties |
279
+ |-------|---------|------------|
280
+ | `page_view` | Page load, route change | `pageName`, `pathname`, `search`, `referrer`, `source` |
281
+ | `click` | Button/link clicks | `tag`, `text`, `href`, `pishon` |
282
+ | `scroll_depth` | Scroll milestones | `depth`, `percent` |
283
+ | `session_start` | New session begins | `referrer` |
284
+ | `session_end` | Tab close / page hide | `durationMs` |
285
+
286
+ ### Tagging Elements
287
+
288
+ Add `data-pishon` attribute to track specific elements:
289
+
290
+ ```html
291
+ <!-- Track this button click -->
292
+ <button data-pishon="signup-hero-btn">Get Started</button>
293
+
294
+ <!-- Track links -->
295
+ <a href="/pricing" data-pishon="nav-pricing-link">Pricing</a>
296
+
297
+ <!-- Track any element -->
298
+ <div data-pishon="feature-card-pricing">...</div>
299
+ ```
300
+
301
+ ---
302
+
303
+ ## React Hooks
304
+
305
+ ### usePishonIQ()
306
+
307
+ Access the PishonIQ instance and auto-tracking functions.
308
+
309
+ ```tsx
310
+ import { usePishonIQ } from 'pishoniq/react';
311
+
312
+ function MyComponent() {
313
+ const { track, identify, page } = usePishonIQ();
314
+
315
+ return (
316
+ <button onClick={() => track('my_event', { foo: 'bar' })}>
317
+ Track Event
318
+ </button>
319
+ );
320
+ }
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Environment Setup
326
+
327
+ ### Development vs Production Keys
328
+
329
+ Use separate API keys for each environment:
330
+
331
+ ```tsx
332
+ const config = {
333
+ development: {
334
+ apiKey: 'pk_test_xxxxxxxxxxxx',
335
+ host: 'http://localhost:3033'
336
+ },
337
+ production: {
338
+ apiKey: 'pk_live_xxxxxxxxxxxx',
339
+ host: 'https://your-pishoniq-backend.com'
340
+ }
341
+ };
342
+
343
+ const env = process.env.NODE_ENV === 'production' ? config.production : config.development;
344
+
345
+ <PishonIQProvider apiKey={env.apiKey} host={env.host}>
346
+ <App />
347
+ </PishonIQProvider>
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Local Development Setup
353
+
354
+ ### Step 1: Build the SDK
355
+
356
+ ```bash
357
+ cd pishoniq-sdk
358
+ npm install
359
+ npm run build
360
+ ```
361
+
362
+ ### Step 2: Link to Your React Project
363
+
364
+ ```bash
365
+ cd ../your-react-project
366
+ npm install ../pishoniq-sdk
367
+ ```
368
+
369
+ ### Step 3: Start Your Backend
370
+
371
+ Make sure your PishonIQ backend is running:
372
+
373
+ ```bash
374
+ cd pishoniq-backend
375
+ npm install
376
+ npm run dev # or: node app.js
377
+ ```
378
+
379
+ ### Step 4: Get Your SDK Key
380
+
381
+ 1. Go to your PishonIQ dashboard
382
+ 2. Navigate to Settings → Application
383
+ 3. Select your environment
384
+ 4. Copy the SDK key (starts with `pk_live_` or `pk_test_`)
385
+
386
+ ---
387
+
388
+ ## Troubleshooting
389
+
390
+ ### Events Not Appearing
391
+
392
+ 1. **Check API key**: Make sure it matches exactly (including `pk_live_` or `pk_test_` prefix)
393
+ 2. **Check backend URL**: Ensure `host` doesn't have trailing slash
394
+ 3. **Enable debug mode**: `PishonIQ.init({ ..., debug: true })`
395
+ 4. **Check browser console**: Look for error messages
396
+ 5. **Verify CORS**: Ensure your backend allows requests from your domain
397
+
398
+ ### Double Page Views in React
399
+
400
+ The `usePishonIQ` hook automatically tracks page views. If you're also calling `PishonIQ.page()` manually, you'll see duplicate events. Remove the manual calls.
401
+
402
+ ### "Invalid API Key" Error
403
+
404
+ 1. Verify the key exists in your PishonIQ dashboard
405
+ 2. Check the key is active (not revoked)
406
+ 3. Ensure the key matches the environment (test vs live)
407
+
408
+ ### CORS Errors
409
+
410
+ The PishonIQ backend must have CORS configured to accept requests from your domain. Add your domain to the allowed origins in backend environment variables.
411
+
412
+ ---
413
+
414
+ ## Payload Format
415
+
416
+ Events are batched and sent as:
417
+
418
+ ```json
419
+ POST /api/events
420
+ Authorization: Bearer pk_live_xxxxx
421
+ Content-Type: application/json
422
+
423
+ {
424
+ "events": [
425
+ {
426
+ "sessionId": "550e8400-e29b-41d4-a716-446655440000",
427
+ "userId": "user_123",
428
+ "event": "page_view",
429
+ "properties": {
430
+ "url": "https://example.com/products",
431
+ "title": "Products"
432
+ },
433
+ "timestamp": 1710000000000,
434
+ "url": "https://example.com/products",
435
+ "userAgent": "Mozilla/5.0..."
436
+ }
437
+ ]
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## Session Management
444
+
445
+ - Sessions last 30 minutes of inactivity
446
+ - Sessions are stored in `localStorage` (`pishoniq_session_id`)
447
+ - User ID and traits persist across sessions
448
+
449
+ ---
450
+
451
+ ## License
452
+
453
+ MIT
@@ -0,0 +1 @@
1
+ var PishonIQBundle=(function(exports){'use strict';var y="pishoniq_session_id",B="pishoniq_session_last_active",J="pishoniq_session_started";var d=null,a=0,g=0,A=0;function m(){return typeof window<"u"&&typeof localStorage<"u"}function j(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return (e==="x"?t:t&3|8).toString(16)})}function G(){if(!m())return 0;if(a)return a;let e=localStorage.getItem(B);return a=e?Number(e):0,a}function Z(e,t){if(!m())return;let n=Date.now();d=e,g=t,a=n,A=n;try{localStorage.setItem(y,e),localStorage.setItem(J,String(t)),localStorage.setItem(B,String(n));}catch{}}function w(){if(!m())return "";let e=Date.now();if(d&&a&&e-a<18e5)return d;let t=d??localStorage.getItem(y),n=G();if(t&&n&&e-n<18e5)return d=t,t;let r=j();return Z(r,e),r}function u(){if(!m())return;let e=Date.now();if(a=e,!(e-A<1e3)){A=e;try{localStorage.setItem(B,String(e));}catch{}}}function ee(){let e=j();return Z(e,Date.now()),{sessionId:e,isNew:true}}function R(){if(!m())return {sessionId:"",isNew:false};let e=Date.now(),t=d??localStorage.getItem(y),n=G();if(t&&n&&e-n<18e5)return d=t,u(),{sessionId:t,isNew:false};let{sessionId:r}=ee();return {sessionId:r,isNew:true}}function Te(){if(!m())return Date.now();if(g)return g;let e=localStorage.getItem(J);return g=e?Number(e):Date.now(),g}function te(){return Date.now()-Te()}function ne(){d=null,a=0,g=0,A=0;}var Ae=2e3,Re=100,Pe=1e3,q=3,Ne=3,Ce=[1e3,2e3,4e3],s=null,i=[],E=null,P=0,p=false,N=false,K=0,z=null,C=false;function I(...e){p&&console.log("[PishonIQ]",...e);}function f(...e){p&&console.warn("[PishonIQ]",...e);}function se(e){p=e;}function D(){return typeof window<"u"}function De(){return D()?(z===null&&(z=navigator.userAgent),{url:window.location.href,userAgent:z}):{url:"",userAgent:""}}function ie(){return s?.flushIntervalMs??Ae}function oe(){return s?.maxBatchSize??Re}function re(){return s?.maxQueueSize??Pe}function ae(){!D()||E||(E=setInterval(()=>{S(false);},ie()));}function H(){E&&(clearInterval(E),E=null);}function ue(){typeof document>"u"||(document.visibilityState==="hidden"?(S(true),H()):ae());}function le(){S(true);}function ce(e){s=e,D()&&(H(),ae(),N||(N=true,window.addEventListener("beforeunload",le),document.addEventListener("visibilitychange",ue)),I(`Network ready | endpoint: ${e.host}/api/events | flush every ${ie()}ms`));}function de(){H(),D()&&N&&(N=false,window.removeEventListener("beforeunload",le),document.removeEventListener("visibilitychange",ue)),s=null,i=[],P=0,K=0,C=false;}function l(e,t){if(!s){f("enqueueEvent called before init \u2014 event dropped:",e);return}let n=s.sampleRate??1;if(!(e==="identify"||e==="session_start"||e==="session_end")&&n<1&&Math.random()>n)return;u();let o={sessionId:w(),userId:s.getUserId(),event:e,properties:t,timestamp:Date.now(),schemaVersion:1,...De()};if(i.push(o),o.sessionId||(C=true),i.length>re()){let h=i.length-re();i.splice(0,h),K+=h,p&&f(`Queue full \u2014 dropped ${h} oldest event(s) (${K} total)`);}p&&I(`Event queued: "${e}" | queue size: ${i.length}`,t??""),i.length>=oe()&&(p&&I(`Queue reached max batch size (${oe()}) \u2014 flushing immediately`),S(false));}function x(e){if(C){for(let t of i)t.sessionId||(t.sessionId=e);C=false;}}function Le(e,t,n){let r=new Array(e.length);for(let o=0;o<e.length;o++){let h=e[o];r[o]={...h,sessionId:h.sessionId||t,userId:h.userId??n};}return r}function Me(){return `${(s?.host??"").replace(/\/$/,"")}/api/events`}async function fe(e,t){if(!s||e.length===0)return {ok:true,retryable:false};let n=Me(),r={events:e};try{I(`Flushing ${e.length} event(s) \u2192 ${n}`);let o=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s.apiKey}`},body:JSON.stringify(r),keepalive:t});return o.ok?(I(`\u2713 Sent ${e.length} event(s) successfully`),{ok:!0,retryable:!1,status:o.status}):(f(`\u2717 Server rejected events | HTTP ${o.status} | url: ${n}`),o.status===401?(f("\u2717 Invalid apiKey \u2014 events will not be retried. Check the apiKey passed to PishonIQ.init()."),{ok:!1,retryable:!1,status:o.status}):o.status===400?{ok:!1,retryable:!1,status:o.status}:{ok:!1,retryable:!0,status:o.status})}catch(o){return f(`\u2717 Network error \u2014 backend unreachable at ${n}`,o instanceof Error?o.message:o),{ok:false,retryable:true}}}function he(e,t){if(t>=q){f(`Gave up after ${q} retries \u2014 ${e.length} event(s) lost`);return}if(P>=Ne){f(`Too many pending retries \u2014 dropping batch of ${e.length} event(s)`);return}let n=Ce[t]??4e3;f(`Retrying in ${n}ms (attempt ${t+1}/${q})`),P+=1,setTimeout(()=>{P-=1,(async()=>{let r=await fe(e,false);!r.ok&&r.retryable&&he(e,t+1);})();},n);}function S(e=false){if(!s||i.length===0)return;let t=i;i=[];let n=t[0]?.sessionId||(typeof window<"u"?localStorage.getItem(y)??"":"");p&&I(`Flushing ${t.length} event(s) | sessionId: ${n} | useBeacon: ${e}`);let r=Le(t,n,s?.getUserId());(async()=>{let o=await fe(r,e);!o.ok&&!e&&o.retryable&&he(r,0);})();}var $e=[25,50,75,100],L=false,pe=false,ge="",me=false,we=false,M=false,c={clicks:true,pageViews:true,scrollDepth:true,sessions:true},F=new Set;function U(){return typeof window<"u"&&typeof document<"u"}function Ue(e){let t=e.target;return t instanceof Element?t.closest("[data-pishon], button, a, input[type='button'], input[type='submit']"):null}function Oe(e){let t=(e.textContent??"").trim().slice(0,200),n={tag:e.tagName,text:t};e instanceof HTMLAnchorElement&&e.href&&(n.href=e.href);let r=e.getAttribute("data-pishon");return r&&(n.pishon=r),n}function W(e,t="auto"){if(!U()||!c.pageViews)return;let n=window.location.href;n===ge&&me||(ge=n,me=true,F.clear(),l("page_view",{pageName:e??document.title,pathname:window.location.pathname,search:window.location.search,referrer:document.referrer||void 0,source:t}));}function V(){we||W(void 0,"history");}function Qe(){if(!U()||pe)return;pe=true;let e=history.pushState.bind(history),t=history.replaceState.bind(history);history.pushState=function(...n){e(...n),V();},history.replaceState=function(...n){t(...n),V();},window.addEventListener("popstate",V);}function Ie(e){if(!c.clicks)return;let t=Ue(e);t&&l("click",Oe(t));}function Be(){let e=document.documentElement,t=window.scrollY||e.scrollTop,n=e.scrollHeight-e.clientHeight;return n<=0?100:Math.min(100,Math.round(t/n*100))}var Y=false;function Se(){!c.scrollDepth||Y||(Y=true,requestAnimationFrame(()=>{Y=false;let e=Be();for(let t of $e)e>=t&&!F.has(t)&&(F.add(t),l("scroll_depth",{depth:t,percent:e}));}));}function $(){!c.sessions||M||(M=true,l("session_end",{durationMs:te()}),S(true));}function ve(e){we=e;}function ye(e){if(!U()||L)return;e&&(c={clicks:e.clicks??true,pageViews:e.pageViews??true,scrollDepth:e.scrollDepth??true,sessions:e.sessions??true}),L=true,M=false;let{isNew:t}=R();t&&c.sessions&&l("session_start",{referrer:document.referrer||void 0}),Qe(),W(void 0,"initial"),c.clicks&&document.addEventListener("click",Ie,true),c.scrollDepth&&window.addEventListener("scroll",Se,{passive:true}),c.sessions&&(window.addEventListener("beforeunload",$),window.addEventListener("pagehide",$));}function Ee(){!U()||!L||(L=false,M=false,document.removeEventListener("click",Ie,true),window.removeEventListener("scroll",Se),window.removeEventListener("beforeunload",$),window.removeEventListener("pagehide",$));}function xe(e){W(e,"manual");}function be(){u();}var _e="pishoniq_user_id",ke="pishoniq_user_traits",qe=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/?$/i,_=false,b="",O="",Q,k;function v(){return typeof window<"u"}function ze(){if(!v())return;Q=localStorage.getItem(_e)??void 0;let e=localStorage.getItem(ke);if(e)try{k=JSON.parse(e);}catch{k=void 0;}}function Ke(e,t){v()&&(localStorage.setItem(_e,e),t&&localStorage.setItem(ke,JSON.stringify(t)));}function X(){if(!_)throw new Error("[PishonIQ] Call PishonIQ.init() before using the SDK")}var T=class{static isInitialized(){return _}static init(t){if(!t.apiKey)throw new Error("[PishonIQ] apiKey is required");if(!t.host)throw new Error("[PishonIQ] host is required \u2014 pass the URL of your PishonIQ backend");let n=t.sampleRate??1;if(!(n>=0&&n<=1))throw new Error("[PishonIQ] sampleRate must be between 0 and 1");if(_){console.warn("[PishonIQ] init() called more than once; ignoring");return}if(qe.test(t.host)&&console.warn(`[PishonIQ] host "${t.host}" looks like a local address \u2014 make sure to pass your production backend URL in deployed environments`),b=t.apiKey,O=t.host.replace(/\/$/,""),_=true,se(t.debug??false),t.debug){let o=`${b.slice(0,6)}...${b.slice(-4)}`;console.log(`[PishonIQ] Initialized | apiKey: ${o} | host: ${O} | debug: true`);}ze();let{sessionId:r}=R();ce({apiKey:b,host:O,getUserId:()=>Q,flushIntervalMs:t.flushIntervalMs,maxBatchSize:t.maxBatchSize,maxQueueSize:t.maxQueueSize,sampleRate:n}),x(r),v()&&ye(t.autoCapture);}static track(t,n){X(),v()&&(u(),x(w()),l(t,n));}static identify(t,n){X(),v()&&(Q=t,k=n,Ke(t,n),be(),u(),x(w()),l("identify",{userId:t,traits:n??k}));}static page(t){X(),v()&&(u(),x(w()),xe(t));}static _setReactRouterActive(t){ve(t);}static _reset(){_=false,b="",O="",Q=void 0,k=void 0,Ee(),de(),ne();}};typeof window<"u"&&(window.PishonIQ=T);var tt=T;exports.PishonIQ=T;exports.default=tt;Object.defineProperty(exports,'__esModule',{value:true});return exports;})({});