@quacklytics/client 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,22 @@
1
+
2
+ MIT License
3
+
4
+ Copyright (c) 2025
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,105 @@
1
- # Temporary Holding Version
1
+ # @quacklytics/client
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
+ First-party TypeScript analytics for Quacklytics.
4
+
5
+ ```ts
6
+ import { createClient } from '@quacklytics/client'
7
+
8
+ const analytics = createClient({
9
+ hostname: 'https://analytics.example.com',
10
+ apiKey: 'public-project-ingestion-key',
11
+ autocapture: true,
12
+ })
13
+
14
+ // Start from your consent UI. Before a decision, events are buffered locally.
15
+ analytics.setConsent('granted')
16
+ await analytics.track('signup', { plan: 'pro' })
17
+ await analytics.identify('account-123', { plan: 'pro' })
18
+ await analytics.flush()
19
+
20
+ // On logout, before tracking activity for a new account:
21
+ analytics.reset()
22
+ ```
23
+
24
+ Add the exact site origin to the project's allowed origins. Browser ingestion keys are public, ingestion-only credentials; never use an administrative credential. Always specify a full HTTPS service URL in production. Bare hostnames default to HTTPS.
25
+
26
+ ## Events and autocapture
27
+
28
+ `$pageview` is collected after permission, on initial load and on pushState, replaceState, back/forward, and Astro page-load navigation. Query strings and fragments are removed by default; changing only a removed parameter is not a new pageview. Set `allowedQueryParameters: ['utm_source', 'utm_campaign']` to retain approved campaign fields. Referrers and captured link URLs always omit query/fragment and URL credentials. Paths and titles may themselves contain private information; redact them with `beforeSend` as appropriate.
29
+
30
+ `page(properties?)` is the manual router adapter. For TanStack Router, call it from an `onResolved` subscription and set `autoTrackPageviews: false`. Subscribe once and unsubscribe when disposing the integration. Repeated calls for the same sanitized URL are deduplicated. `pageViewId` associates subsequent events with the current page. Automatic collectors install once; a second client for the same service/key throws rather than double-counting.
31
+
32
+ `autocapture: true` collects `$click` from links, buttons, role=button/link, and elements with `data-ql-event`. It records tagName, sanitized link URL, and only role/type/data-ql-id attributes. It never reads text, input values, arbitrary data attributes, element IDs, or CSS classes.
33
+
34
+ ```html
35
+ <button data-ql-event="trial_started" data-ql-id="hero-trial">Start trial</button>
36
+ <div data-ql-ignore><button>Private action</button></div>
37
+ ```
38
+
39
+ Use `data-ql-ignore` on an element or ancestor to exclude the subtree. Consent-banner clicks are excluded. Custom names cannot start with `$`; `$pageview` and `$click` are reserved built-ins with validated payloads. `enableClickAutotracking({ client, selectors })` is available for manually installed collection, returning `stop()`; do not also enable built-in autocapture.
40
+
41
+ ## Identity and consent
42
+
43
+ The SDK maintains an anonymous visitor UUID and a session UUID, rotating the latter after 30 minutes of inactivity. Identity and consent use localStorage scoped by service endpoint and project key and synchronize across tabs. Storage failures fall back to in-memory identity. `sessionTimeoutMs` changes the inactivity window.
44
+
45
+ `identify(userId, traits?)` durably links the current anonymous visitor's history across sessions to the application user, then sets that user on subsequent events. It is a control operation, excluded from event counts. Multiple visitors/devices can identify as the same user. A visitor cannot change users without `reset()`. Reset creates a new anonymous/session identity on the next event; already queued events retain their original capture identity. Person traits merge per key using timestamp/event ID ordering.
46
+
47
+ The service resolves SDK actors as `anonymous:<id>` and `user:<id>` in its existing person_id analytical column. Legacy first-party person IDs remain unchanged. Identity changes republish affected historical Parquet partitions that are not yet archived; a committed identification can therefore precede updated browser reports. Archived months (seven days after a month ends) keep their original attribution.
48
+
49
+ Default consent is `required`. Three modes follow from the visitor's decision:
50
+
51
+ - **Undecided:** nothing is sent. Captures (including `identify`) are buffered in localStorage, survive reloads, and return `{ status: 'buffered' }`. The buffer is bounded by `maxQueueSize`, `maxQueueBytes` and `maxQueueAgeMs`.
52
+ - **Granted:** buffered captures are released with the persistent visitor/session identity, in order, so pre-consent activity and any buffered `identify` are attributed normally. Collection continues with full identity.
53
+ - **Denied:** buffered events are released anonymously and collection continues in anonymous mode. Events are sent with `anonymous: true` and no identifiers; nothing about the visitor is stored. The service derives a `daily:<hash>` visitor from HMAC-SHA-256 over the project, client IP and user agent, keyed with a random salt per UTC day. Salts older than yesterday are deleted the next time a declined visitor's event is processed, after which an ID can no longer be linked back to anyone. These visitors appear as `anonymous:daily:<hash>` in SQL. A visit continues one session until 30 minutes of inactivity, including across midnight. `identify`/`reset` are disabled and buffered `identify` calls are discarded. Visitors behind the same IP with the same browser are counted as one.
54
+
55
+ `setConsent('denied')` after granting clears the visitor identity and pending outbox, cancels delivery, persists the choice and switches to anonymous mode. Already sent data cannot be recalled. Each identity change records the current page once. `consent: 'granted'` explicitly opts into immediate collection, but a persisted denial still takes precedence.
56
+
57
+ Optional UI:
58
+
59
+ ```ts
60
+ import { createCookieConsent } from '@quacklytics/client/consent'
61
+ const banner = createCookieConsent({ client: analytics })
62
+ banner.showBanner()
63
+ ```
64
+
65
+ The banner is presentation only; the client always enforces collection state. It follows the page's CSS `color-scheme` (falling back to the visitor's OS preference), so it switches to dark mode with your site. Pass `css` in the banner options to restyle it; scope selectors with `[data-quacklytics-cookie-banner]` and target `.ql-banner`, `.ql-text`, `.ql-actions`, `.ql-primary` or `.ql-secondary`. `/consent` imports Preact; the core and server entrypoints do not. Custom `textHtml` must be trusted application-authored markup.
66
+
67
+ ## Delivery and lifecycle
68
+
69
+ - `track`/`page` resolve to `{status: 'queued', id}`, `disabled`, or `dropped`. Queued means accepted locally, not committed. `beforeSend(event)` may redact or return null; identity, timestamp and ID are immutable. Invalid payloads/overflow reject the promise.
70
+ - `identify` waits for the flush containing the control operation. `flush()` resolves only after every batch it drains receives a validated commit acknowledgment. Failures reject; uncertain/transient deliveries remain queued.
71
+ - Automatic browser batching flushes every 5 seconds, at 20 pending operations, and on hidden/pagehide. Envelopes remain below 55 KB for keepalive delivery. Lifecycle delivery is best-effort; a browser can terminate without sending.
72
+ - Network failures, timeouts, 429, and 5xx are attempted up to 3 times per flush (`maxAttempts`) with exponential jitter and Retry-After (capped at 60 seconds); batches that still fail stay queued for the next flush. Other HTTP errors drop the rejected batch and reject flush without retry. Request timeout defaults to 10 seconds. Exact request/event IDs and serialized bodies survive retries and reloads.
73
+ - The bounded outbox uses **per-tab sessionStorage**: survives reloads and temporary outages, not closing the tab. Tabs do not overwrite each other's outboxes. Storage failures leave a memory-only queue. Defaults: 1,000 operations, 1 MB serialized queue, 48 KB per operation, 24-hour retention. Expiry reports through onError; overflow rejects rather than silently evicting older data. `maxQueueSize`, `maxQueueBytes`, `maxEventBytes`, `maxQueueAgeMs`, `batchSize`, `flushIntervalMs`, `maxAttempts`, `retryDelayMs`, and `requestTimeoutMs` configure these bounds.
74
+ - `destroy()` removes collectors/timers and cancels in-flight delivery; it retains the outbox for a future permitted client. Call `await flush()` before `destroy()` when a committed shutdown is required.
75
+ - Automatic failures call `onError`; application-triggered track/flush/identify failures reject their promises.
76
+
77
+ Typed application events:
78
+
79
+ ```ts
80
+ interface Events {
81
+ signup: { plan: string }
82
+ purchase: { amount: number; currency: string }
83
+ }
84
+ const client = createClient<Events>({ hostname, apiKey })
85
+ await client.track('purchase', { amount: 42, currency: 'EUR' })
86
+ ```
87
+
88
+ ## Server and existing first-party callers
89
+
90
+ ```ts
91
+ import { createServerClient } from '@quacklytics/client/server'
92
+ const client = createServerClient({ hostname, apiKey })
93
+ await client.track('invoice_paid', { amount: 42 }, { userId: 'account-123' })
94
+ await client.flush()
95
+ await client.destroy()
96
+ ```
97
+
98
+ Server `send`/`sendBatch` preserve the existing first-party importer protocol, including `deferExport`, `backfill` (for archived months) and AbortSignal. They acknowledge committed batches and never collect browser context or create identities. Use `personId: 'user:account-123'` when intentionally joining a server event to the SDK's resolved user actor. `track()` uses the versioned queue and requires an explicit `flush()` in server runtimes. Browser `send`/`sendBatch` are convenience capture-and-flush methods and may split into multiple transactions; a later invalid event can leave earlier events queued. Explicit browser identity fields, `personProperties`, AbortSignal, `deferExport` and `backfill` are rejected: use identify/reset for browser identity and the server importer for explicit batches. The legacy cookie-wrapper API has been replaced; rejection now means no collection.
99
+
100
+ The `/protocol` entrypoint exports Zod schemas and inferred types for versioned events and identify operations. Keep protocol changes backward compatible or introduce a new version.
101
+
102
+
103
+ ## Upgrading from 0.1
104
+
105
+ This is a breaking browser API release. Replace the old consent wrapper with `createClient` plus `setConsent` (or the presentation-only banner). Identity no longer uses the old `ql_person_id`/`ql_session_id` cookies; remove those legacy cookies in your application if you previously deployed them, including any configured parent-domain copies. Old consent does not silently authorize the new collector. Replace `includeText`/DOM-path capture with explicit `data-ql-id` and `data-ql-event` instrumentation. Page context now lives under `$context`; use the Website traffic template for new SDK reports. Existing generic dashboards, stored legacy events, and the first-party server importer remain supported. The public client is version 0.2.0; its new wire protocol is version 1.
@@ -0,0 +1,20 @@
1
+ import type { AnalyticsClient, EventProperties } from "./index.js";
2
+ export declare const DEFAULT_CLICK_SELECTORS: readonly ["a", "button", "[role='button']", "[role='link']", "[data-ql-event]"];
3
+ export interface ClickAutotrackOptions {
4
+ /** Consent is enforced by the client: it buffers, anonymizes, or sends each click. */
5
+ client: Pick<AnalyticsClient, "track">;
6
+ enabled?: () => boolean;
7
+ selectors?: readonly string[];
8
+ document?: Document;
9
+ onError?: (error: unknown) => void;
10
+ }
11
+ export interface ClickAutotrackHandle {
12
+ stop(): void;
13
+ }
14
+ export interface TrackPageviewOptions {
15
+ client: Pick<AnalyticsClient, "page">;
16
+ properties?: EventProperties;
17
+ }
18
+ export declare function trackPageview(options: TrackPageviewOptions): Promise<import("./index.js").CaptureResult>;
19
+ export declare function enableClickAutotracking(options: ClickAutotrackOptions): ClickAutotrackHandle;
20
+ //# sourceMappingURL=autotrack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autotrack.d.ts","sourceRoot":"","sources":["../src/autotrack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGnE,eAAO,MAAM,uBAAuB,iFAM1B,CAAC;AACX,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AACD,MAAM,WAAW,oBAAoB;IACnC,IAAI,IAAI,IAAI,CAAC;CACd;AACD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AACD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,+CAE1D;AACD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB,CAiCtB"}
@@ -0,0 +1,50 @@
1
+ import { sanitizeUrl } from "./context.js";
2
+ export const DEFAULT_CLICK_SELECTORS = [
3
+ "a",
4
+ "button",
5
+ "[role='button']",
6
+ "[role='link']",
7
+ "[data-ql-event]",
8
+ ];
9
+ export function trackPageview(options) {
10
+ return options.client.page(options.properties);
11
+ }
12
+ export function enableClickAutotracking(options) {
13
+ const doc = options.document ?? globalThis.document;
14
+ if (!doc)
15
+ throw new Error("Autocapture requires a browser");
16
+ const selector = (options.selectors ?? DEFAULT_CLICK_SELECTORS).join(",");
17
+ // Validate selectors during initialization, rather than throwing on a user click.
18
+ doc.querySelector(selector);
19
+ const listener = (event) => {
20
+ if (options.enabled?.() === false)
21
+ return;
22
+ const target = event.target;
23
+ if (!doc.defaultView || !(target instanceof doc.defaultView.Element))
24
+ return;
25
+ if (target.closest("[data-ql-ignore], [data-quacklytics-cookie-banner]"))
26
+ return;
27
+ const element = target.closest(selector);
28
+ if (!element || element.closest("[data-ql-ignore]"))
29
+ return;
30
+ const attributes = {};
31
+ for (const key of ["role", "type", "data-ql-id"]) {
32
+ const value = element.getAttribute(key);
33
+ if (value)
34
+ attributes[key] = value.slice(0, 128);
35
+ }
36
+ const properties = {
37
+ tagName: element.tagName.toLowerCase(),
38
+ attributes,
39
+ };
40
+ if (element instanceof doc.defaultView.HTMLAnchorElement)
41
+ properties.href = sanitizeUrl(element.href);
42
+ const eventType = element.getAttribute("data-ql-event") || "$click";
43
+ void options.client
44
+ .track(eventType, properties)
45
+ .catch((error) => options.onError?.(error));
46
+ };
47
+ doc.addEventListener("click", listener, true);
48
+ return { stop: () => doc.removeEventListener("click", listener, true) };
49
+ }
50
+ //# sourceMappingURL=autotrack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autotrack.js","sourceRoot":"","sources":["../src/autotrack.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,GAAG;IACH,QAAQ;IACR,iBAAiB;IACjB,eAAe;IACf,iBAAiB;CACT,CAAC;AAgBX,MAAM,UAAU,aAAa,CAAC,OAA6B;IACzD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AACjD,CAAC;AACD,MAAM,UAAU,uBAAuB,CACrC,OAA8B;IAE9B,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC;IACpD,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,uBAAuB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1E,kFAAkF;IAClF,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAG,CAAC,KAAiB,EAAE,EAAE;QACrC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,KAAK,KAAK;YAAE,OAAO;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,YAAY,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC;YAClE,OAAO;QACT,IAAI,MAAM,CAAC,OAAO,CAAC,oDAAoD,CAAC;YACtE,OAAO;QACT,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC;YAAE,OAAO;QAC5D,MAAM,UAAU,GAAoB,EAAE,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,KAAK;gBAAE,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,UAAU,GAAoB;YAClC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE;YACtC,UAAU;SACX,CAAC;QACF,IAAI,OAAO,YAAY,GAAG,CAAC,WAAW,CAAC,iBAAiB;YACtD,UAAU,CAAC,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,IAAI,QAAQ,CAAC;QACpE,KAAK,OAAO,CAAC,MAAM;aAChB,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC;aAC5B,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC;IACF,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9C,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { AnalyticsClient } from "./index.js";
2
+ export interface CookieBannerOptions {
3
+ textHtml?: string;
4
+ acceptLabel?: string;
5
+ rejectLabel?: string;
6
+ /**
7
+ * Extra CSS appended after the default banner styles. Scope selectors with
8
+ * `[data-quacklytics-cookie-banner]` and target `.ql-banner`, `.ql-text`,
9
+ * `.ql-actions`, `.ql-primary` (accept) or `.ql-secondary` (reject).
10
+ */
11
+ css?: string;
12
+ }
13
+ export interface CookieBannerHandle {
14
+ close(): void;
15
+ }
16
+ type ConsentClient = Pick<AnalyticsClient, "getConsent" | "setConsent">;
17
+ export interface CookieConsentOptions<Client extends ConsentClient = ConsentClient> {
18
+ client: Client;
19
+ enabled?: boolean;
20
+ document?: Document;
21
+ banner?: CookieBannerOptions;
22
+ }
23
+ /** Presentation only: consent and storage are always enforced by the client. */
24
+ export declare function createCookieConsent<Client extends ConsentClient>(options: CookieConsentOptions<Client>): {
25
+ client: Client;
26
+ decision: () => import("./index.js").ConsentState;
27
+ accept: () => void;
28
+ reject: () => void;
29
+ showBanner(override?: CookieBannerOptions): CookieBannerHandle | undefined;
30
+ destroy(): void;
31
+ };
32
+ export {};
33
+ //# sourceMappingURL=consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../src/consent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,IAAI,CAAC;CACf;AACD,KAAK,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;AACxE,MAAM,WAAW,oBAAoB,CACnC,MAAM,SAAS,aAAa,GAAG,aAAa;IAE5C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B;AAGD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,MAAM,SAAS,aAAa,EAC9D,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC;;;;;0BAeb,mBAAmB;;EAqB5C"}
@@ -0,0 +1,126 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ import { render } from "preact";
3
+ const DEFAULT_BANNER_HTML = "Allow analytics to help us understand how this site is used?";
4
+ /** Presentation only: consent and storage are always enforced by the client. */
5
+ export function createCookieConsent(options) {
6
+ const doc = options.document ?? globalThis.document;
7
+ let banner;
8
+ const decide = (state) => {
9
+ options.client.setConsent(state);
10
+ banner?.close();
11
+ banner = undefined;
12
+ };
13
+ if (options.enabled === false)
14
+ decide("granted");
15
+ return {
16
+ client: options.client,
17
+ decision: () => options.client.getConsent(),
18
+ accept: () => decide("granted"),
19
+ reject: () => decide("denied"),
20
+ showBanner(override) {
21
+ if (!doc ||
22
+ options.enabled === false ||
23
+ options.client.getConsent() !== "pending")
24
+ return undefined;
25
+ banner?.close();
26
+ banner = renderCookieBanner(doc, {
27
+ ...options.banner,
28
+ ...override,
29
+ onAccept: () => decide("granted"),
30
+ onReject: () => decide("denied"),
31
+ });
32
+ return banner;
33
+ },
34
+ destroy() {
35
+ banner?.close();
36
+ banner = undefined;
37
+ },
38
+ };
39
+ }
40
+ function renderCookieBanner(doc, options) {
41
+ const container = doc.createElement("div");
42
+ container.setAttribute("data-quacklytics-cookie-banner", "");
43
+ // Inherit the page's declared color-scheme (so theme toggles apply live);
44
+ // pages that declare none get the visitor's OS preference instead.
45
+ const pageScheme = doc.defaultView
46
+ ?.getComputedStyle(doc.documentElement)
47
+ .getPropertyValue("color-scheme")
48
+ .trim();
49
+ if (!pageScheme || pageScheme === "normal")
50
+ container.style.setProperty("color-scheme", "light dark");
51
+ doc.body.appendChild(container);
52
+ const close = () => {
53
+ render(null, container);
54
+ container.remove();
55
+ };
56
+ render(_jsx(CookieBanner, { textHtml: options.textHtml ?? DEFAULT_BANNER_HTML, acceptLabel: options.acceptLabel ?? "Accept", rejectLabel: options.rejectLabel ?? "Reject", css: options.css, onAccept: options.onAccept, onReject: options.onReject }), container);
57
+ return { close };
58
+ }
59
+ function CookieBanner(props) {
60
+ return (_jsxs("div", { class: "ql-banner", role: "dialog", "aria-live": "polite", "aria-label": "Analytics consent", children: [_jsx("style", { children: BANNER_CSS }), props.css ? _jsx("style", { children: props.css }) : null, _jsx("div", { class: "ql-text", dangerouslySetInnerHTML: { __html: props.textHtml } }), _jsxs("div", { class: "ql-actions", children: [_jsx("button", { type: "button", class: "ql-secondary", onClick: props.onReject, children: props.rejectLabel }), _jsx("button", { type: "button", class: "ql-primary", onClick: props.onAccept, children: props.acceptLabel })] })] }));
61
+ }
62
+ /**
63
+ * Colors follow the host page's `color-scheme` via `light-dark()`; the plain
64
+ * declaration before each one is the light fallback for older browsers.
65
+ */
66
+ const BANNER_CSS = `
67
+ [data-quacklytics-cookie-banner] .ql-banner {
68
+ position: fixed;
69
+ right: 16px;
70
+ bottom: 16px;
71
+ z-index: 2147483647;
72
+ width: min(420px, calc(100vw - 32px));
73
+ box-sizing: border-box;
74
+ display: grid;
75
+ gap: 14px;
76
+ padding: 16px;
77
+ color: #111827;
78
+ color: light-dark(#111827, #f3f4f6);
79
+ background: rgba(255, 255, 255, 0.96);
80
+ background: light-dark(rgba(255, 255, 255, 0.96), rgba(17, 24, 39, 0.96));
81
+ border: 1px solid rgba(17, 24, 39, 0.16);
82
+ border-color: light-dark(rgba(17, 24, 39, 0.16), rgba(243, 244, 246, 0.16));
83
+ border-radius: 8px;
84
+ box-shadow: 0 18px 60px rgba(17, 24, 39, 0.18);
85
+ box-shadow: 0 18px 60px light-dark(rgba(17, 24, 39, 0.18), rgba(0, 0, 0, 0.5));
86
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
87
+ font-size: 13px;
88
+ line-height: 1.45;
89
+ }
90
+ [data-quacklytics-cookie-banner] .ql-text {
91
+ margin: 0;
92
+ }
93
+ [data-quacklytics-cookie-banner] .ql-text a {
94
+ color: inherit;
95
+ }
96
+ [data-quacklytics-cookie-banner] .ql-actions {
97
+ display: flex;
98
+ justify-content: flex-end;
99
+ gap: 8px;
100
+ flex-wrap: wrap;
101
+ }
102
+ [data-quacklytics-cookie-banner] button {
103
+ margin: 0;
104
+ border-radius: 6px;
105
+ padding: 8px 12px;
106
+ font: inherit;
107
+ cursor: pointer;
108
+ }
109
+ [data-quacklytics-cookie-banner] .ql-primary {
110
+ border: 1px solid #111827;
111
+ border-color: light-dark(#111827, #f3f4f6);
112
+ background: #111827;
113
+ background: light-dark(#111827, #f3f4f6);
114
+ color: #ffffff;
115
+ color: light-dark(#ffffff, #111827);
116
+ }
117
+ [data-quacklytics-cookie-banner] .ql-secondary {
118
+ border: 1px solid rgba(17, 24, 39, 0.18);
119
+ border-color: light-dark(rgba(17, 24, 39, 0.18), rgba(243, 244, 246, 0.24));
120
+ background: #ffffff;
121
+ background: light-dark(#ffffff, #1f2937);
122
+ color: #111827;
123
+ color: light-dark(#111827, #f3f4f6);
124
+ }
125
+ `;
126
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.js","sourceRoot":"","sources":["../src/consent.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type SdkEvent } from "./protocol.js";
2
+ export declare function sanitizeUrl(value: string, allowed?: readonly string[]): string;
3
+ /** Browser, OS and device class, as attached to every browser event's context. */
4
+ export declare function describeUserAgent(userAgent: string): {
5
+ readonly browser: "Edge" | "Firefox" | "Chrome" | "Safari" | "Other";
6
+ readonly os: "Other" | "Android" | "iOS" | "Windows" | "macOS" | "Linux";
7
+ readonly deviceType: "desktop" | "mobile" | "tablet";
8
+ };
9
+ export declare function context(allowed: readonly string[], browser: boolean): SdkEvent["context"];
10
+ export declare function observeNavigation(listener: () => void): () => void;
11
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE3D,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,SAAS,MAAM,EAAO,GAC9B,MAAM,CAcR;AACD,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM;;;;EA4BlD;AACD,wBAAgB,OAAO,CACrB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,OAAO,GACf,QAAQ,CAAC,SAAS,CAAC,CAkBrB;AAID,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAuClE"}
@@ -0,0 +1,109 @@
1
+ import { SDK_VERSION } from "./protocol.js";
2
+ export function sanitizeUrl(value, allowed = []) {
3
+ if (!value)
4
+ return "";
5
+ try {
6
+ const url = new URL(value, globalThis.location?.href);
7
+ if (!["http:", "https:"].includes(url.protocol))
8
+ return "";
9
+ url.username = "";
10
+ url.password = "";
11
+ url.hash = "";
12
+ for (const key of [...url.searchParams.keys()])
13
+ if (!allowed.includes(key))
14
+ url.searchParams.delete(key);
15
+ return url.toString();
16
+ }
17
+ catch {
18
+ return "";
19
+ }
20
+ }
21
+ /** Browser, OS and device class, as attached to every browser event's context. */
22
+ export function describeUserAgent(userAgent) {
23
+ return {
24
+ browser: /Edg\//.test(userAgent)
25
+ ? "Edge"
26
+ : /Firefox\//.test(userAgent)
27
+ ? "Firefox"
28
+ : /Chrome\//.test(userAgent)
29
+ ? "Chrome"
30
+ : /Safari\//.test(userAgent)
31
+ ? "Safari"
32
+ : "Other",
33
+ os: /Android/.test(userAgent)
34
+ ? "Android"
35
+ : /iPhone|iPad/.test(userAgent)
36
+ ? "iOS"
37
+ : /Windows/.test(userAgent)
38
+ ? "Windows"
39
+ : /Mac/.test(userAgent)
40
+ ? "macOS"
41
+ : /Linux/.test(userAgent)
42
+ ? "Linux"
43
+ : "Other",
44
+ deviceType: /iPad|Tablet/.test(userAgent)
45
+ ? "tablet"
46
+ : /Mobi|Android|iPhone/.test(userAgent)
47
+ ? "mobile"
48
+ : "desktop",
49
+ };
50
+ }
51
+ export function context(allowed, browser) {
52
+ const base = {
53
+ sdk: { name: "@quacklytics/client", version: SDK_VERSION },
54
+ };
55
+ if (!browser)
56
+ return base;
57
+ return {
58
+ ...base,
59
+ page: {
60
+ url: sanitizeUrl(location.href, allowed),
61
+ path: location.pathname,
62
+ title: document.title,
63
+ referrer: sanitizeUrl(document.referrer),
64
+ },
65
+ ...describeUserAgent(navigator.userAgent),
66
+ language: navigator.language,
67
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
68
+ viewport: { width: innerWidth, height: innerHeight },
69
+ };
70
+ }
71
+ const observers = new Set();
72
+ let stopObserving;
73
+ export function observeNavigation(listener) {
74
+ observers.add(listener);
75
+ if (!stopObserving) {
76
+ const push = history.pushState, replace = history.replaceState;
77
+ const notify = () => {
78
+ for (const callback of observers)
79
+ callback();
80
+ };
81
+ const wrappedPush = function (...args) {
82
+ push.apply(this, args);
83
+ notify();
84
+ };
85
+ const wrappedReplace = function (...args) {
86
+ replace.apply(this, args);
87
+ notify();
88
+ };
89
+ history.pushState = wrappedPush;
90
+ history.replaceState = wrappedReplace;
91
+ window.addEventListener("popstate", notify);
92
+ document.addEventListener("astro:page-load", notify);
93
+ stopObserving = () => {
94
+ if (history.pushState === wrappedPush)
95
+ history.pushState = push;
96
+ if (history.replaceState === wrappedReplace)
97
+ history.replaceState = replace;
98
+ window.removeEventListener("popstate", notify);
99
+ document.removeEventListener("astro:page-load", notify);
100
+ stopObserving = undefined;
101
+ };
102
+ }
103
+ return () => {
104
+ observers.delete(listener);
105
+ if (!observers.size)
106
+ stopObserving?.();
107
+ };
108
+ }
109
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { type SdkEvent } from "./protocol.js";
2
+ export type { EventProperties, JsonValue } from "./protocol.js";
3
+ import type { EventProperties } from "./protocol.js";
4
+ export { enableClickAutotracking, trackPageview, DEFAULT_CLICK_SELECTORS, } from "./autotrack.js";
5
+ export type { ClickAutotrackOptions, ClickAutotrackHandle, TrackPageviewOptions, } from "./autotrack.js";
6
+ export type { AnalyticsEvent, SendOptions } from "./legacy.js";
7
+ import type { AnalyticsEvent, SendOptions } from "./legacy.js";
8
+ export type ConsentState = "pending" | "granted" | "denied";
9
+ export type CaptureResult = {
10
+ status: "queued" | "buffered";
11
+ id: string;
12
+ } | {
13
+ status: "disabled" | "dropped";
14
+ };
15
+ export interface AnalyticsClientConfig {
16
+ apiKey: string;
17
+ hostname: string;
18
+ port?: number;
19
+ protocol?: "http" | "https";
20
+ fetch?: typeof fetch;
21
+ runtime?: "browser" | "server";
22
+ consent?: "required" | "granted";
23
+ autoTrackPageviews?: boolean;
24
+ autocapture?: boolean;
25
+ allowedQueryParameters?: readonly string[];
26
+ sessionTimeoutMs?: number;
27
+ flushIntervalMs?: number;
28
+ batchSize?: number;
29
+ maxQueueSize?: number;
30
+ maxQueueBytes?: number;
31
+ maxEventBytes?: number;
32
+ maxQueueAgeMs?: number;
33
+ maxAttempts?: number;
34
+ retryDelayMs?: number;
35
+ requestTimeoutMs?: number;
36
+ beforeSend?: (event: SdkEvent) => SdkEvent | null;
37
+ onError?: (error: unknown) => void;
38
+ }
39
+ export interface CaptureOptions {
40
+ userId?: string;
41
+ sessionId?: string;
42
+ timestamp?: Date | string | number;
43
+ id?: string;
44
+ }
45
+ export interface AnalyticsClient<Events extends {
46
+ [Name in keyof Events]: EventProperties;
47
+ } = Record<string, EventProperties>> {
48
+ track<Name extends Extract<keyof Events, string>>(name: Name, ...args: EventProperties extends Events[Name] ? [properties?: Events[Name], options?: CaptureOptions] : [properties: Events[Name], options?: CaptureOptions]): Promise<CaptureResult>;
49
+ page(properties?: EventProperties): Promise<CaptureResult>;
50
+ identify(userId: string, traits?: EventProperties): Promise<CaptureResult>;
51
+ reset(): void;
52
+ setConsent(state: "granted" | "denied"): void;
53
+ getConsent(): ConsentState;
54
+ flush(): Promise<void>;
55
+ destroy(): Promise<void>;
56
+ send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;
57
+ sendBatch(events: readonly AnalyticsEvent[], options?: SendOptions): Promise<void>;
58
+ trackPageview(options?: {
59
+ properties?: EventProperties;
60
+ }): Promise<void>;
61
+ }
62
+ export declare function createClient<Events extends {
63
+ [Name in keyof Events]: EventProperties;
64
+ } = Record<string, EventProperties>>(config: AnalyticsClientConfig): AnalyticsClient<Events>;
65
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}