pishoniq-tracker 0.0.0-stage → 2.0.1
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 +25 -0
- package/LICENSE +21 -0
- package/README.md +452 -2
- package/dist/browser.global.js +1 -0
- package/dist/chunk-PDDJOQBE.js +625 -0
- package/dist/chunk-S3CPHTEK.mjs +622 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +16 -0
- package/dist/index.mjs +1 -0
- package/dist/react.d.mts +30 -0
- package/dist/react.d.ts +30 -0
- package/dist/react.js +46 -0
- package/dist/react.mjs +43 -0
- package/package.json +64 -4
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
|
-
#
|
|
1
|
+
# pishoniq
|
|
2
2
|
|
|
3
|
-
|
|
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-tracker
|
|
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-tracker@2.0.0/dist/browser.global.js"></script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The script exposes `window.PishonIQ`. Use `https://unpkg.com/pishoniq-tracker@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-tracker';
|
|
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-tracker@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-tracker@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-tracker/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-tracker/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-tracker/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-tracker/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;})({});
|