@funelr/events 0.4.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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/client.d.ts +116 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +123 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas.d.ts +46 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +27 -0
- package/dist/schemas.js.map +1 -0
- package/dist/session.d.ts +31 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +64 -0
- package/dist/session.js.map +1 -0
- package/dist/transport.d.ts +23 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +43 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +19 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +28 -0
- package/dist/validation.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 json
|
|
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
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @funelr/events
|
|
2
|
+
|
|
3
|
+
Official JavaScript/TypeScript SDK for [funelr.io](https://funelr.io) — event tracking with batching, consent management, and automatic session handling.
|
|
4
|
+
|
|
5
|
+
## Introduction
|
|
6
|
+
|
|
7
|
+
`@funelr/events` sends analytics events from browser applications to the funelr.io ingest API. Key characteristics:
|
|
8
|
+
|
|
9
|
+
- **Consent-first** — no data is collected until `setConsent(true)` is called
|
|
10
|
+
- **Batched delivery** — events are queued in memory and flushed by threshold, timer, or page lifecycle hooks
|
|
11
|
+
- **Resilient transport** — `sendBeacon` is preferred on page unload; `fetch` with exponential-backoff retry handles the rest
|
|
12
|
+
- **Zero config for sessions** — visitor and session identifiers are generated automatically via `crypto.randomUUID()` once consent is granted
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Node.js ≥ 22 (build / server-side usage)
|
|
17
|
+
- A modern browser with `crypto.randomUUID`, `sessionStorage`, and `fetch`
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @funelr/events
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Basic initialisation
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createFunnelClient } from "@funelr/events"
|
|
29
|
+
|
|
30
|
+
const client = createFunnelClient({
|
|
31
|
+
apiKey: "fjs_live_abc123",
|
|
32
|
+
endpoint: "https://ingest.funelr.io/v1/collect",
|
|
33
|
+
consent: true, // or call client.setConsent(true) after cookie banner
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### With deferred consent
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const client = createFunnelClient({
|
|
41
|
+
apiKey: "fjs_live_abc123",
|
|
42
|
+
endpoint: "https://ingest.funelr.io/v1/collect",
|
|
43
|
+
// consent defaults to false — nothing is tracked yet
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// later, once the user accepts the cookie banner:
|
|
47
|
+
client.setConsent(true)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### Tracking events
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
client.track("page_view")
|
|
56
|
+
client.track("cta_click", { label: "Get started", position: "hero" })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`track()` is a no-op when:
|
|
60
|
+
- consent has not been granted
|
|
61
|
+
- `allowedEventNames` is configured and `eventName` is not in the list
|
|
62
|
+
|
|
63
|
+
### Consent management
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// grant
|
|
67
|
+
client.setConsent(true)
|
|
68
|
+
|
|
69
|
+
// revoke — flushes nothing, clears queue and stored IDs
|
|
70
|
+
client.setConsent(false)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Reading identifiers
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const sessionId = client.getSessionId() // scoped to the current tab (sessionStorage)
|
|
77
|
+
const anonymousId = client.getAnonymousId() // persistent across sessions (localStorage)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Both return `undefined` when consent has not been granted or the storage API is unavailable (e.g. SSR).
|
|
81
|
+
|
|
82
|
+
### Manual flush and teardown
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// send all queued events immediately
|
|
86
|
+
client.flush()
|
|
87
|
+
|
|
88
|
+
// flush, stop the timer, and remove page-lifecycle listeners
|
|
89
|
+
client.destroy()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Call `destroy()` when unmounting a SPA layout or in framework cleanup hooks.
|
|
93
|
+
|
|
94
|
+
### Configuration reference
|
|
95
|
+
|
|
96
|
+
| Option | Type | Default | Description |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `endpoint` | `string` | — | Base URL of the ingest API (**required**) |
|
|
99
|
+
| `apiKey` | `string` | — | API key sent as `X-Api-Key` header |
|
|
100
|
+
| `consent` | `boolean` | `false` | Initial consent state |
|
|
101
|
+
| `allowedEventNames` | `readonly string[]` | — | Allowlist — unknown names are silently dropped |
|
|
102
|
+
| `batchSize` | `number` | `20` | Queue length that triggers an automatic flush |
|
|
103
|
+
| `flushInterval` | `number` | `5000` | Periodic flush interval in milliseconds |
|
|
104
|
+
| `maxRetries` | `number` | `3` | Retry attempts on network errors or 5xx responses |
|
|
105
|
+
| `sessionStorageKey` | `string` | `"funnel_session_id"` | Custom key for `sessionStorage` |
|
|
106
|
+
| `anonymousIdStorageKey` | `string` | `"funnel_anonymous_id"` | Custom key for `localStorage` |
|
|
107
|
+
|
|
108
|
+
## Server-side payload validation
|
|
109
|
+
|
|
110
|
+
The Zod schema is exported for use in API routes or test assertions:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { eventPayloadSchema } from "@funelr/events"
|
|
114
|
+
|
|
115
|
+
const result = eventPayloadSchema.safeParse(req.body)
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
return res.status(400).json(result.error)
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Standalone utilities
|
|
122
|
+
|
|
123
|
+
Session and validation helpers can be used independently of `createFunnelClient`:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import {
|
|
127
|
+
getOrCreateSessionId,
|
|
128
|
+
getOrCreateAnonymousId,
|
|
129
|
+
clearSessionId,
|
|
130
|
+
clearAnonymousId,
|
|
131
|
+
isValidUUID,
|
|
132
|
+
isAllowedEventName,
|
|
133
|
+
} from "@funelr/events"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Source structure
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
src/
|
|
140
|
+
├── client.ts # createFunnelClient — public API, batching, consent gate
|
|
141
|
+
├── transport.ts # sendBatch — fetch + sendBeacon + retry logic
|
|
142
|
+
├── session.ts # getOrCreateSessionId / getOrCreateAnonymousId
|
|
143
|
+
├── validation.ts # isValidUUID, isAllowedEventName
|
|
144
|
+
├── schemas.ts # Zod schema (eventPayloadSchema) and EventPayload type
|
|
145
|
+
├── types.ts # TypeScript types for Stats & Analytics API responses
|
|
146
|
+
├── constants.ts # SDK_VERSION, field length limits, UUID_V4_REGEX
|
|
147
|
+
└── index.ts # public re-exports
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm run build # compile to dist/
|
|
154
|
+
npm run typecheck # tsc --noEmit
|
|
155
|
+
npm run test # vitest run
|
|
156
|
+
npm run test:watch # vitest (watch mode)
|
|
157
|
+
npm run lint # biome check
|
|
158
|
+
npm run lint:fix # biome check --write
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** Configuration options for {@link createFunnelClient}. */
|
|
2
|
+
export interface FunnelClientConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Base URL of the ingest endpoint.
|
|
5
|
+
*
|
|
6
|
+
* @example "https://ingest.funelr.io/v1/collect"
|
|
7
|
+
*/
|
|
8
|
+
endpoint: string;
|
|
9
|
+
/**
|
|
10
|
+
* API key for the project. Events are sent as a batch to `{endpoint}/batch`
|
|
11
|
+
* with an `X-Api-Key` header.
|
|
12
|
+
*/
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional allowlist of accepted event names. Events whose name is not
|
|
16
|
+
* present in this list are silently dropped. When omitted, all names are
|
|
17
|
+
* accepted.
|
|
18
|
+
*/
|
|
19
|
+
allowedEventNames?: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* Initial consent state. No data is collected until consent is `true`.
|
|
22
|
+
*
|
|
23
|
+
* @defaultValue `false`
|
|
24
|
+
*/
|
|
25
|
+
consent?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Custom `sessionStorage` key for the session identifier.
|
|
28
|
+
*
|
|
29
|
+
* @defaultValue `"funnel_session_id"`
|
|
30
|
+
*/
|
|
31
|
+
sessionStorageKey?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Custom `localStorage` key for the anonymous visitor identifier.
|
|
34
|
+
*
|
|
35
|
+
* @defaultValue `"funnel_anonymous_id"`
|
|
36
|
+
*/
|
|
37
|
+
anonymousIdStorageKey?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Number of queued events that triggers an automatic flush.
|
|
40
|
+
*
|
|
41
|
+
* @defaultValue `20`
|
|
42
|
+
*/
|
|
43
|
+
batchSize?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Interval in milliseconds between automatic flushes while events are queued.
|
|
46
|
+
*
|
|
47
|
+
* @defaultValue `5000`
|
|
48
|
+
*/
|
|
49
|
+
flushInterval?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Maximum retry attempts on network errors or 5xx responses.
|
|
52
|
+
*
|
|
53
|
+
* @defaultValue `3`
|
|
54
|
+
*/
|
|
55
|
+
maxRetries?: number;
|
|
56
|
+
}
|
|
57
|
+
/** Public interface returned by {@link createFunnelClient}. */
|
|
58
|
+
export interface FunnelClient {
|
|
59
|
+
/**
|
|
60
|
+
* Records an event in the internal queue.
|
|
61
|
+
*
|
|
62
|
+
* The event is flushed automatically when the batch threshold or flush
|
|
63
|
+
* interval is reached, or when `flush()` is called explicitly. This method
|
|
64
|
+
* is a no-op when consent has not been granted or when `eventName` is not
|
|
65
|
+
* in the configured `allowedEventNames` list.
|
|
66
|
+
*/
|
|
67
|
+
track(eventName: string, properties?: Record<string, unknown>): void;
|
|
68
|
+
/**
|
|
69
|
+
* Returns the current session ID, or `undefined` if consent has not been
|
|
70
|
+
* granted or `sessionStorage` is unavailable.
|
|
71
|
+
*/
|
|
72
|
+
getSessionId(): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Returns the persistent anonymous visitor ID, or `undefined` if consent
|
|
75
|
+
* has not been granted or `localStorage` is unavailable.
|
|
76
|
+
*/
|
|
77
|
+
getAnonymousId(): string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Updates the consent state at runtime.
|
|
80
|
+
*
|
|
81
|
+
* When consent is revoked (`false`), all queued events are discarded, the
|
|
82
|
+
* flush timer is stopped, and all locally stored identifiers are removed.
|
|
83
|
+
*/
|
|
84
|
+
setConsent(given: boolean): void;
|
|
85
|
+
/** Immediately sends all queued events to the ingest endpoint. */
|
|
86
|
+
flush(): void;
|
|
87
|
+
/**
|
|
88
|
+
* Flushes remaining events, stops the flush timer, and removes the
|
|
89
|
+
* page-lifecycle event listeners registered by this client instance.
|
|
90
|
+
*/
|
|
91
|
+
destroy(): void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Creates a new {@link FunnelClient} instance.
|
|
95
|
+
*
|
|
96
|
+
* The client batches events in memory and flushes them to the configured
|
|
97
|
+
* ingest endpoint when the batch size threshold is reached, on a periodic
|
|
98
|
+
* interval, or when `flush()` is called explicitly. In browser environments
|
|
99
|
+
* the client also flushes on `beforeunload` and when the tab is hidden.
|
|
100
|
+
*
|
|
101
|
+
* Event collection is disabled by default. Pass `consent: true` or call
|
|
102
|
+
* `setConsent(true)` after the user grants permission before tracking events.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* const client = createFunnelClient({
|
|
107
|
+
* apiKey: "fjs_live_abc123",
|
|
108
|
+
* endpoint: "https://ingest.funelr.io/v1/collect",
|
|
109
|
+
* consent: true,
|
|
110
|
+
* })
|
|
111
|
+
*
|
|
112
|
+
* client.track("page_view", { path: "/home" })
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function createFunnelClient(config: FunnelClientConfig): FunnelClient;
|
|
116
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAUA,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAErC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAE1B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAE9B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAEpE;;;OAGG;IACH,YAAY,IAAI,MAAM,GAAG,SAAS,CAAA;IAElC;;;OAGG;IACH,cAAc,IAAI,MAAM,GAAG,SAAS,CAAA;IAEpC;;;;;OAKG;IACH,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IAEhC,kEAAkE;IAClE,KAAK,IAAI,IAAI,CAAA;IAEb;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAyG3E"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { SDK_VERSION } from "./constants.js";
|
|
2
|
+
import { clearAnonymousId, clearSessionId, getOrCreateAnonymousId, getOrCreateSessionId, } from "./session.js";
|
|
3
|
+
import { sendBatch } from "./transport.js";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new {@link FunnelClient} instance.
|
|
6
|
+
*
|
|
7
|
+
* The client batches events in memory and flushes them to the configured
|
|
8
|
+
* ingest endpoint when the batch size threshold is reached, on a periodic
|
|
9
|
+
* interval, or when `flush()` is called explicitly. In browser environments
|
|
10
|
+
* the client also flushes on `beforeunload` and when the tab is hidden.
|
|
11
|
+
*
|
|
12
|
+
* Event collection is disabled by default. Pass `consent: true` or call
|
|
13
|
+
* `setConsent(true)` after the user grants permission before tracking events.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const client = createFunnelClient({
|
|
18
|
+
* apiKey: "fjs_live_abc123",
|
|
19
|
+
* endpoint: "https://ingest.funelr.io/v1/collect",
|
|
20
|
+
* consent: true,
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* client.track("page_view", { path: "/home" })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function createFunnelClient(config) {
|
|
27
|
+
try {
|
|
28
|
+
new URL(config.endpoint);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error(`[FunnelClient] Invalid endpoint URL: "${config.endpoint}"`);
|
|
32
|
+
}
|
|
33
|
+
let consentGiven = config.consent ?? false;
|
|
34
|
+
const batchSize = config.batchSize ?? 20;
|
|
35
|
+
const flushInterval = config.flushInterval ?? 5000;
|
|
36
|
+
const maxRetries = config.maxRetries ?? 3;
|
|
37
|
+
const queue = [];
|
|
38
|
+
let flushTimer;
|
|
39
|
+
function startTimer() {
|
|
40
|
+
if (flushTimer !== undefined)
|
|
41
|
+
return;
|
|
42
|
+
flushTimer = setInterval(flush, flushInterval);
|
|
43
|
+
}
|
|
44
|
+
function stopTimer() {
|
|
45
|
+
if (flushTimer !== undefined) {
|
|
46
|
+
clearInterval(flushTimer);
|
|
47
|
+
flushTimer = undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function flush() {
|
|
51
|
+
if (queue.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
const batch = queue.splice(0, queue.length);
|
|
54
|
+
sendBatch({
|
|
55
|
+
endpoint: config.endpoint,
|
|
56
|
+
events: batch,
|
|
57
|
+
...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}),
|
|
58
|
+
maxRetries,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function getAnonymousId() {
|
|
62
|
+
if (!consentGiven)
|
|
63
|
+
return undefined;
|
|
64
|
+
return getOrCreateAnonymousId(config.anonymousIdStorageKey);
|
|
65
|
+
}
|
|
66
|
+
function getSessionId() {
|
|
67
|
+
if (!consentGiven)
|
|
68
|
+
return undefined;
|
|
69
|
+
return getOrCreateSessionId(config.sessionStorageKey);
|
|
70
|
+
}
|
|
71
|
+
function track(eventName, properties) {
|
|
72
|
+
if (!consentGiven)
|
|
73
|
+
return;
|
|
74
|
+
if (config.allowedEventNames && !config.allowedEventNames.includes(eventName))
|
|
75
|
+
return;
|
|
76
|
+
const anonymousId = getAnonymousId();
|
|
77
|
+
const sessionId = getSessionId();
|
|
78
|
+
const url = typeof location !== "undefined" ? location.href : undefined;
|
|
79
|
+
const referrer = typeof document !== "undefined" ? document.referrer || undefined : undefined;
|
|
80
|
+
const payload = {
|
|
81
|
+
eventName,
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
sdkVersion: SDK_VERSION,
|
|
84
|
+
...(anonymousId !== undefined ? { anonymousId } : {}),
|
|
85
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
86
|
+
...(url !== undefined ? { url } : {}),
|
|
87
|
+
...(referrer !== undefined ? { referrer } : {}),
|
|
88
|
+
...(properties !== undefined ? { properties } : {}),
|
|
89
|
+
};
|
|
90
|
+
queue.push(payload);
|
|
91
|
+
startTimer();
|
|
92
|
+
if (queue.length >= batchSize) {
|
|
93
|
+
flush();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function setConsent(given) {
|
|
97
|
+
consentGiven = given;
|
|
98
|
+
if (!given) {
|
|
99
|
+
clearSessionId(config.sessionStorageKey);
|
|
100
|
+
clearAnonymousId(config.anonymousIdStorageKey);
|
|
101
|
+
queue.splice(0, queue.length);
|
|
102
|
+
stopTimer();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function onVisibilityChange() {
|
|
106
|
+
if (document.visibilityState === "hidden")
|
|
107
|
+
flush();
|
|
108
|
+
}
|
|
109
|
+
function destroy() {
|
|
110
|
+
flush();
|
|
111
|
+
stopTimer();
|
|
112
|
+
if (typeof window !== "undefined") {
|
|
113
|
+
window.removeEventListener("beforeunload", flush);
|
|
114
|
+
window.removeEventListener("visibilitychange", onVisibilityChange);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (typeof window !== "undefined") {
|
|
118
|
+
window.addEventListener("beforeunload", flush);
|
|
119
|
+
window.addEventListener("visibilitychange", onVisibilityChange);
|
|
120
|
+
}
|
|
121
|
+
return { track, getSessionId, getAnonymousId, setConsent, flush, destroy };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE5C,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AA6G1C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAA0B;IAC3D,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAA;IAC9E,CAAC;IAED,IAAI,YAAY,GAAG,MAAM,CAAC,OAAO,IAAI,KAAK,CAAA;IAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAA;IACxC,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,IAAI,CAAA;IAClD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,CAAC,CAAA;IAEzC,MAAM,KAAK,GAAmB,EAAE,CAAA;IAChC,IAAI,UAAsD,CAAA;IAE1D,SAAS,UAAU;QACjB,IAAI,UAAU,KAAK,SAAS;YAAE,OAAM;QACpC,UAAU,GAAG,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;IAChD,CAAC;IAED,SAAS,SAAS;QAChB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,aAAa,CAAC,UAAU,CAAC,CAAA;YACzB,UAAU,GAAG,SAAS,CAAA;QACxB,CAAC;IACH,CAAC;IAED,SAAS,KAAK;QACZ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QAC3C,SAAS,CAAC;YACR,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,KAAK;YACb,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,UAAU;SACX,CAAC,CAAA;IACJ,CAAC;IAED,SAAS,cAAc;QACrB,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAA;QACnC,OAAO,sBAAsB,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;IAC7D,CAAC;IAED,SAAS,YAAY;QACnB,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAA;QACnC,OAAO,oBAAoB,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAA;IACvD,CAAC;IAED,SAAS,KAAK,CAAC,SAAiB,EAAE,UAAoC;QACpE,IAAI,CAAC,YAAY;YAAE,OAAM;QACzB,IAAI,MAAM,CAAC,iBAAiB,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAM;QAErF,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;QACpC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;QAChC,MAAM,GAAG,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;QACvE,MAAM,QAAQ,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;QAE7F,MAAM,OAAO,GAAiB;YAC5B,SAAS;YACT,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,UAAU,EAAE,WAAW;YACvB,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpD,CAAA;QAED,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACnB,UAAU,EAAE,CAAA;QAEZ,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YAC9B,KAAK,EAAE,CAAA;QACT,CAAC;IACH,CAAC;IAED,SAAS,UAAU,CAAC,KAAc;QAChC,YAAY,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,cAAc,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAA;YACxC,gBAAgB,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;YAC9C,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;YAC7B,SAAS,EAAE,CAAA;QACb,CAAC;IACH,CAAC;IAED,SAAS,kBAAkB;QACzB,IAAI,QAAQ,CAAC,eAAe,KAAK,QAAQ;YAAE,KAAK,EAAE,CAAA;IACpD,CAAC;IAED,SAAS,OAAO;QACd,KAAK,EAAE,CAAA;QACP,SAAS,EAAE,CAAA;QACX,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,mBAAmB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;YACjD,MAAM,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;QAC9C,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAA;IACjE,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AAC5E,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Current version of the SDK, injected into every event payload. */
|
|
2
|
+
export declare const SDK_VERSION = "0.4.0";
|
|
3
|
+
/** Maximum allowed length for event names. */
|
|
4
|
+
export declare const EVENT_NAME_MAX_LENGTH = 100;
|
|
5
|
+
/** Maximum allowed length for session and anonymous identifier fields. */
|
|
6
|
+
export declare const SESSION_ID_MAX_LENGTH = 64;
|
|
7
|
+
/** Maximum allowed length for URL and referrer fields. */
|
|
8
|
+
export declare const URL_MAX_LENGTH = 2048;
|
|
9
|
+
/** Regular expression that matches a UUID v4 string. */
|
|
10
|
+
export declare const UUID_V4_REGEX: RegExp;
|
|
11
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,eAAO,MAAM,WAAW,UAAU,CAAA;AAElC,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,KAAK,CAAA;AAEvC,0DAA0D;AAC1D,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,wDAAwD;AACxD,eAAO,MAAM,aAAa,QACgD,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Current version of the SDK, injected into every event payload. */
|
|
2
|
+
export const SDK_VERSION = "0.4.0";
|
|
3
|
+
/** Maximum allowed length for event names. */
|
|
4
|
+
export const EVENT_NAME_MAX_LENGTH = 100;
|
|
5
|
+
/** Maximum allowed length for session and anonymous identifier fields. */
|
|
6
|
+
export const SESSION_ID_MAX_LENGTH = 64;
|
|
7
|
+
/** Maximum allowed length for URL and referrer fields. */
|
|
8
|
+
export const URL_MAX_LENGTH = 2048;
|
|
9
|
+
/** Regular expression that matches a UUID v4 string. */
|
|
10
|
+
export const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAA;AAElC,8CAA8C;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAA;AAExC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAEvC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAA;AAElC,wDAAwD;AACxD,MAAM,CAAC,MAAM,aAAa,GACxB,wEAAwE,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createFunnelClient, type FunnelClient, type FunnelClientConfig } from "./client.js";
|
|
2
|
+
export { EVENT_NAME_MAX_LENGTH, SDK_VERSION, SESSION_ID_MAX_LENGTH, URL_MAX_LENGTH, UUID_V4_REGEX, } from "./constants.js";
|
|
3
|
+
export { type EventPayload, eventPayloadSchema } from "./schemas.js";
|
|
4
|
+
export type { FunnelConfig, FunnelStatsResponse, FunnelStep, FunnelStepStat, FunnelSummaryResponse, PropertyBreakdownItem, RawEvent, RawEventsResponse, SessionEvent, TopEventStat, TrendDataPoint, TrendsResponse, } from "./types.js";
|
|
5
|
+
export { clearAnonymousId, clearSessionId, getOrCreateAnonymousId, getOrCreateSessionId, } from "./session.js";
|
|
6
|
+
export { isAllowedEventName, isValidUUID } from "./validation.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC5F,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,qBAAqB,EACrB,cAAc,EACd,aAAa,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,KAAK,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACpE,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createFunnelClient } from "./client.js";
|
|
2
|
+
export { EVENT_NAME_MAX_LENGTH, SDK_VERSION, SESSION_ID_MAX_LENGTH, URL_MAX_LENGTH, UUID_V4_REGEX, } from "./constants.js";
|
|
3
|
+
export { eventPayloadSchema } from "./schemas.js";
|
|
4
|
+
export { clearAnonymousId, clearSessionId, getOrCreateAnonymousId, getOrCreateSessionId, } from "./session.js";
|
|
5
|
+
export { isAllowedEventName, isValidUUID } from "./validation.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA8C,MAAM,aAAa,CAAA;AAC5F,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,qBAAqB,EACrB,cAAc,EACd,aAAa,GACd,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAqB,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAepE,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for validating an ingest event payload.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the server-side contract accepted by `POST /v1/collect/batch`.
|
|
6
|
+
* Use this schema on the server or in tests to assert payload correctness.
|
|
7
|
+
*/
|
|
8
|
+
export declare const eventPayloadSchema: z.ZodObject<{
|
|
9
|
+
/** Name of the event, e.g. `"page_view"` or `"cta_click"`. */
|
|
10
|
+
eventName: z.ZodString;
|
|
11
|
+
/** Persistent visitor identifier stored in `localStorage` (post-consent). */
|
|
12
|
+
anonymousId: z.ZodOptional<z.ZodString>;
|
|
13
|
+
/** Session-scoped identifier stored in `sessionStorage`. */
|
|
14
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
15
|
+
/** Full URL of the page where the event occurred. */
|
|
16
|
+
url: z.ZodOptional<z.ZodString>;
|
|
17
|
+
/** HTTP referrer. An empty string is accepted when no referrer is present. */
|
|
18
|
+
referrer: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
|
|
19
|
+
/** Arbitrary key/value properties attached to the event. */
|
|
20
|
+
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
21
|
+
/** ISO 8601 timestamp recorded on the client at the time of the event. */
|
|
22
|
+
timestamp: z.ZodOptional<z.ZodString>;
|
|
23
|
+
/** Version string of the SDK that produced this payload, e.g. `"0.3.0"`. */
|
|
24
|
+
sdkVersion: z.ZodOptional<z.ZodString>;
|
|
25
|
+
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
eventName: string;
|
|
27
|
+
anonymousId?: string | undefined;
|
|
28
|
+
sessionId?: string | undefined;
|
|
29
|
+
url?: string | undefined;
|
|
30
|
+
referrer?: string | undefined;
|
|
31
|
+
properties?: Record<string, unknown> | undefined;
|
|
32
|
+
timestamp?: string | undefined;
|
|
33
|
+
sdkVersion?: string | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
eventName: string;
|
|
36
|
+
anonymousId?: string | undefined;
|
|
37
|
+
sessionId?: string | undefined;
|
|
38
|
+
url?: string | undefined;
|
|
39
|
+
referrer?: string | undefined;
|
|
40
|
+
properties?: Record<string, unknown> | undefined;
|
|
41
|
+
timestamp?: string | undefined;
|
|
42
|
+
sdkVersion?: string | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
/** Ingest event payload — the unit of data sent to `POST /v1/collect/batch`. */
|
|
45
|
+
export type EventPayload = z.infer<typeof eventPayloadSchema>;
|
|
46
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;IAC7B,8DAA8D;;IAE9D,6EAA6E;;IAE7E,4DAA4D;;IAE5D,qDAAqD;;IAErD,8EAA8E;;IAE9E,4DAA4D;;IAE5D,0EAA0E;;IAE1E,4EAA4E;;;;;;;;;;;;;;;;;;;;EAE5E,CAAA;AAEF,gFAAgF;AAChF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA"}
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EVENT_NAME_MAX_LENGTH, SESSION_ID_MAX_LENGTH, URL_MAX_LENGTH } from "./constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for validating an ingest event payload.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the server-side contract accepted by `POST /v1/collect/batch`.
|
|
7
|
+
* Use this schema on the server or in tests to assert payload correctness.
|
|
8
|
+
*/
|
|
9
|
+
export const eventPayloadSchema = z.object({
|
|
10
|
+
/** Name of the event, e.g. `"page_view"` or `"cta_click"`. */
|
|
11
|
+
eventName: z.string().min(1).max(EVENT_NAME_MAX_LENGTH),
|
|
12
|
+
/** Persistent visitor identifier stored in `localStorage` (post-consent). */
|
|
13
|
+
anonymousId: z.string().min(1).max(SESSION_ID_MAX_LENGTH).optional(),
|
|
14
|
+
/** Session-scoped identifier stored in `sessionStorage`. */
|
|
15
|
+
sessionId: z.string().max(SESSION_ID_MAX_LENGTH).optional(),
|
|
16
|
+
/** Full URL of the page where the event occurred. */
|
|
17
|
+
url: z.string().url().max(URL_MAX_LENGTH).optional(),
|
|
18
|
+
/** HTTP referrer. An empty string is accepted when no referrer is present. */
|
|
19
|
+
referrer: z.string().url().max(URL_MAX_LENGTH).optional().or(z.literal("")),
|
|
20
|
+
/** Arbitrary key/value properties attached to the event. */
|
|
21
|
+
properties: z.record(z.string(), z.unknown()).optional(),
|
|
22
|
+
/** ISO 8601 timestamp recorded on the client at the time of the event. */
|
|
23
|
+
timestamp: z.string().datetime().optional(),
|
|
24
|
+
/** Version string of the SDK that produced this payload, e.g. `"0.3.0"`. */
|
|
25
|
+
sdkVersion: z.string().optional(),
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE7F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,8DAA8D;IAC9D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC;IACvD,6EAA6E;IAC7E,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IACpE,4DAA4D;IAC5D,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IAC3D,qDAAqD;IACrD,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE;IACpD,8EAA8E;IAC9E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3E,4DAA4D;IAC5D,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;IACxD,0EAA0E;IAC1E,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC3C,4EAA4E;IAC5E,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the session ID stored under `key`, creating and persisting a new
|
|
3
|
+
* UUID v4 if none exists. Returns `undefined` when called outside a browser
|
|
4
|
+
* context (e.g. SSR).
|
|
5
|
+
*
|
|
6
|
+
* The ID is scoped to the current browser tab via `sessionStorage` and is
|
|
7
|
+
* cleared automatically when the tab is closed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getOrCreateSessionId(key?: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Removes the session ID from `sessionStorage`.
|
|
12
|
+
*
|
|
13
|
+
* Call this when the user revokes tracking consent.
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearSessionId(key?: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the persistent anonymous visitor ID stored under `key`, creating
|
|
18
|
+
* and persisting a new UUID v4 if none exists. Returns `undefined` outside a
|
|
19
|
+
* browser context or when `localStorage` is unavailable.
|
|
20
|
+
*
|
|
21
|
+
* The ID is stored in `localStorage` and persists across sessions.
|
|
22
|
+
* It must only be created after the user has granted consent.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getOrCreateAnonymousId(key?: string): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Removes the anonymous visitor ID from `localStorage`.
|
|
27
|
+
*
|
|
28
|
+
* Call this when the user revokes tracking consent.
|
|
29
|
+
*/
|
|
30
|
+
export declare function clearAnonymousId(key?: string): void;
|
|
31
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUrE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAGjD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUvE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAGnD"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const DEFAULT_SESSION_KEY = "funnel_session_id";
|
|
2
|
+
const DEFAULT_ANONYMOUS_KEY = "funnel_anonymous_id";
|
|
3
|
+
function isBrowser() {
|
|
4
|
+
return (typeof globalThis.crypto !== "undefined" && typeof globalThis.sessionStorage !== "undefined");
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns the session ID stored under `key`, creating and persisting a new
|
|
8
|
+
* UUID v4 if none exists. Returns `undefined` when called outside a browser
|
|
9
|
+
* context (e.g. SSR).
|
|
10
|
+
*
|
|
11
|
+
* The ID is scoped to the current browser tab via `sessionStorage` and is
|
|
12
|
+
* cleared automatically when the tab is closed.
|
|
13
|
+
*/
|
|
14
|
+
export function getOrCreateSessionId(key) {
|
|
15
|
+
if (!isBrowser())
|
|
16
|
+
return undefined;
|
|
17
|
+
const storageKey = key ?? DEFAULT_SESSION_KEY;
|
|
18
|
+
const existing = sessionStorage.getItem(storageKey);
|
|
19
|
+
if (existing)
|
|
20
|
+
return existing;
|
|
21
|
+
const id = crypto.randomUUID();
|
|
22
|
+
sessionStorage.setItem(storageKey, id);
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Removes the session ID from `sessionStorage`.
|
|
27
|
+
*
|
|
28
|
+
* Call this when the user revokes tracking consent.
|
|
29
|
+
*/
|
|
30
|
+
export function clearSessionId(key) {
|
|
31
|
+
if (!isBrowser())
|
|
32
|
+
return;
|
|
33
|
+
sessionStorage.removeItem(key ?? DEFAULT_SESSION_KEY);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns the persistent anonymous visitor ID stored under `key`, creating
|
|
37
|
+
* and persisting a new UUID v4 if none exists. Returns `undefined` outside a
|
|
38
|
+
* browser context or when `localStorage` is unavailable.
|
|
39
|
+
*
|
|
40
|
+
* The ID is stored in `localStorage` and persists across sessions.
|
|
41
|
+
* It must only be created after the user has granted consent.
|
|
42
|
+
*/
|
|
43
|
+
export function getOrCreateAnonymousId(key) {
|
|
44
|
+
if (!isBrowser() || typeof localStorage === "undefined")
|
|
45
|
+
return undefined;
|
|
46
|
+
const storageKey = key ?? DEFAULT_ANONYMOUS_KEY;
|
|
47
|
+
const existing = localStorage.getItem(storageKey);
|
|
48
|
+
if (existing)
|
|
49
|
+
return existing;
|
|
50
|
+
const id = crypto.randomUUID();
|
|
51
|
+
localStorage.setItem(storageKey, id);
|
|
52
|
+
return id;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Removes the anonymous visitor ID from `localStorage`.
|
|
56
|
+
*
|
|
57
|
+
* Call this when the user revokes tracking consent.
|
|
58
|
+
*/
|
|
59
|
+
export function clearAnonymousId(key) {
|
|
60
|
+
if (!isBrowser() || typeof localStorage === "undefined")
|
|
61
|
+
return;
|
|
62
|
+
localStorage.removeItem(key ?? DEFAULT_ANONYMOUS_KEY);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,MAAM,mBAAmB,GAAG,mBAAmB,CAAA;AAC/C,MAAM,qBAAqB,GAAG,qBAAqB,CAAA;AAEnD,SAAS,SAAS;IAChB,OAAO,CACL,OAAO,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,UAAU,CAAC,cAAc,KAAK,WAAW,CAC7F,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO,SAAS,CAAA;IAElC,MAAM,UAAU,GAAG,GAAG,IAAI,mBAAmB,CAAA;IAC7C,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACnD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAE7B,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;IAC9B,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IACtC,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,IAAI,CAAC,SAAS,EAAE;QAAE,OAAM;IACxB,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,mBAAmB,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAY;IACjD,IAAI,CAAC,SAAS,EAAE,IAAI,OAAO,YAAY,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA;IAEzE,MAAM,UAAU,GAAG,GAAG,IAAI,qBAAqB,CAAA;IAC/C,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACjD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAE7B,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;IAC9B,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IACpC,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,IAAI,CAAC,SAAS,EAAE,IAAI,OAAO,YAAY,KAAK,WAAW;QAAE,OAAM;IAC/D,YAAY,CAAC,UAAU,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAA;AACvD,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EventPayload } from "./schemas.js";
|
|
2
|
+
/** Options for {@link sendBatch}. */
|
|
3
|
+
export interface BatchSendOptions {
|
|
4
|
+
/** Base endpoint URL, e.g. `"https://ingest.funelr.io/v1/collect"`. */
|
|
5
|
+
endpoint: string;
|
|
6
|
+
/** Events to deliver. */
|
|
7
|
+
events: EventPayload[];
|
|
8
|
+
/** API key sent as the `X-Api-Key` request header. */
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
/** Maximum retry attempts on network errors or 5xx responses. Defaults to `3`. */
|
|
11
|
+
maxRetries?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sends a batch of events to `{endpoint}/batch` via `POST`.
|
|
15
|
+
*
|
|
16
|
+
* For single-event flushes the function attempts `navigator.sendBeacon` first
|
|
17
|
+
* to guarantee delivery during page unload. Multi-event batches fall back to
|
|
18
|
+
* `fetch` with exponential-backoff retries on network errors and 5xx responses.
|
|
19
|
+
*
|
|
20
|
+
* Delivery failures are intentionally silent — event tracking is best-effort.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sendBatch({ endpoint, events, apiKey, maxRetries }: BatchSendOptions): void;
|
|
23
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAA;IAChB,yBAAyB;IACzB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA4BD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAc,EAAE,EAAE,gBAAgB,GAAG,IAAI,CAe9F"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function backoff(attempt) {
|
|
2
|
+
return new Promise((resolve) => setTimeout(resolve, Math.min(1000 * 2 ** attempt, 30_000)));
|
|
3
|
+
}
|
|
4
|
+
async function sendWithRetry(endpoint, body, headers, maxRetries, attempt = 0) {
|
|
5
|
+
try {
|
|
6
|
+
const res = await fetch(endpoint, { method: "POST", headers, body, keepalive: true });
|
|
7
|
+
if (!res.ok && res.status >= 500 && attempt < maxRetries) {
|
|
8
|
+
await backoff(attempt);
|
|
9
|
+
return sendWithRetry(endpoint, body, headers, maxRetries, attempt + 1);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
if (attempt < maxRetries) {
|
|
14
|
+
await backoff(attempt);
|
|
15
|
+
return sendWithRetry(endpoint, body, headers, maxRetries, attempt + 1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sends a batch of events to `{endpoint}/batch` via `POST`.
|
|
21
|
+
*
|
|
22
|
+
* For single-event flushes the function attempts `navigator.sendBeacon` first
|
|
23
|
+
* to guarantee delivery during page unload. Multi-event batches fall back to
|
|
24
|
+
* `fetch` with exponential-backoff retries on network errors and 5xx responses.
|
|
25
|
+
*
|
|
26
|
+
* Delivery failures are intentionally silent — event tracking is best-effort.
|
|
27
|
+
*/
|
|
28
|
+
export function sendBatch({ endpoint, events, apiKey, maxRetries = 3 }) {
|
|
29
|
+
if (events.length === 0)
|
|
30
|
+
return;
|
|
31
|
+
const body = JSON.stringify({ events });
|
|
32
|
+
const headers = { "Content-Type": "application/json" };
|
|
33
|
+
if (apiKey)
|
|
34
|
+
headers["X-Api-Key"] = apiKey;
|
|
35
|
+
const batchEndpoint = `${endpoint}/batch`;
|
|
36
|
+
if (events.length === 1 && typeof navigator?.sendBeacon === "function") {
|
|
37
|
+
const blob = new Blob([body], { type: "application/json" });
|
|
38
|
+
if (navigator.sendBeacon(batchEndpoint, blob))
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
sendWithRetry(batchEndpoint, body, headers, maxRetries).catch(() => { });
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAeA,SAAS,OAAO,CAAC,OAAe;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAC7F,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,QAAgB,EAChB,IAAY,EACZ,OAA+B,EAC/B,UAAkB,EAClB,OAAO,GAAG,CAAC;IAEX,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACrF,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;YACzD,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;YACtB,OAAO,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,CAAC,CAAC,CAAA;QACxE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;YACzB,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;YACtB,OAAO,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,CAAC,CAAC,CAAA;QACxE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAoB;IACtF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAE/B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IACvC,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAA;IAC9E,IAAI,MAAM;QAAE,OAAO,CAAC,WAAW,CAAC,GAAG,MAAM,CAAA;IAEzC,MAAM,aAAa,GAAG,GAAG,QAAQ,QAAQ,CAAA;IAEzC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,SAAS,EAAE,UAAU,KAAK,UAAU,EAAE,CAAC;QACvE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAA;QAC3D,IAAI,SAAS,CAAC,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC;YAAE,OAAM;IACvD,CAAC;IAED,aAAa,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AACzE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** A single step in a funnel definition. */
|
|
2
|
+
export interface FunnelStep {
|
|
3
|
+
/** Event name that represents reaching this step. */
|
|
4
|
+
eventName: string;
|
|
5
|
+
/** Human-readable label shown in the dashboard. */
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
/** Configuration for a funnel, as returned by the funelr.io API. */
|
|
9
|
+
export interface FunnelConfig {
|
|
10
|
+
/** UUID of the project this funnel belongs to. */
|
|
11
|
+
siteId: string;
|
|
12
|
+
/** Ordered list of steps that define the funnel. */
|
|
13
|
+
steps: FunnelStep[];
|
|
14
|
+
}
|
|
15
|
+
/** Response from `GET /v1/stats/:projectId/summary`. */
|
|
16
|
+
export interface FunnelSummaryResponse {
|
|
17
|
+
/** Start of the requested period (ISO 8601). */
|
|
18
|
+
from: string;
|
|
19
|
+
/** End of the requested period (ISO 8601). */
|
|
20
|
+
to: string;
|
|
21
|
+
/** Number of unique `anonymous_id` values observed in the period. */
|
|
22
|
+
visitors: number;
|
|
23
|
+
/** Total event count keyed by `event_name`. */
|
|
24
|
+
eventTotals: Record<string, number>;
|
|
25
|
+
}
|
|
26
|
+
/** A single step entry within a {@link FunnelStatsResponse}. */
|
|
27
|
+
export interface FunnelStepStat {
|
|
28
|
+
step_order: number;
|
|
29
|
+
name: string;
|
|
30
|
+
event_name: string;
|
|
31
|
+
users_reached: number;
|
|
32
|
+
/** Conversion rate relative to the first step, or `null` for the first step itself. */
|
|
33
|
+
conversion_from_first: number | null;
|
|
34
|
+
/** Conversion rate relative to the previous step, or `null` for the first step. */
|
|
35
|
+
conversion_from_prev: number | null;
|
|
36
|
+
}
|
|
37
|
+
/** Response from `GET /v1/stats/:projectId/funnels/:funnelId`. */
|
|
38
|
+
export interface FunnelStatsResponse {
|
|
39
|
+
funnelId: string;
|
|
40
|
+
funnelName: string;
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
overallConversionRate: number;
|
|
44
|
+
steps: FunnelStepStat[];
|
|
45
|
+
}
|
|
46
|
+
/** A single time-series data point within a {@link TrendsResponse}. */
|
|
47
|
+
export interface TrendDataPoint {
|
|
48
|
+
/** Bucket start time (ISO 8601). */
|
|
49
|
+
bucket: string;
|
|
50
|
+
event_count: number;
|
|
51
|
+
unique_visitors: number;
|
|
52
|
+
}
|
|
53
|
+
/** Response from `GET /v1/stats/:projectId/trends`. */
|
|
54
|
+
export interface TrendsResponse {
|
|
55
|
+
series: Array<{
|
|
56
|
+
eventName: string;
|
|
57
|
+
data: TrendDataPoint[];
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
/** A single row from `GET /analytics/:projectId/events/top`. */
|
|
61
|
+
export interface TopEventStat {
|
|
62
|
+
event_name: string;
|
|
63
|
+
total_count: number;
|
|
64
|
+
unique_visitors: number;
|
|
65
|
+
}
|
|
66
|
+
/** A single row from `GET /analytics/:projectId/property-breakdown`. */
|
|
67
|
+
export interface PropertyBreakdownItem {
|
|
68
|
+
value: string;
|
|
69
|
+
count: number;
|
|
70
|
+
}
|
|
71
|
+
/** A single event record from `GET /analytics/:projectId/raw-events`. */
|
|
72
|
+
export interface RawEvent {
|
|
73
|
+
id: string;
|
|
74
|
+
event_name: string;
|
|
75
|
+
anonymous_id: string;
|
|
76
|
+
properties: Record<string, unknown> | null;
|
|
77
|
+
server_received_at: string;
|
|
78
|
+
}
|
|
79
|
+
/** Response from `GET /analytics/:projectId/raw-events`. */
|
|
80
|
+
export interface RawEventsResponse {
|
|
81
|
+
events: RawEvent[];
|
|
82
|
+
total: number;
|
|
83
|
+
limit: number;
|
|
84
|
+
offset: number;
|
|
85
|
+
}
|
|
86
|
+
/** A single event record from `GET /analytics/:projectId/sessions/:anonymousId/events`. */
|
|
87
|
+
export interface SessionEvent {
|
|
88
|
+
id: string;
|
|
89
|
+
event_name: string;
|
|
90
|
+
properties: Record<string, unknown> | null;
|
|
91
|
+
url: string | null;
|
|
92
|
+
referrer: string | null;
|
|
93
|
+
server_received_at: string;
|
|
94
|
+
client_sent_at: string | null;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Convention: nullable fields in API response types use `null` (mirrors JSON).
|
|
3
|
+
// Optional fields in request/config types use `undefined` (TypeScript idiom).
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `true` when `value` is a well-formed UUID v4 string.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* isValidUUID("550e8400-e29b-41d4-a716-446655440000") // true
|
|
6
|
+
* isValidUUID("not-a-uuid") // false
|
|
7
|
+
*/
|
|
8
|
+
export declare function isValidUUID(value: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Returns `true` when `name` is a non-empty string within the allowed length
|
|
11
|
+
* and, if `allowedNames` is provided, is present in that allowlist.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* isAllowedEventName("page_view") // true (no allowlist)
|
|
15
|
+
* isAllowedEventName("page_view", ["page_view", "click"]) // true
|
|
16
|
+
* isAllowedEventName("unknown", ["page_view", "click"]) // false
|
|
17
|
+
*/
|
|
18
|
+
export declare function isAllowedEventName(name: string, allowedNames?: readonly string[]): boolean;
|
|
19
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAI1F"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EVENT_NAME_MAX_LENGTH, UUID_V4_REGEX } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns `true` when `value` is a well-formed UUID v4 string.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* isValidUUID("550e8400-e29b-41d4-a716-446655440000") // true
|
|
7
|
+
* isValidUUID("not-a-uuid") // false
|
|
8
|
+
*/
|
|
9
|
+
export function isValidUUID(value) {
|
|
10
|
+
return UUID_V4_REGEX.test(value);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns `true` when `name` is a non-empty string within the allowed length
|
|
14
|
+
* and, if `allowedNames` is provided, is present in that allowlist.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* isAllowedEventName("page_view") // true (no allowlist)
|
|
18
|
+
* isAllowedEventName("page_view", ["page_view", "click"]) // true
|
|
19
|
+
* isAllowedEventName("unknown", ["page_view", "click"]) // false
|
|
20
|
+
*/
|
|
21
|
+
export function isAllowedEventName(name, allowedNames) {
|
|
22
|
+
if (name.length === 0 || name.length > EVENT_NAME_MAX_LENGTH)
|
|
23
|
+
return false;
|
|
24
|
+
if (!allowedNames)
|
|
25
|
+
return true;
|
|
26
|
+
return allowedNames.includes(name);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAErE;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,YAAgC;IAC/E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB;QAAE,OAAO,KAAK,CAAA;IAC1E,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAA;IAC9B,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AACpC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@funelr/events",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Official event tracking SDK for funelr.io",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepare": "npm run build",
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"lint": "biome check .",
|
|
22
|
+
"lint:fix": "biome check --write .",
|
|
23
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"zod": "^3.24.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@biomejs/biome": "^2.0.0",
|
|
30
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
31
|
+
"jsdom": "^28.1.0",
|
|
32
|
+
"typescript": "^5.7.0",
|
|
33
|
+
"vitest": "^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/funelr/events-sdk.git"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"analytics",
|
|
47
|
+
"funnel",
|
|
48
|
+
"tracking",
|
|
49
|
+
"events",
|
|
50
|
+
"sdk"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|