@pingaura/telemetry 0.1.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 +201 -0
- package/README.md +124 -0
- package/dist/cloudflare.cjs +60 -0
- package/dist/cloudflare.cjs.map +1 -0
- package/dist/cloudflare.d.cts +37 -0
- package/dist/cloudflare.d.mts +37 -0
- package/dist/cloudflare.mjs +57 -0
- package/dist/cloudflare.mjs.map +1 -0
- package/dist/core-Cvg8zn6Q.mjs +139 -0
- package/dist/core-Cvg8zn6Q.mjs.map +1 -0
- package/dist/core-DVilpn_i.d.cts +93 -0
- package/dist/core-DVilpn_i.d.mts +93 -0
- package/dist/core-KgLaVgR4.cjs +162 -0
- package/dist/core-KgLaVgR4.cjs.map +1 -0
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/matchers-CMwG37qc.cjs +18 -0
- package/dist/matchers-CMwG37qc.cjs.map +1 -0
- package/dist/matchers-CUD5NL5K.mjs +13 -0
- package/dist/matchers-CUD5NL5K.mjs.map +1 -0
- package/dist/next.cjs +39 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +39 -0
- package/dist/next.d.mts +39 -0
- package/dist/next.mjs +36 -0
- package/dist/next.mjs.map +1 -0
- package/dist/node.cjs +51 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +25 -0
- package/dist/node.d.mts +25 -0
- package/dist/node.mjs +49 -0
- package/dist/node.mjs.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-Cvg8zn6Q.mjs","names":[],"sources":["../src/contract.ts","../src/core.ts"],"sourcesContent":["// PingAura analytics event envelope. Zero runtime dependencies by design.\n\nexport const ANALYTICS_SCHEMA_VERSION = 1 as const;\n\nexport const EVENT_TYPES = [\n 'page_view',\n 'page_leave',\n 'web_vitals',\n 'track',\n] as const;\nexport type EventType = (typeof EVENT_TYPES)[number];\n\nexport interface EventContext {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n screen?: string;\n user_agent?: string;\n country?: string;\n}\n\nexport interface AnalyticsEvent {\n event_id: string;\n schema_version: typeof ANALYTICS_SCHEMA_VERSION;\n type: EventType;\n view_id?: string;\n timestamp: string;\n sent_at: string;\n context: EventContext;\n properties: Record<string, unknown>;\n library?: { name: string; version: string };\n}\n\nexport interface BuildEventInput {\n type: EventType;\n context: EventContext;\n /**\n * Event metadata, archived verbatim. Never put PII here (emails, names, user\n * IDs, raw query strings) — the collector rejects events whose values look\n * like PII. Use opaque or aggregate values only.\n */\n properties?: Record<string, unknown>;\n timestamp?: string;\n view_id?: string;\n library?: { name: string; version: string };\n}\n\n/** Build a contract-valid event envelope. `now` is injectable for tests. */\nexport function buildEvent(\n input: BuildEventInput,\n now: () => string = () => new Date().toISOString(),\n): AnalyticsEvent {\n const ts = input.timestamp ?? now();\n return {\n event_id: globalThis.crypto.randomUUID(),\n schema_version: ANALYTICS_SCHEMA_VERSION,\n type: input.type,\n view_id: input.view_id,\n timestamp: ts,\n sent_at: now(),\n context: input.context,\n properties: input.properties ?? {},\n library: input.library,\n };\n}\n","import {\n type AnalyticsEvent,\n type BuildEventInput,\n buildEvent,\n} from './contract';\n\nexport interface ClientConfig {\n /** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */\n writeKey: string | undefined;\n /** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */\n endpoint?: string;\n /** Registered site domain (e.g. \"example.com\"), required for attribution. Missing/empty disables tracking. */\n domain: string | undefined;\n timeoutMs?: number;\n debug?: boolean;\n /** Injectable for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Injectable warning sink; defaults to console.warn. */\n onWarn?: (message: string) => void;\n /** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */\n keepalive?: boolean;\n}\n\nexport interface SendOptions {\n /** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */\n ip?: string;\n}\n\nexport interface PageViewInput {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n userAgent?: string;\n ip?: string;\n properties?: Record<string, unknown>;\n}\n\nexport interface AnalyticsClient {\n send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;\n sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;\n pageView(input: PageViewInput): Promise<void>;\n /** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */\n track(\n name: string,\n properties: Record<string, unknown> | undefined,\n context: { url: string; path?: string; referrer?: string },\n ): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst DEFAULT_ENDPOINT = 'https://telemetry.pingaura.ai/v1/events';\n\n// Reads a 2xx body (releasing the keepalive socket) and returns the collector's\n// rejected count. Cancels the stream on a non-JSON body so the socket is freed.\nasync function drainAndCountRejected(res: Response): Promise<number> {\n try {\n if (typeof res.json === 'function') {\n const body = (await res.json()) as { rejected?: unknown };\n const rejected = body?.rejected;\n if (Array.isArray(rejected)) return rejected.length;\n if (typeof rejected === 'number') return rejected;\n return 0;\n }\n } catch {\n // not JSON / already consumed — fall through to cancel\n }\n await res.body?.cancel?.().catch(() => {});\n return 0;\n}\n\nexport function createClient(config: ClientConfig): AnalyticsClient {\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const warn = config.onWarn ?? ((m: string) => console.warn(m));\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n // 4xx rejections never self-heal — warn once per status instead of per request.\n const warnedStatuses = new Set<number>();\n\n if (!config.writeKey)\n warn('[pingaura] writeKey missing — analytics disabled');\n if (!config.domain) warn('[pingaura] domain missing — analytics disabled');\n\n async function send(\n event: AnalyticsEvent,\n options: SendOptions = {},\n ): Promise<void> {\n if (!config.writeKey || !config.domain) return; // no-op (already warned at init)\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n authorization: `Bearer ${config.writeKey}`,\n };\n if (options.ip) headers['x-pa-client-ip'] = options.ip;\n\n let body: string;\n try {\n body = JSON.stringify({ domain: config.domain, events: [event] });\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to serialize event: ${String(err)}`);\n return;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n // best-effort; ignored on older Node and rejected by some edge runtimes for large bodies\n keepalive: config.keepalive ?? true,\n });\n\n if (res.status < 200 || res.status >= 300) {\n let detail = '';\n try {\n detail = ((await res.text?.()) ?? '').slice(0, 200);\n } catch {\n // body unreadable — the status code alone is enough\n }\n const message = `[pingaura] ingest rejected (${res.status})${detail ? `: ${detail}` : ''}`;\n if (res.status === 429 || res.status >= 500) {\n // transient (rate-limit / server) — debug-only, same as network failures\n if (config.debug) warn(message);\n } else if (!warnedStatuses.has(res.status)) {\n // deterministic client error (bad key/domain/payload) — warn once\n warnedStatuses.add(res.status);\n warn(message);\n }\n return;\n }\n\n // 2xx: drain the body to release the keepalive socket and surface partial drops\n const rejected = await drainAndCountRejected(res);\n if (rejected > 0)\n warn(`[pingaura] ${rejected} event(s) rejected by collector`);\n } catch (err) {\n // network / abort / timeout — transient; quiet outside debug\n if (config.debug) warn(`[pingaura] send failed: ${String(err)}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Build inside the guard so a synchronous throw (crypto.randomUUID) never\n // escapes into the caller's request path — every adapter is fire-and-forget.\n function safeSend(\n input: BuildEventInput,\n options?: SendOptions,\n ): Promise<void> {\n let event: AnalyticsEvent;\n try {\n event = buildEvent(input);\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to build event: ${String(err)}`);\n return Promise.resolve();\n }\n return send(event, options);\n }\n\n return {\n send,\n sendRaw: (input, options) => safeSend(input, options),\n pageView: (input) =>\n safeSend(\n {\n type: 'page_view',\n context: {\n url: input.url,\n path: input.path,\n referrer: input.referrer,\n title: input.title,\n locale: input.locale,\n user_agent: input.userAgent,\n },\n properties: input.properties,\n },\n { ip: input.ip },\n ),\n track: (name, properties = {}, context) =>\n safeSend({\n type: 'track',\n context: {\n url: context.url,\n path: context.path,\n referrer: context.referrer,\n },\n properties: { ...properties, name },\n }),\n };\n}\n"],"mappings":";AAEA,MAAa,2BAA2B;AAExC,MAAa,cAAc;CACzB;CACA;CACA;CACA;AACF;;AAyCA,SAAgB,WACd,OACA,6BAA0B,IAAI,KAAK,EAAA,CAAE,YAAY,GACjC;CAChB,MAAM,KAAK,MAAM,aAAa,IAAI;CAClC,OAAO;EACL,UAAU,WAAW,OAAO,WAAW;EACvC,gBAAA;EACA,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,WAAW;EACX,SAAS,IAAI;EACb,SAAS,MAAM;EACf,YAAY,MAAM,cAAc,CAAC;EACjC,SAAS,MAAM;CACjB;AACF;;;ACfA,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAIzB,eAAe,sBAAsB,KAAgC;CACnE,IAAI;EACF,IAAI,OAAO,IAAI,SAAS,YAAY;GAElC,MAAM,YAAW,MADG,IAAI,KAAK,EAAA,EACN;GACvB,IAAI,MAAM,QAAQ,QAAQ,GAAG,OAAO,SAAS;GAC7C,IAAI,OAAO,aAAa,UAAU,OAAO;GACzC,OAAO;EACT;CACF,QAAQ,CAER;CACA,MAAM,IAAI,MAAM,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC;CACzC,OAAO;AACT;AAEA,SAAgB,aAAa,QAAuC;CAClE,MAAM,YAAY,OAAO,aAAa,WAAW;CACjD,MAAM,OAAO,OAAO,YAAY,MAAc,QAAQ,KAAK,CAAC;CAC5D,MAAM,YAAY,OAAO,aAAa;CACtC,MAAM,WAAW,OAAO,YAAY;CAEpC,MAAM,iCAAiB,IAAI,IAAY;CAEvC,IAAI,CAAC,OAAO,UACV,KAAK,kDAAkD;CACzD,IAAI,CAAC,OAAO,QAAQ,KAAK,gDAAgD;CAEzE,eAAe,KACb,OACA,UAAuB,CAAC,GACT;EACf,IAAI,CAAC,OAAO,YAAY,CAAC,OAAO,QAAQ;EAExC,MAAM,UAAkC;GACtC,gBAAgB;GAChB,eAAe,UAAU,OAAO;EAClC;EACA,IAAI,QAAQ,IAAI,QAAQ,oBAAoB,QAAQ;EAEpD,IAAI;EACJ,IAAI;GACF,OAAO,KAAK,UAAU;IAAE,QAAQ,OAAO;IAAQ,QAAQ,CAAC,KAAK;GAAE,CAAC;EAClE,SAAS,KAAK;GACZ,IAAI,OAAO,OACT,KAAK,yCAAyC,OAAO,GAAG,GAAG;GAC7D;EACF;EAEA,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,SAAS;EAC5D,IAAI;GACF,MAAM,MAAM,MAAM,UAAU,UAAU;IACpC,QAAQ;IACR;IACA;IACA,QAAQ,WAAW;IAEnB,WAAW,OAAO,aAAa;GACjC,CAAC;GAED,IAAI,IAAI,SAAS,OAAO,IAAI,UAAU,KAAK;IACzC,IAAI,SAAS;IACb,IAAI;KACF,UAAW,MAAM,IAAI,OAAO,KAAM,GAAA,CAAI,MAAM,GAAG,GAAG;IACpD,QAAQ,CAER;IACA,MAAM,UAAU,+BAA+B,IAAI,OAAO,GAAG,SAAS,KAAK,WAAW;IACtF,IAAI,IAAI,WAAW,OAAO,IAAI,UAAU;SAElC,OAAO,OAAO,KAAK,OAAO;IAAA,OACzB,IAAI,CAAC,eAAe,IAAI,IAAI,MAAM,GAAG;KAE1C,eAAe,IAAI,IAAI,MAAM;KAC7B,KAAK,OAAO;IACd;IACA;GACF;GAGA,MAAM,WAAW,MAAM,sBAAsB,GAAG;GAChD,IAAI,WAAW,GACb,KAAK,cAAc,SAAS,gCAAgC;EAChE,SAAS,KAAK;GAEZ,IAAI,OAAO,OAAO,KAAK,2BAA2B,OAAO,GAAG,GAAG;EACjE,UAAU;GACR,aAAa,KAAK;EACpB;CACF;CAIA,SAAS,SACP,OACA,SACe;EACf,IAAI;EACJ,IAAI;GACF,QAAQ,WAAW,KAAK;EAC1B,SAAS,KAAK;GACZ,IAAI,OAAO,OACT,KAAK,qCAAqC,OAAO,GAAG,GAAG;GACzD,OAAO,QAAQ,QAAQ;EACzB;EACA,OAAO,KAAK,OAAO,OAAO;CAC5B;CAEA,OAAO;EACL;EACA,UAAU,OAAO,YAAY,SAAS,OAAO,OAAO;EACpD,WAAW,UACT,SACE;GACE,MAAM;GACN,SAAS;IACP,KAAK,MAAM;IACX,MAAM,MAAM;IACZ,UAAU,MAAM;IAChB,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,YAAY,MAAM;GACpB;GACA,YAAY,MAAM;EACpB,GACA,EAAE,IAAI,MAAM,GAAG,CACjB;EACF,QAAQ,MAAM,aAAa,CAAC,GAAG,YAC7B,SAAS;GACP,MAAM;GACN,SAAS;IACP,KAAK,QAAQ;IACb,MAAM,QAAQ;IACd,UAAU,QAAQ;GACpB;GACA,YAAY;IAAE,GAAG;IAAY;GAAK;EACpC,CAAC;CACL;AACF"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//#region src/contract.d.ts
|
|
2
|
+
declare const ANALYTICS_SCHEMA_VERSION: 1;
|
|
3
|
+
declare const EVENT_TYPES: readonly ["page_view", "page_leave", "web_vitals", "track"];
|
|
4
|
+
type EventType = (typeof EVENT_TYPES)[number];
|
|
5
|
+
interface EventContext {
|
|
6
|
+
url: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
referrer?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
locale?: string;
|
|
11
|
+
screen?: string;
|
|
12
|
+
user_agent?: string;
|
|
13
|
+
country?: string;
|
|
14
|
+
}
|
|
15
|
+
interface AnalyticsEvent {
|
|
16
|
+
event_id: string;
|
|
17
|
+
schema_version: typeof ANALYTICS_SCHEMA_VERSION;
|
|
18
|
+
type: EventType;
|
|
19
|
+
view_id?: string;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
sent_at: string;
|
|
22
|
+
context: EventContext;
|
|
23
|
+
properties: Record<string, unknown>;
|
|
24
|
+
library?: {
|
|
25
|
+
name: string;
|
|
26
|
+
version: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
interface BuildEventInput {
|
|
30
|
+
type: EventType;
|
|
31
|
+
context: EventContext;
|
|
32
|
+
/**
|
|
33
|
+
* Event metadata, archived verbatim. Never put PII here (emails, names, user
|
|
34
|
+
* IDs, raw query strings) — the collector rejects events whose values look
|
|
35
|
+
* like PII. Use opaque or aggregate values only.
|
|
36
|
+
*/
|
|
37
|
+
properties?: Record<string, unknown>;
|
|
38
|
+
timestamp?: string;
|
|
39
|
+
view_id?: string;
|
|
40
|
+
library?: {
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Build a contract-valid event envelope. `now` is injectable for tests. */
|
|
46
|
+
declare function buildEvent(input: BuildEventInput, now?: () => string): AnalyticsEvent;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/core.d.ts
|
|
49
|
+
interface ClientConfig {
|
|
50
|
+
/** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */
|
|
51
|
+
writeKey: string | undefined;
|
|
52
|
+
/** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */
|
|
53
|
+
endpoint?: string;
|
|
54
|
+
/** Registered site domain (e.g. "example.com"), required for attribution. Missing/empty disables tracking. */
|
|
55
|
+
domain: string | undefined;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
debug?: boolean;
|
|
58
|
+
/** Injectable for tests; defaults to global fetch. */
|
|
59
|
+
fetchImpl?: typeof fetch;
|
|
60
|
+
/** Injectable warning sink; defaults to console.warn. */
|
|
61
|
+
onWarn?: (message: string) => void;
|
|
62
|
+
/** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */
|
|
63
|
+
keepalive?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface SendOptions {
|
|
66
|
+
/** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */
|
|
67
|
+
ip?: string;
|
|
68
|
+
}
|
|
69
|
+
interface PageViewInput {
|
|
70
|
+
url: string;
|
|
71
|
+
path?: string;
|
|
72
|
+
referrer?: string;
|
|
73
|
+
title?: string;
|
|
74
|
+
locale?: string;
|
|
75
|
+
userAgent?: string;
|
|
76
|
+
ip?: string;
|
|
77
|
+
properties?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
interface AnalyticsClient {
|
|
80
|
+
send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;
|
|
81
|
+
sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;
|
|
82
|
+
pageView(input: PageViewInput): Promise<void>;
|
|
83
|
+
/** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */
|
|
84
|
+
track(name: string, properties: Record<string, unknown> | undefined, context: {
|
|
85
|
+
url: string;
|
|
86
|
+
path?: string;
|
|
87
|
+
referrer?: string;
|
|
88
|
+
}): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
declare function createClient(config: ClientConfig): AnalyticsClient;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { createClient as a, BuildEventInput as c, EventType as d, buildEvent as f, SendOptions as i, EVENT_TYPES as l, ClientConfig as n, ANALYTICS_SCHEMA_VERSION as o, PageViewInput as r, AnalyticsEvent as s, AnalyticsClient as t, EventContext as u };
|
|
93
|
+
//# sourceMappingURL=core-DVilpn_i.d.cts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//#region src/contract.d.ts
|
|
2
|
+
declare const ANALYTICS_SCHEMA_VERSION: 1;
|
|
3
|
+
declare const EVENT_TYPES: readonly ["page_view", "page_leave", "web_vitals", "track"];
|
|
4
|
+
type EventType = (typeof EVENT_TYPES)[number];
|
|
5
|
+
interface EventContext {
|
|
6
|
+
url: string;
|
|
7
|
+
path?: string;
|
|
8
|
+
referrer?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
locale?: string;
|
|
11
|
+
screen?: string;
|
|
12
|
+
user_agent?: string;
|
|
13
|
+
country?: string;
|
|
14
|
+
}
|
|
15
|
+
interface AnalyticsEvent {
|
|
16
|
+
event_id: string;
|
|
17
|
+
schema_version: typeof ANALYTICS_SCHEMA_VERSION;
|
|
18
|
+
type: EventType;
|
|
19
|
+
view_id?: string;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
sent_at: string;
|
|
22
|
+
context: EventContext;
|
|
23
|
+
properties: Record<string, unknown>;
|
|
24
|
+
library?: {
|
|
25
|
+
name: string;
|
|
26
|
+
version: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
interface BuildEventInput {
|
|
30
|
+
type: EventType;
|
|
31
|
+
context: EventContext;
|
|
32
|
+
/**
|
|
33
|
+
* Event metadata, archived verbatim. Never put PII here (emails, names, user
|
|
34
|
+
* IDs, raw query strings) — the collector rejects events whose values look
|
|
35
|
+
* like PII. Use opaque or aggregate values only.
|
|
36
|
+
*/
|
|
37
|
+
properties?: Record<string, unknown>;
|
|
38
|
+
timestamp?: string;
|
|
39
|
+
view_id?: string;
|
|
40
|
+
library?: {
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Build a contract-valid event envelope. `now` is injectable for tests. */
|
|
46
|
+
declare function buildEvent(input: BuildEventInput, now?: () => string): AnalyticsEvent;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/core.d.ts
|
|
49
|
+
interface ClientConfig {
|
|
50
|
+
/** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */
|
|
51
|
+
writeKey: string | undefined;
|
|
52
|
+
/** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */
|
|
53
|
+
endpoint?: string;
|
|
54
|
+
/** Registered site domain (e.g. "example.com"), required for attribution. Missing/empty disables tracking. */
|
|
55
|
+
domain: string | undefined;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
debug?: boolean;
|
|
58
|
+
/** Injectable for tests; defaults to global fetch. */
|
|
59
|
+
fetchImpl?: typeof fetch;
|
|
60
|
+
/** Injectable warning sink; defaults to console.warn. */
|
|
61
|
+
onWarn?: (message: string) => void;
|
|
62
|
+
/** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */
|
|
63
|
+
keepalive?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface SendOptions {
|
|
66
|
+
/** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */
|
|
67
|
+
ip?: string;
|
|
68
|
+
}
|
|
69
|
+
interface PageViewInput {
|
|
70
|
+
url: string;
|
|
71
|
+
path?: string;
|
|
72
|
+
referrer?: string;
|
|
73
|
+
title?: string;
|
|
74
|
+
locale?: string;
|
|
75
|
+
userAgent?: string;
|
|
76
|
+
ip?: string;
|
|
77
|
+
properties?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
interface AnalyticsClient {
|
|
80
|
+
send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;
|
|
81
|
+
sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;
|
|
82
|
+
pageView(input: PageViewInput): Promise<void>;
|
|
83
|
+
/** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */
|
|
84
|
+
track(name: string, properties: Record<string, unknown> | undefined, context: {
|
|
85
|
+
url: string;
|
|
86
|
+
path?: string;
|
|
87
|
+
referrer?: string;
|
|
88
|
+
}): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
declare function createClient(config: ClientConfig): AnalyticsClient;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { createClient as a, BuildEventInput as c, EventType as d, buildEvent as f, SendOptions as i, EVENT_TYPES as l, ClientConfig as n, ANALYTICS_SCHEMA_VERSION as o, PageViewInput as r, AnalyticsEvent as s, AnalyticsClient as t, EventContext as u };
|
|
93
|
+
//# sourceMappingURL=core-DVilpn_i.d.mts.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//#region src/contract.ts
|
|
2
|
+
const ANALYTICS_SCHEMA_VERSION = 1;
|
|
3
|
+
const EVENT_TYPES = [
|
|
4
|
+
"page_view",
|
|
5
|
+
"page_leave",
|
|
6
|
+
"web_vitals",
|
|
7
|
+
"track"
|
|
8
|
+
];
|
|
9
|
+
/** Build a contract-valid event envelope. `now` is injectable for tests. */
|
|
10
|
+
function buildEvent(input, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
11
|
+
const ts = input.timestamp ?? now();
|
|
12
|
+
return {
|
|
13
|
+
event_id: globalThis.crypto.randomUUID(),
|
|
14
|
+
schema_version: 1,
|
|
15
|
+
type: input.type,
|
|
16
|
+
view_id: input.view_id,
|
|
17
|
+
timestamp: ts,
|
|
18
|
+
sent_at: now(),
|
|
19
|
+
context: input.context,
|
|
20
|
+
properties: input.properties ?? {},
|
|
21
|
+
library: input.library
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/core.ts
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 2e3;
|
|
27
|
+
const DEFAULT_ENDPOINT = "https://telemetry.pingaura.ai/v1/events";
|
|
28
|
+
async function drainAndCountRejected(res) {
|
|
29
|
+
try {
|
|
30
|
+
if (typeof res.json === "function") {
|
|
31
|
+
const rejected = (await res.json())?.rejected;
|
|
32
|
+
if (Array.isArray(rejected)) return rejected.length;
|
|
33
|
+
if (typeof rejected === "number") return rejected;
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
await res.body?.cancel?.().catch(() => {});
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
function createClient(config) {
|
|
41
|
+
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
42
|
+
const warn = config.onWarn ?? ((m) => console.warn(m));
|
|
43
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
44
|
+
const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
45
|
+
const warnedStatuses = /* @__PURE__ */ new Set();
|
|
46
|
+
if (!config.writeKey) warn("[pingaura] writeKey missing — analytics disabled");
|
|
47
|
+
if (!config.domain) warn("[pingaura] domain missing — analytics disabled");
|
|
48
|
+
async function send(event, options = {}) {
|
|
49
|
+
if (!config.writeKey || !config.domain) return;
|
|
50
|
+
const headers = {
|
|
51
|
+
"content-type": "application/json",
|
|
52
|
+
authorization: `Bearer ${config.writeKey}`
|
|
53
|
+
};
|
|
54
|
+
if (options.ip) headers["x-pa-client-ip"] = options.ip;
|
|
55
|
+
let body;
|
|
56
|
+
try {
|
|
57
|
+
body = JSON.stringify({
|
|
58
|
+
domain: config.domain,
|
|
59
|
+
events: [event]
|
|
60
|
+
});
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (config.debug) warn(`[pingaura] failed to serialize event: ${String(err)}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetchImpl(endpoint, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers,
|
|
71
|
+
body,
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
keepalive: config.keepalive ?? true
|
|
74
|
+
});
|
|
75
|
+
if (res.status < 200 || res.status >= 300) {
|
|
76
|
+
let detail = "";
|
|
77
|
+
try {
|
|
78
|
+
detail = (await res.text?.() ?? "").slice(0, 200);
|
|
79
|
+
} catch {}
|
|
80
|
+
const message = `[pingaura] ingest rejected (${res.status})${detail ? `: ${detail}` : ""}`;
|
|
81
|
+
if (res.status === 429 || res.status >= 500) {
|
|
82
|
+
if (config.debug) warn(message);
|
|
83
|
+
} else if (!warnedStatuses.has(res.status)) {
|
|
84
|
+
warnedStatuses.add(res.status);
|
|
85
|
+
warn(message);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const rejected = await drainAndCountRejected(res);
|
|
90
|
+
if (rejected > 0) warn(`[pingaura] ${rejected} event(s) rejected by collector`);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (config.debug) warn(`[pingaura] send failed: ${String(err)}`);
|
|
93
|
+
} finally {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function safeSend(input, options) {
|
|
98
|
+
let event;
|
|
99
|
+
try {
|
|
100
|
+
event = buildEvent(input);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (config.debug) warn(`[pingaura] failed to build event: ${String(err)}`);
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
}
|
|
105
|
+
return send(event, options);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
send,
|
|
109
|
+
sendRaw: (input, options) => safeSend(input, options),
|
|
110
|
+
pageView: (input) => safeSend({
|
|
111
|
+
type: "page_view",
|
|
112
|
+
context: {
|
|
113
|
+
url: input.url,
|
|
114
|
+
path: input.path,
|
|
115
|
+
referrer: input.referrer,
|
|
116
|
+
title: input.title,
|
|
117
|
+
locale: input.locale,
|
|
118
|
+
user_agent: input.userAgent
|
|
119
|
+
},
|
|
120
|
+
properties: input.properties
|
|
121
|
+
}, { ip: input.ip }),
|
|
122
|
+
track: (name, properties = {}, context) => safeSend({
|
|
123
|
+
type: "track",
|
|
124
|
+
context: {
|
|
125
|
+
url: context.url,
|
|
126
|
+
path: context.path,
|
|
127
|
+
referrer: context.referrer
|
|
128
|
+
},
|
|
129
|
+
properties: {
|
|
130
|
+
...properties,
|
|
131
|
+
name
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
Object.defineProperty(exports, "ANALYTICS_SCHEMA_VERSION", {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
get: function() {
|
|
140
|
+
return ANALYTICS_SCHEMA_VERSION;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
Object.defineProperty(exports, "EVENT_TYPES", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
get: function() {
|
|
146
|
+
return EVENT_TYPES;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
Object.defineProperty(exports, "buildEvent", {
|
|
150
|
+
enumerable: true,
|
|
151
|
+
get: function() {
|
|
152
|
+
return buildEvent;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
Object.defineProperty(exports, "createClient", {
|
|
156
|
+
enumerable: true,
|
|
157
|
+
get: function() {
|
|
158
|
+
return createClient;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=core-KgLaVgR4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-KgLaVgR4.cjs","names":[],"sources":["../src/contract.ts","../src/core.ts"],"sourcesContent":["// PingAura analytics event envelope. Zero runtime dependencies by design.\n\nexport const ANALYTICS_SCHEMA_VERSION = 1 as const;\n\nexport const EVENT_TYPES = [\n 'page_view',\n 'page_leave',\n 'web_vitals',\n 'track',\n] as const;\nexport type EventType = (typeof EVENT_TYPES)[number];\n\nexport interface EventContext {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n screen?: string;\n user_agent?: string;\n country?: string;\n}\n\nexport interface AnalyticsEvent {\n event_id: string;\n schema_version: typeof ANALYTICS_SCHEMA_VERSION;\n type: EventType;\n view_id?: string;\n timestamp: string;\n sent_at: string;\n context: EventContext;\n properties: Record<string, unknown>;\n library?: { name: string; version: string };\n}\n\nexport interface BuildEventInput {\n type: EventType;\n context: EventContext;\n /**\n * Event metadata, archived verbatim. Never put PII here (emails, names, user\n * IDs, raw query strings) — the collector rejects events whose values look\n * like PII. Use opaque or aggregate values only.\n */\n properties?: Record<string, unknown>;\n timestamp?: string;\n view_id?: string;\n library?: { name: string; version: string };\n}\n\n/** Build a contract-valid event envelope. `now` is injectable for tests. */\nexport function buildEvent(\n input: BuildEventInput,\n now: () => string = () => new Date().toISOString(),\n): AnalyticsEvent {\n const ts = input.timestamp ?? now();\n return {\n event_id: globalThis.crypto.randomUUID(),\n schema_version: ANALYTICS_SCHEMA_VERSION,\n type: input.type,\n view_id: input.view_id,\n timestamp: ts,\n sent_at: now(),\n context: input.context,\n properties: input.properties ?? {},\n library: input.library,\n };\n}\n","import {\n type AnalyticsEvent,\n type BuildEventInput,\n buildEvent,\n} from './contract';\n\nexport interface ClientConfig {\n /** Ingest key (e.g. `process.env.PINGAURA_INGEST_KEY`). Missing/empty disables tracking. */\n writeKey: string | undefined;\n /** Ingest endpoint. Defaults to the production collector; override for region/dev/testing. */\n endpoint?: string;\n /** Registered site domain (e.g. \"example.com\"), required for attribution. Missing/empty disables tracking. */\n domain: string | undefined;\n timeoutMs?: number;\n debug?: boolean;\n /** Injectable for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Injectable warning sink; defaults to console.warn. */\n onWarn?: (message: string) => void;\n /** Pass keepalive on the fetch (default true). Set false in edge runtimes (use ctx.waitUntil). */\n keepalive?: boolean;\n}\n\nexport interface SendOptions {\n /** Visitor IP — sent as the X-PA-Client-IP header, never in the body. */\n ip?: string;\n}\n\nexport interface PageViewInput {\n url: string;\n path?: string;\n referrer?: string;\n title?: string;\n locale?: string;\n userAgent?: string;\n ip?: string;\n properties?: Record<string, unknown>;\n}\n\nexport interface AnalyticsClient {\n send(event: AnalyticsEvent, options?: SendOptions): Promise<void>;\n sendRaw(input: BuildEventInput, options?: SendOptions): Promise<void>;\n pageView(input: PageViewInput): Promise<void>;\n /** `properties` is archived verbatim — never pass PII; opaque/aggregate values only. */\n track(\n name: string,\n properties: Record<string, unknown> | undefined,\n context: { url: string; path?: string; referrer?: string },\n ): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst DEFAULT_ENDPOINT = 'https://telemetry.pingaura.ai/v1/events';\n\n// Reads a 2xx body (releasing the keepalive socket) and returns the collector's\n// rejected count. Cancels the stream on a non-JSON body so the socket is freed.\nasync function drainAndCountRejected(res: Response): Promise<number> {\n try {\n if (typeof res.json === 'function') {\n const body = (await res.json()) as { rejected?: unknown };\n const rejected = body?.rejected;\n if (Array.isArray(rejected)) return rejected.length;\n if (typeof rejected === 'number') return rejected;\n return 0;\n }\n } catch {\n // not JSON / already consumed — fall through to cancel\n }\n await res.body?.cancel?.().catch(() => {});\n return 0;\n}\n\nexport function createClient(config: ClientConfig): AnalyticsClient {\n const fetchImpl = config.fetchImpl ?? globalThis.fetch;\n const warn = config.onWarn ?? ((m: string) => console.warn(m));\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n // 4xx rejections never self-heal — warn once per status instead of per request.\n const warnedStatuses = new Set<number>();\n\n if (!config.writeKey)\n warn('[pingaura] writeKey missing — analytics disabled');\n if (!config.domain) warn('[pingaura] domain missing — analytics disabled');\n\n async function send(\n event: AnalyticsEvent,\n options: SendOptions = {},\n ): Promise<void> {\n if (!config.writeKey || !config.domain) return; // no-op (already warned at init)\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n authorization: `Bearer ${config.writeKey}`,\n };\n if (options.ip) headers['x-pa-client-ip'] = options.ip;\n\n let body: string;\n try {\n body = JSON.stringify({ domain: config.domain, events: [event] });\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to serialize event: ${String(err)}`);\n return;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n // best-effort; ignored on older Node and rejected by some edge runtimes for large bodies\n keepalive: config.keepalive ?? true,\n });\n\n if (res.status < 200 || res.status >= 300) {\n let detail = '';\n try {\n detail = ((await res.text?.()) ?? '').slice(0, 200);\n } catch {\n // body unreadable — the status code alone is enough\n }\n const message = `[pingaura] ingest rejected (${res.status})${detail ? `: ${detail}` : ''}`;\n if (res.status === 429 || res.status >= 500) {\n // transient (rate-limit / server) — debug-only, same as network failures\n if (config.debug) warn(message);\n } else if (!warnedStatuses.has(res.status)) {\n // deterministic client error (bad key/domain/payload) — warn once\n warnedStatuses.add(res.status);\n warn(message);\n }\n return;\n }\n\n // 2xx: drain the body to release the keepalive socket and surface partial drops\n const rejected = await drainAndCountRejected(res);\n if (rejected > 0)\n warn(`[pingaura] ${rejected} event(s) rejected by collector`);\n } catch (err) {\n // network / abort / timeout — transient; quiet outside debug\n if (config.debug) warn(`[pingaura] send failed: ${String(err)}`);\n } finally {\n clearTimeout(timer);\n }\n }\n\n // Build inside the guard so a synchronous throw (crypto.randomUUID) never\n // escapes into the caller's request path — every adapter is fire-and-forget.\n function safeSend(\n input: BuildEventInput,\n options?: SendOptions,\n ): Promise<void> {\n let event: AnalyticsEvent;\n try {\n event = buildEvent(input);\n } catch (err) {\n if (config.debug)\n warn(`[pingaura] failed to build event: ${String(err)}`);\n return Promise.resolve();\n }\n return send(event, options);\n }\n\n return {\n send,\n sendRaw: (input, options) => safeSend(input, options),\n pageView: (input) =>\n safeSend(\n {\n type: 'page_view',\n context: {\n url: input.url,\n path: input.path,\n referrer: input.referrer,\n title: input.title,\n locale: input.locale,\n user_agent: input.userAgent,\n },\n properties: input.properties,\n },\n { ip: input.ip },\n ),\n track: (name, properties = {}, context) =>\n safeSend({\n type: 'track',\n context: {\n url: context.url,\n path: context.path,\n referrer: context.referrer,\n },\n properties: { ...properties, name },\n }),\n };\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_core = require("./core-KgLaVgR4.cjs");
|
|
3
|
+
exports.ANALYTICS_SCHEMA_VERSION = require_core.ANALYTICS_SCHEMA_VERSION;
|
|
4
|
+
exports.EVENT_TYPES = require_core.EVENT_TYPES;
|
|
5
|
+
exports.buildEvent = require_core.buildEvent;
|
|
6
|
+
exports.createClient = require_core.createClient;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createClient, c as BuildEventInput, d as EventType, f as buildEvent, i as SendOptions, l as EVENT_TYPES, n as ClientConfig, o as ANALYTICS_SCHEMA_VERSION, r as PageViewInput, s as AnalyticsEvent, t as AnalyticsClient, u as EventContext } from "./core-DVilpn_i.cjs";
|
|
2
|
+
export { ANALYTICS_SCHEMA_VERSION, AnalyticsClient, AnalyticsEvent, BuildEventInput, ClientConfig, EVENT_TYPES, EventContext, EventType, PageViewInput, SendOptions, buildEvent, createClient };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createClient, c as BuildEventInput, d as EventType, f as buildEvent, i as SendOptions, l as EVENT_TYPES, n as ClientConfig, o as ANALYTICS_SCHEMA_VERSION, r as PageViewInput, s as AnalyticsEvent, t as AnalyticsClient, u as EventContext } from "./core-DVilpn_i.mjs";
|
|
2
|
+
export { ANALYTICS_SCHEMA_VERSION, AnalyticsClient, AnalyticsEvent, BuildEventInput, ClientConfig, EVENT_TYPES, EventContext, EventType, PageViewInput, SendOptions, buildEvent, createClient };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/matchers.ts
|
|
2
|
+
const ASSET_EXT = /\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;
|
|
3
|
+
/** Skip framework internals, API routes, and static assets. */
|
|
4
|
+
function shouldTrackPath(pathname) {
|
|
5
|
+
if (pathname.startsWith("/_next/")) return false;
|
|
6
|
+
if (pathname.startsWith("/api/")) return false;
|
|
7
|
+
if (ASSET_EXT.test(pathname)) return false;
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
Object.defineProperty(exports, "shouldTrackPath", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function() {
|
|
14
|
+
return shouldTrackPath;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=matchers-CMwG37qc.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchers-CMwG37qc.cjs","names":[],"sources":["../src/matchers.ts"],"sourcesContent":["// Default path matcher shared by the next/node/cloudflare adapters.\n\nconst ASSET_EXT =\n /\\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;\n\n/** Skip framework internals, API routes, and static assets. */\nexport function shouldTrackPath(pathname: string): boolean {\n if (pathname.startsWith('/_next/')) return false;\n if (pathname.startsWith('/api/')) return false;\n if (ASSET_EXT.test(pathname)) return false;\n return true;\n}\n"],"mappings":";AAEA,MAAM,YACJ;;AAGF,SAAgB,gBAAgB,UAA2B;CACzD,IAAI,SAAS,WAAW,SAAS,GAAG,OAAO;CAC3C,IAAI,SAAS,WAAW,OAAO,GAAG,OAAO;CACzC,IAAI,UAAU,KAAK,QAAQ,GAAG,OAAO;CACrC,OAAO;AACT"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/matchers.ts
|
|
2
|
+
const ASSET_EXT = /\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;
|
|
3
|
+
/** Skip framework internals, API routes, and static assets. */
|
|
4
|
+
function shouldTrackPath(pathname) {
|
|
5
|
+
if (pathname.startsWith("/_next/")) return false;
|
|
6
|
+
if (pathname.startsWith("/api/")) return false;
|
|
7
|
+
if (ASSET_EXT.test(pathname)) return false;
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { shouldTrackPath as t };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=matchers-CUD5NL5K.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchers-CUD5NL5K.mjs","names":[],"sources":["../src/matchers.ts"],"sourcesContent":["// Default path matcher shared by the next/node/cloudflare adapters.\n\nconst ASSET_EXT =\n /\\.(?:ico|png|jpe?g|gif|svg|webp|avif|css|js|map|txt|xml|json|woff2?|ttf|eot|mp4|webm)$/i;\n\n/** Skip framework internals, API routes, and static assets. */\nexport function shouldTrackPath(pathname: string): boolean {\n if (pathname.startsWith('/_next/')) return false;\n if (pathname.startsWith('/api/')) return false;\n if (ASSET_EXT.test(pathname)) return false;\n return true;\n}\n"],"mappings":";AAEA,MAAM,YACJ;;AAGF,SAAgB,gBAAgB,UAA2B;CACzD,IAAI,SAAS,WAAW,SAAS,GAAG,OAAO;CAC3C,IAAI,SAAS,WAAW,OAAO,GAAG,OAAO;CACzC,IAAI,UAAU,KAAK,QAAQ,GAAG,OAAO;CACrC,OAAO;AACT"}
|
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_core = require("./core-KgLaVgR4.cjs");
|
|
3
|
+
const require_matchers = require("./matchers-CMwG37qc.cjs");
|
|
4
|
+
//#region src/next.ts
|
|
5
|
+
function extractRequestData(req) {
|
|
6
|
+
const ip = (req.headers.get("x-forwarded-for") ?? "").split(",")[0]?.trim() || void 0;
|
|
7
|
+
return {
|
|
8
|
+
url: req.nextUrl.href || req.url,
|
|
9
|
+
path: req.nextUrl.pathname,
|
|
10
|
+
referrer: req.headers.get("referer") ?? void 0,
|
|
11
|
+
userAgent: req.headers.get("user-agent") ?? void 0,
|
|
12
|
+
ip
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns a fire-and-forget tracker. Call it from your Next middleware:
|
|
17
|
+
* const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
|
|
18
|
+
* export function middleware(req, event) { track(req, event); return NextResponse.next(); }
|
|
19
|
+
*/
|
|
20
|
+
function createAnalyticsMiddleware(config) {
|
|
21
|
+
const client = require_core.createClient({
|
|
22
|
+
...config,
|
|
23
|
+
keepalive: false
|
|
24
|
+
});
|
|
25
|
+
const matcher = config.shouldTrack ?? require_matchers.shouldTrackPath;
|
|
26
|
+
return (req, event) => {
|
|
27
|
+
try {
|
|
28
|
+
if (!matcher(req.nextUrl.pathname)) return;
|
|
29
|
+
const data = extractRequestData(req);
|
|
30
|
+
event.waitUntil(client.pageView(data));
|
|
31
|
+
} catch {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
exports.createAnalyticsMiddleware = createAnalyticsMiddleware;
|
|
36
|
+
exports.extractRequestData = extractRequestData;
|
|
37
|
+
exports.shouldTrackPath = require_matchers.shouldTrackPath;
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=next.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.cjs","names":["createClient","shouldTrackPath"],"sources":["../src/next.ts"],"sourcesContent":["import { type AnalyticsClient, type ClientConfig, createClient } from './core';\nimport { shouldTrackPath } from './matchers';\n\nexport { shouldTrackPath } from './matchers';\n\n// Minimal structural types — avoids a hard runtime dependency on `next`.\ninterface NextRequestLike {\n url: string;\n nextUrl: { pathname: string; href: string };\n headers: Headers;\n}\ninterface FetchEventLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface RequestData {\n url: string;\n path: string;\n referrer?: string;\n userAgent?: string;\n ip?: string;\n}\n\nexport function extractRequestData(req: NextRequestLike): RequestData {\n const xff = req.headers.get('x-forwarded-for') ?? '';\n const ip = xff.split(',')[0]?.trim() || undefined;\n return {\n url: req.nextUrl.href || req.url,\n path: req.nextUrl.pathname,\n referrer: req.headers.get('referer') ?? undefined,\n userAgent: req.headers.get('user-agent') ?? undefined,\n ip,\n };\n}\n\nexport interface MiddlewareConfig extends ClientConfig {\n /** Override the default path matcher. */\n shouldTrack?: (pathname: string) => boolean;\n}\n\n/**\n * Returns a fire-and-forget tracker. Call it from your Next middleware:\n * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });\n * export function middleware(req, event) { track(req, event); return NextResponse.next(); }\n */\nexport function createAnalyticsMiddleware(\n config: MiddlewareConfig,\n): (req: NextRequestLike, event: FetchEventLike) => void {\n // dispatch rides event.waitUntil — keepalive off, like the Cloudflare adapter\n const client: AnalyticsClient = createClient({ ...config, keepalive: false });\n const matcher = config.shouldTrack ?? shouldTrackPath;\n\n return (req, event) => {\n try {\n if (!matcher(req.nextUrl.pathname)) return;\n const data = extractRequestData(req);\n event.waitUntil(client.pageView(data));\n } catch {\n // never block the request\n }\n };\n}\n"],"mappings":";;;;AAuBA,SAAgB,mBAAmB,KAAmC;CAEpE,MAAM,MADM,IAAI,QAAQ,IAAI,iBAAiB,KAAK,GAAA,CACnC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK,KAAA;CACxC,OAAO;EACL,KAAK,IAAI,QAAQ,QAAQ,IAAI;EAC7B,MAAM,IAAI,QAAQ;EAClB,UAAU,IAAI,QAAQ,IAAI,SAAS,KAAK,KAAA;EACxC,WAAW,IAAI,QAAQ,IAAI,YAAY,KAAK,KAAA;EAC5C;CACF;AACF;;;;;;AAYA,SAAgB,0BACd,QACuD;CAEvD,MAAM,SAA0BA,aAAAA,aAAa;EAAE,GAAG;EAAQ,WAAW;CAAM,CAAC;CAC5E,MAAM,UAAU,OAAO,eAAeC,iBAAAA;CAEtC,QAAQ,KAAK,UAAU;EACrB,IAAI;GACF,IAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;GACpC,MAAM,OAAO,mBAAmB,GAAG;GACnC,MAAM,UAAU,OAAO,SAAS,IAAI,CAAC;EACvC,QAAQ,CAER;CACF;AACF"}
|
package/dist/next.d.cts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { n as ClientConfig } from "./core-DVilpn_i.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/matchers.d.ts
|
|
4
|
+
/** Skip framework internals, API routes, and static assets. */
|
|
5
|
+
declare function shouldTrackPath(pathname: string): boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/next.d.ts
|
|
8
|
+
interface NextRequestLike {
|
|
9
|
+
url: string;
|
|
10
|
+
nextUrl: {
|
|
11
|
+
pathname: string;
|
|
12
|
+
href: string;
|
|
13
|
+
};
|
|
14
|
+
headers: Headers;
|
|
15
|
+
}
|
|
16
|
+
interface FetchEventLike {
|
|
17
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
18
|
+
}
|
|
19
|
+
interface RequestData {
|
|
20
|
+
url: string;
|
|
21
|
+
path: string;
|
|
22
|
+
referrer?: string;
|
|
23
|
+
userAgent?: string;
|
|
24
|
+
ip?: string;
|
|
25
|
+
}
|
|
26
|
+
declare function extractRequestData(req: NextRequestLike): RequestData;
|
|
27
|
+
interface MiddlewareConfig extends ClientConfig {
|
|
28
|
+
/** Override the default path matcher. */
|
|
29
|
+
shouldTrack?: (pathname: string) => boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns a fire-and-forget tracker. Call it from your Next middleware:
|
|
33
|
+
* const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
|
|
34
|
+
* export function middleware(req, event) { track(req, event); return NextResponse.next(); }
|
|
35
|
+
*/
|
|
36
|
+
declare function createAnalyticsMiddleware(config: MiddlewareConfig): (req: NextRequestLike, event: FetchEventLike) => void;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { MiddlewareConfig, RequestData, createAnalyticsMiddleware, extractRequestData, shouldTrackPath };
|
|
39
|
+
//# sourceMappingURL=next.d.cts.map
|
package/dist/next.d.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { n as ClientConfig } from "./core-DVilpn_i.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/matchers.d.ts
|
|
4
|
+
/** Skip framework internals, API routes, and static assets. */
|
|
5
|
+
declare function shouldTrackPath(pathname: string): boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/next.d.ts
|
|
8
|
+
interface NextRequestLike {
|
|
9
|
+
url: string;
|
|
10
|
+
nextUrl: {
|
|
11
|
+
pathname: string;
|
|
12
|
+
href: string;
|
|
13
|
+
};
|
|
14
|
+
headers: Headers;
|
|
15
|
+
}
|
|
16
|
+
interface FetchEventLike {
|
|
17
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
18
|
+
}
|
|
19
|
+
interface RequestData {
|
|
20
|
+
url: string;
|
|
21
|
+
path: string;
|
|
22
|
+
referrer?: string;
|
|
23
|
+
userAgent?: string;
|
|
24
|
+
ip?: string;
|
|
25
|
+
}
|
|
26
|
+
declare function extractRequestData(req: NextRequestLike): RequestData;
|
|
27
|
+
interface MiddlewareConfig extends ClientConfig {
|
|
28
|
+
/** Override the default path matcher. */
|
|
29
|
+
shouldTrack?: (pathname: string) => boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns a fire-and-forget tracker. Call it from your Next middleware:
|
|
33
|
+
* const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
|
|
34
|
+
* export function middleware(req, event) { track(req, event); return NextResponse.next(); }
|
|
35
|
+
*/
|
|
36
|
+
declare function createAnalyticsMiddleware(config: MiddlewareConfig): (req: NextRequestLike, event: FetchEventLike) => void;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { MiddlewareConfig, RequestData, createAnalyticsMiddleware, extractRequestData, shouldTrackPath };
|
|
39
|
+
//# sourceMappingURL=next.d.mts.map
|
package/dist/next.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { t as createClient } from "./core-Cvg8zn6Q.mjs";
|
|
2
|
+
import { t as shouldTrackPath } from "./matchers-CUD5NL5K.mjs";
|
|
3
|
+
//#region src/next.ts
|
|
4
|
+
function extractRequestData(req) {
|
|
5
|
+
const ip = (req.headers.get("x-forwarded-for") ?? "").split(",")[0]?.trim() || void 0;
|
|
6
|
+
return {
|
|
7
|
+
url: req.nextUrl.href || req.url,
|
|
8
|
+
path: req.nextUrl.pathname,
|
|
9
|
+
referrer: req.headers.get("referer") ?? void 0,
|
|
10
|
+
userAgent: req.headers.get("user-agent") ?? void 0,
|
|
11
|
+
ip
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Returns a fire-and-forget tracker. Call it from your Next middleware:
|
|
16
|
+
* const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });
|
|
17
|
+
* export function middleware(req, event) { track(req, event); return NextResponse.next(); }
|
|
18
|
+
*/
|
|
19
|
+
function createAnalyticsMiddleware(config) {
|
|
20
|
+
const client = createClient({
|
|
21
|
+
...config,
|
|
22
|
+
keepalive: false
|
|
23
|
+
});
|
|
24
|
+
const matcher = config.shouldTrack ?? shouldTrackPath;
|
|
25
|
+
return (req, event) => {
|
|
26
|
+
try {
|
|
27
|
+
if (!matcher(req.nextUrl.pathname)) return;
|
|
28
|
+
const data = extractRequestData(req);
|
|
29
|
+
event.waitUntil(client.pageView(data));
|
|
30
|
+
} catch {}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { createAnalyticsMiddleware, extractRequestData, shouldTrackPath };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=next.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.mjs","names":[],"sources":["../src/next.ts"],"sourcesContent":["import { type AnalyticsClient, type ClientConfig, createClient } from './core';\nimport { shouldTrackPath } from './matchers';\n\nexport { shouldTrackPath } from './matchers';\n\n// Minimal structural types — avoids a hard runtime dependency on `next`.\ninterface NextRequestLike {\n url: string;\n nextUrl: { pathname: string; href: string };\n headers: Headers;\n}\ninterface FetchEventLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface RequestData {\n url: string;\n path: string;\n referrer?: string;\n userAgent?: string;\n ip?: string;\n}\n\nexport function extractRequestData(req: NextRequestLike): RequestData {\n const xff = req.headers.get('x-forwarded-for') ?? '';\n const ip = xff.split(',')[0]?.trim() || undefined;\n return {\n url: req.nextUrl.href || req.url,\n path: req.nextUrl.pathname,\n referrer: req.headers.get('referer') ?? undefined,\n userAgent: req.headers.get('user-agent') ?? undefined,\n ip,\n };\n}\n\nexport interface MiddlewareConfig extends ClientConfig {\n /** Override the default path matcher. */\n shouldTrack?: (pathname: string) => boolean;\n}\n\n/**\n * Returns a fire-and-forget tracker. Call it from your Next middleware:\n * const track = createAnalyticsMiddleware({ writeKey, domain: 'example.com' });\n * export function middleware(req, event) { track(req, event); return NextResponse.next(); }\n */\nexport function createAnalyticsMiddleware(\n config: MiddlewareConfig,\n): (req: NextRequestLike, event: FetchEventLike) => void {\n // dispatch rides event.waitUntil — keepalive off, like the Cloudflare adapter\n const client: AnalyticsClient = createClient({ ...config, keepalive: false });\n const matcher = config.shouldTrack ?? shouldTrackPath;\n\n return (req, event) => {\n try {\n if (!matcher(req.nextUrl.pathname)) return;\n const data = extractRequestData(req);\n event.waitUntil(client.pageView(data));\n } catch {\n // never block the request\n }\n };\n}\n"],"mappings":";;;AAuBA,SAAgB,mBAAmB,KAAmC;CAEpE,MAAM,MADM,IAAI,QAAQ,IAAI,iBAAiB,KAAK,GAAA,CACnC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK,KAAA;CACxC,OAAO;EACL,KAAK,IAAI,QAAQ,QAAQ,IAAI;EAC7B,MAAM,IAAI,QAAQ;EAClB,UAAU,IAAI,QAAQ,IAAI,SAAS,KAAK,KAAA;EACxC,WAAW,IAAI,QAAQ,IAAI,YAAY,KAAK,KAAA;EAC5C;CACF;AACF;;;;;;AAYA,SAAgB,0BACd,QACuD;CAEvD,MAAM,SAA0B,aAAa;EAAE,GAAG;EAAQ,WAAW;CAAM,CAAC;CAC5E,MAAM,UAAU,OAAO,eAAe;CAEtC,QAAQ,KAAK,UAAU;EACrB,IAAI;GACF,IAAI,CAAC,QAAQ,IAAI,QAAQ,QAAQ,GAAG;GACpC,MAAM,OAAO,mBAAmB,GAAG;GACnC,MAAM,UAAU,OAAO,SAAS,IAAI,CAAC;EACvC,QAAQ,CAER;CACF;AACF"}
|