@dumbledor/sdk 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.
@@ -0,0 +1,247 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/constants.ts
8
+ var DEFAULT_INGEST_URL = "https://ingest.dumbledor.com";
9
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
10
+
11
+ // src/config.ts
12
+ var DumbledorConfigError = class extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "DumbledorConfigError";
16
+ }
17
+ };
18
+ function normalizeIngestUrl(value) {
19
+ const raw = value?.trim() || DEFAULT_INGEST_URL;
20
+ return raw.replace(/\/$/, "");
21
+ }
22
+ function assertClientConfig(config) {
23
+ const websiteId = config.websiteId?.trim();
24
+ if (!websiteId) {
25
+ throw new DumbledorConfigError("websiteId is required.");
26
+ }
27
+ if (!UUID_PATTERN.test(websiteId)) {
28
+ throw new DumbledorConfigError("websiteId must be a valid UUID.");
29
+ }
30
+ return {
31
+ websiteId,
32
+ ingestUrl: normalizeIngestUrl(config.ingestUrl),
33
+ honorDoNotTrack: config.honorDoNotTrack ?? false,
34
+ disabled: config.disabled ?? false,
35
+ fetch: config.fetch
36
+ };
37
+ }
38
+ function withWebsiteId(payload, websiteId) {
39
+ return {
40
+ ...payload,
41
+ websiteId
42
+ };
43
+ }
44
+ function resolveFetch(fetchImpl) {
45
+ const resolved = fetchImpl ?? globalThis.fetch;
46
+ if (!resolved) {
47
+ throw new DumbledorConfigError("fetch is not available in this runtime.");
48
+ }
49
+ return resolved;
50
+ }
51
+
52
+ // src/dnt.ts
53
+ function hasDoNotTrack() {
54
+ if (typeof window === "undefined") {
55
+ return false;
56
+ }
57
+ const win = window;
58
+ const dnt = win.doNotTrack ?? win.navigator.doNotTrack ?? win.navigator.msDoNotTrack;
59
+ return dnt === 1 || dnt === "1" || dnt === "yes";
60
+ }
61
+ function isTrackingDisabled(honorDoNotTrack) {
62
+ return honorDoNotTrack && hasDoNotTrack();
63
+ }
64
+
65
+ // src/client.ts
66
+ function defaultPageviewInput() {
67
+ if (typeof window === "undefined" || typeof document === "undefined") {
68
+ return {};
69
+ }
70
+ return {
71
+ url: window.location.href,
72
+ referrer: document.referrer || void 0,
73
+ title: document.title,
74
+ hostname: window.location.hostname,
75
+ language: navigator.language,
76
+ screen: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0
77
+ };
78
+ }
79
+ function createTransport(config) {
80
+ const endpoint = `${config.ingestUrl}/v1/collect`;
81
+ const fetchImpl = resolveFetch(config.fetch);
82
+ return async function send(payload) {
83
+ if (config.disabled || isTrackingDisabled(config.honorDoNotTrack)) {
84
+ return;
85
+ }
86
+ const body = withWebsiteId(payload, config.websiteId);
87
+ try {
88
+ const response = await fetchImpl(endpoint, {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify(body),
92
+ keepalive: true,
93
+ credentials: "omit",
94
+ mode: "cors"
95
+ });
96
+ if (!response.ok) {
97
+ return {
98
+ ok: false,
99
+ error: `Ingest request failed with status ${response.status}`
100
+ };
101
+ }
102
+ return await response.json();
103
+ } catch {
104
+ return {
105
+ ok: false,
106
+ error: "Ingest request failed"
107
+ };
108
+ }
109
+ };
110
+ }
111
+ function createClient(config) {
112
+ const resolved = assertClientConfig(config);
113
+ const send = createTransport(resolved);
114
+ return {
115
+ config: resolved,
116
+ page(input = {}) {
117
+ const defaults = defaultPageviewInput();
118
+ const url = input.url ?? defaults.url;
119
+ if (!url) {
120
+ return;
121
+ }
122
+ void send({
123
+ type: "pageview",
124
+ url,
125
+ referrer: input.referrer ?? defaults.referrer,
126
+ title: input.title ?? defaults.title,
127
+ hostname: input.hostname ?? defaults.hostname,
128
+ language: input.language ?? defaults.language,
129
+ screen: input.screen ?? defaults.screen
130
+ });
131
+ },
132
+ track(name, properties) {
133
+ const defaults = defaultPageviewInput();
134
+ void send({
135
+ type: "track",
136
+ name,
137
+ properties: properties ?? {},
138
+ url: defaults.url,
139
+ hostname: defaults.hostname
140
+ });
141
+ },
142
+ identify(userId, traits) {
143
+ void send({
144
+ type: "identify",
145
+ userId,
146
+ traits: traits ?? {}
147
+ });
148
+ }
149
+ };
150
+ }
151
+ var defaultClient = null;
152
+ function init(config) {
153
+ defaultClient = createClient(config);
154
+ return defaultClient;
155
+ }
156
+ function getClient() {
157
+ if (!defaultClient) {
158
+ throw new Error(
159
+ "Dumbledor is not initialized. Call init(), wrap with DumbledorProvider, or createClient() first."
160
+ );
161
+ }
162
+ return defaultClient;
163
+ }
164
+
165
+ // src/react/context.tsx
166
+ var DumbledorContext = react.createContext(null);
167
+ function useDumbledor() {
168
+ const client = react.useContext(DumbledorContext);
169
+ if (client) {
170
+ return client;
171
+ }
172
+ return getClient();
173
+ }
174
+ function PageView({ pathname, trackInitialView = true }) {
175
+ const client = useDumbledor();
176
+ const lastPathRef = react.useRef(null);
177
+ react.useEffect(() => {
178
+ const nextPath = pathname ?? (typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}` : null);
179
+ if (!nextPath || lastPathRef.current === nextPath) {
180
+ return;
181
+ }
182
+ const isFirstView = lastPathRef.current === null;
183
+ lastPathRef.current = nextPath;
184
+ if (isFirstView && !trackInitialView) {
185
+ return;
186
+ }
187
+ client.page();
188
+ }, [client, pathname, trackInitialView]);
189
+ return null;
190
+ }
191
+ function useTrack() {
192
+ const client = useDumbledor();
193
+ return react.useCallback(
194
+ (name, properties) => {
195
+ client.track(name, properties);
196
+ },
197
+ [client]
198
+ );
199
+ }
200
+ function useIdentify() {
201
+ const client = useDumbledor();
202
+ return react.useCallback(
203
+ (userId, traits) => {
204
+ client.identify(userId, traits);
205
+ },
206
+ [client]
207
+ );
208
+ }
209
+ function usePage() {
210
+ const client = useDumbledor();
211
+ return react.useCallback(
212
+ (input) => {
213
+ client.page(input);
214
+ },
215
+ [client]
216
+ );
217
+ }
218
+ function DumbledorProvider({
219
+ children,
220
+ websiteId,
221
+ ingestUrl,
222
+ honorDoNotTrack,
223
+ disabled,
224
+ fetch
225
+ }) {
226
+ const client = react.useMemo(
227
+ () => init({
228
+ websiteId,
229
+ ingestUrl,
230
+ honorDoNotTrack,
231
+ disabled,
232
+ fetch
233
+ }),
234
+ [websiteId, ingestUrl, honorDoNotTrack, disabled, fetch]
235
+ );
236
+ return /* @__PURE__ */ jsxRuntime.jsx(DumbledorContext.Provider, { value: client, children });
237
+ }
238
+
239
+ exports.DumbledorContext = DumbledorContext;
240
+ exports.DumbledorProvider = DumbledorProvider;
241
+ exports.PageView = PageView;
242
+ exports.useDumbledor = useDumbledor;
243
+ exports.useIdentify = useIdentify;
244
+ exports.usePage = usePage;
245
+ exports.useTrack = useTrack;
246
+ //# sourceMappingURL=index.cjs.map
247
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/config.ts","../../src/dnt.ts","../../src/client.ts","../../src/react/context.tsx","../../src/react/page-view.tsx","../../src/react/hooks.ts","../../src/react/provider.tsx"],"names":["createContext","useContext","useRef","useEffect","useCallback","useMemo"],"mappings":"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","file":"index.cjs","sourcesContent":["export const DEFAULT_INGEST_URL = \"https://ingest.dumbledor.com\";\n\nexport const UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n","import { DEFAULT_INGEST_URL, UUID_PATTERN } from \"./constants\";\nimport type {\n CollectPayload,\n CollectPayloadInput,\n DumbledorClientConfig,\n ResolvedDumbledorConfig,\n} from \"./types\";\n\nexport class DumbledorConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DumbledorConfigError\";\n }\n}\n\nexport function normalizeIngestUrl(value?: string): string {\n const raw = value?.trim() || DEFAULT_INGEST_URL;\n return raw.replace(/\\/$/, \"\");\n}\n\nexport function assertClientConfig(config: DumbledorClientConfig): ResolvedDumbledorConfig {\n const websiteId = config.websiteId?.trim();\n\n if (!websiteId) {\n throw new DumbledorConfigError(\"websiteId is required.\");\n }\n\n if (!UUID_PATTERN.test(websiteId)) {\n throw new DumbledorConfigError(\"websiteId must be a valid UUID.\");\n }\n\n return {\n websiteId,\n ingestUrl: normalizeIngestUrl(config.ingestUrl),\n honorDoNotTrack: config.honorDoNotTrack ?? false,\n disabled: config.disabled ?? false,\n fetch: config.fetch,\n };\n}\n\nexport function withWebsiteId(\n payload: CollectPayloadInput,\n websiteId: string,\n): CollectPayload {\n return {\n ...payload,\n websiteId,\n };\n}\n\nexport function resolveFetch(fetchImpl?: typeof fetch): typeof fetch {\n const resolved = fetchImpl ?? globalThis.fetch;\n if (!resolved) {\n throw new DumbledorConfigError(\"fetch is not available in this runtime.\");\n }\n\n return resolved;\n}\n","type NavigatorWithMsDnt = Navigator & {\n msDoNotTrack?: string | number | null;\n};\n\ntype WindowWithDnt = Window & {\n doNotTrack?: string | number | null;\n};\n\nexport function hasDoNotTrack(): boolean {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n const win = window as WindowWithDnt;\n const dnt =\n win.doNotTrack ??\n win.navigator.doNotTrack ??\n (win.navigator as NavigatorWithMsDnt).msDoNotTrack;\n\n return dnt === 1 || dnt === \"1\" || dnt === \"yes\";\n}\n\nexport function isTrackingDisabled(honorDoNotTrack: boolean): boolean {\n return honorDoNotTrack && hasDoNotTrack();\n}\n","import { assertClientConfig, resolveFetch, withWebsiteId } from \"./config\";\nimport { isTrackingDisabled } from \"./dnt\";\nimport type {\n CollectPayloadInput,\n CollectResponse,\n DumbledorClient,\n DumbledorClientConfig,\n PageviewInput,\n ResolvedDumbledorConfig,\n} from \"./types\";\n\nfunction defaultPageviewInput(): PageviewInput {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return {};\n }\n\n return {\n url: window.location.href,\n referrer: document.referrer || undefined,\n title: document.title,\n hostname: window.location.hostname,\n language: navigator.language,\n screen: typeof screen !== \"undefined\" ? `${screen.width}x${screen.height}` : undefined,\n };\n}\n\nexport function createTransport(config: ResolvedDumbledorConfig) {\n const endpoint = `${config.ingestUrl}/v1/collect`;\n const fetchImpl = resolveFetch(config.fetch);\n\n return async function send(payload: CollectPayloadInput): Promise<CollectResponse | void> {\n if (config.disabled || isTrackingDisabled(config.honorDoNotTrack)) {\n return;\n }\n\n const body = withWebsiteId(payload, config.websiteId);\n\n try {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n keepalive: true,\n credentials: \"omit\",\n mode: \"cors\",\n });\n\n if (!response.ok) {\n return {\n ok: false,\n error: `Ingest request failed with status ${response.status}`,\n };\n }\n\n return (await response.json()) as CollectResponse;\n } catch {\n return {\n ok: false,\n error: \"Ingest request failed\",\n };\n }\n };\n}\n\nexport function createClient(config: DumbledorClientConfig): DumbledorClient {\n const resolved = assertClientConfig(config);\n const send = createTransport(resolved);\n\n return {\n config: resolved,\n page(input = {}) {\n const defaults = defaultPageviewInput();\n const url = input.url ?? defaults.url;\n if (!url) {\n return;\n }\n\n void send({\n type: \"pageview\",\n url,\n referrer: input.referrer ?? defaults.referrer,\n title: input.title ?? defaults.title,\n hostname: input.hostname ?? defaults.hostname,\n language: input.language ?? defaults.language,\n screen: input.screen ?? defaults.screen,\n });\n },\n track(name, properties) {\n const defaults = defaultPageviewInput();\n void send({\n type: \"track\",\n name,\n properties: properties ?? {},\n url: defaults.url,\n hostname: defaults.hostname,\n });\n },\n identify(userId, traits) {\n void send({\n type: \"identify\",\n userId,\n traits: traits ?? {},\n });\n },\n };\n}\n\nlet defaultClient: DumbledorClient | null = null;\n\nexport function init(config: DumbledorClientConfig): DumbledorClient {\n defaultClient = createClient(config);\n return defaultClient;\n}\n\nexport function getClient(): DumbledorClient {\n if (!defaultClient) {\n throw new Error(\n \"Dumbledor is not initialized. Call init(), wrap with DumbledorProvider, or createClient() first.\",\n );\n }\n\n return defaultClient;\n}\n\nexport function page(input?: PageviewInput): void {\n getClient().page(input);\n}\n\nexport function track(name: string, properties?: Record<string, unknown>): void {\n getClient().track(name, properties);\n}\n\nexport function identify(userId: string, traits?: Record<string, unknown>): void {\n getClient().identify(userId, traits);\n}\n","\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport { getClient } from \"../client\";\nimport type { DumbledorClient } from \"../types\";\n\nexport const DumbledorContext = createContext<DumbledorClient | null>(null);\n\nexport function useDumbledor(): DumbledorClient {\n const client = useContext(DumbledorContext);\n if (client) {\n return client;\n }\n\n return getClient();\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useDumbledor } from \"./context\";\n\nexport type PageViewProps = {\n /** Route pathname. When omitted, falls back to `window.location.pathname`. */\n pathname?: string;\n /** Track the first render. Default: `true`. */\n trackInitialView?: boolean;\n};\n\nexport function PageView({ pathname, trackInitialView = true }: PageViewProps) {\n const client = useDumbledor();\n const lastPathRef = useRef<string | null>(null);\n\n useEffect(() => {\n const nextPath =\n pathname ??\n (typeof window !== \"undefined\"\n ? `${window.location.pathname}${window.location.search}`\n : null);\n\n if (!nextPath || lastPathRef.current === nextPath) {\n return;\n }\n\n const isFirstView = lastPathRef.current === null;\n lastPathRef.current = nextPath;\n\n if (isFirstView && !trackInitialView) {\n return;\n }\n\n client.page();\n }, [client, pathname, trackInitialView]);\n\n return null;\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useDumbledor } from \"./context\";\n\nexport function useTrack() {\n const client = useDumbledor();\n return useCallback(\n (name: string, properties?: Record<string, unknown>) => {\n client.track(name, properties);\n },\n [client],\n );\n}\n\nexport function useIdentify() {\n const client = useDumbledor();\n return useCallback(\n (userId: string, traits?: Record<string, unknown>) => {\n client.identify(userId, traits);\n },\n [client],\n );\n}\n\nexport function usePage() {\n const client = useDumbledor();\n return useCallback(\n (input?: Parameters<typeof client.page>[0]) => {\n client.page(input);\n },\n [client],\n );\n}\n","\"use client\";\n\nimport { useMemo } from \"react\";\nimport { init } from \"../client\";\nimport type { DumbledorClientConfig } from \"../types\";\nimport { DumbledorContext } from \"./context\";\n\nexport type DumbledorProviderProps = DumbledorClientConfig & {\n children: React.ReactNode;\n};\n\nexport function DumbledorProvider({\n children,\n websiteId,\n ingestUrl,\n honorDoNotTrack,\n disabled,\n fetch,\n}: DumbledorProviderProps) {\n const client = useMemo(\n () =>\n init({\n websiteId,\n ingestUrl,\n honorDoNotTrack,\n disabled,\n fetch,\n }),\n [websiteId, ingestUrl, honorDoNotTrack, disabled, fetch],\n );\n\n return <DumbledorContext.Provider value={client}>{children}</DumbledorContext.Provider>;\n}\n"]}
@@ -0,0 +1,51 @@
1
+ import * as react from 'react';
2
+
3
+ type DumbledorClientConfig = {
4
+ /** Project website ID from tracking settings. */
5
+ websiteId: string;
6
+ /** Ingest API origin. Defaults to `https://ingest.dumbledor.com`. */
7
+ ingestUrl?: string;
8
+ /** Skip all tracking when the browser sends Do Not Track. Default: `false`. */
9
+ honorDoNotTrack?: boolean;
10
+ /** Disable tracking entirely. Useful for local development. */
11
+ disabled?: boolean;
12
+ /** Custom fetch implementation (testing, edge runtimes). */
13
+ fetch?: typeof fetch;
14
+ };
15
+ type DumbledorClient = {
16
+ readonly config: Readonly<ResolvedDumbledorConfig>;
17
+ page: (input?: PageviewInput) => void;
18
+ track: (name: string, properties?: Record<string, unknown>) => void;
19
+ identify: (userId: string, traits?: Record<string, unknown>) => void;
20
+ };
21
+ type ResolvedDumbledorConfig = Required<Pick<DumbledorClientConfig, "websiteId" | "ingestUrl" | "honorDoNotTrack" | "disabled">> & Pick<DumbledorClientConfig, "fetch">;
22
+ type PageviewInput = {
23
+ url?: string;
24
+ referrer?: string;
25
+ title?: string;
26
+ hostname?: string;
27
+ language?: string;
28
+ screen?: string;
29
+ };
30
+
31
+ type DumbledorProviderProps = DumbledorClientConfig & {
32
+ children: React.ReactNode;
33
+ };
34
+ declare function DumbledorProvider({ children, websiteId, ingestUrl, honorDoNotTrack, disabled, fetch, }: DumbledorProviderProps): react.JSX.Element;
35
+
36
+ declare const DumbledorContext: react.Context<DumbledorClient | null>;
37
+ declare function useDumbledor(): DumbledorClient;
38
+
39
+ type PageViewProps = {
40
+ /** Route pathname. When omitted, falls back to `window.location.pathname`. */
41
+ pathname?: string;
42
+ /** Track the first render. Default: `true`. */
43
+ trackInitialView?: boolean;
44
+ };
45
+ declare function PageView({ pathname, trackInitialView }: PageViewProps): null;
46
+
47
+ declare function useTrack(): (name: string, properties?: Record<string, unknown>) => void;
48
+ declare function useIdentify(): (userId: string, traits?: Record<string, unknown>) => void;
49
+ declare function usePage(): (input?: Parameters<(input?: PageviewInput) => void>[0]) => void;
50
+
51
+ export { DumbledorContext, DumbledorProvider, type DumbledorProviderProps, PageView, type PageViewProps, useDumbledor, useIdentify, usePage, useTrack };
@@ -0,0 +1,51 @@
1
+ import * as react from 'react';
2
+
3
+ type DumbledorClientConfig = {
4
+ /** Project website ID from tracking settings. */
5
+ websiteId: string;
6
+ /** Ingest API origin. Defaults to `https://ingest.dumbledor.com`. */
7
+ ingestUrl?: string;
8
+ /** Skip all tracking when the browser sends Do Not Track. Default: `false`. */
9
+ honorDoNotTrack?: boolean;
10
+ /** Disable tracking entirely. Useful for local development. */
11
+ disabled?: boolean;
12
+ /** Custom fetch implementation (testing, edge runtimes). */
13
+ fetch?: typeof fetch;
14
+ };
15
+ type DumbledorClient = {
16
+ readonly config: Readonly<ResolvedDumbledorConfig>;
17
+ page: (input?: PageviewInput) => void;
18
+ track: (name: string, properties?: Record<string, unknown>) => void;
19
+ identify: (userId: string, traits?: Record<string, unknown>) => void;
20
+ };
21
+ type ResolvedDumbledorConfig = Required<Pick<DumbledorClientConfig, "websiteId" | "ingestUrl" | "honorDoNotTrack" | "disabled">> & Pick<DumbledorClientConfig, "fetch">;
22
+ type PageviewInput = {
23
+ url?: string;
24
+ referrer?: string;
25
+ title?: string;
26
+ hostname?: string;
27
+ language?: string;
28
+ screen?: string;
29
+ };
30
+
31
+ type DumbledorProviderProps = DumbledorClientConfig & {
32
+ children: React.ReactNode;
33
+ };
34
+ declare function DumbledorProvider({ children, websiteId, ingestUrl, honorDoNotTrack, disabled, fetch, }: DumbledorProviderProps): react.JSX.Element;
35
+
36
+ declare const DumbledorContext: react.Context<DumbledorClient | null>;
37
+ declare function useDumbledor(): DumbledorClient;
38
+
39
+ type PageViewProps = {
40
+ /** Route pathname. When omitted, falls back to `window.location.pathname`. */
41
+ pathname?: string;
42
+ /** Track the first render. Default: `true`. */
43
+ trackInitialView?: boolean;
44
+ };
45
+ declare function PageView({ pathname, trackInitialView }: PageViewProps): null;
46
+
47
+ declare function useTrack(): (name: string, properties?: Record<string, unknown>) => void;
48
+ declare function useIdentify(): (userId: string, traits?: Record<string, unknown>) => void;
49
+ declare function usePage(): (input?: Parameters<(input?: PageviewInput) => void>[0]) => void;
50
+
51
+ export { DumbledorContext, DumbledorProvider, type DumbledorProviderProps, PageView, type PageViewProps, useDumbledor, useIdentify, usePage, useTrack };
@@ -0,0 +1,239 @@
1
+ "use client";
2
+ import { createContext, useContext, useRef, useEffect, useCallback, useMemo } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/constants.ts
6
+ var DEFAULT_INGEST_URL = "https://ingest.dumbledor.com";
7
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8
+
9
+ // src/config.ts
10
+ var DumbledorConfigError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "DumbledorConfigError";
14
+ }
15
+ };
16
+ function normalizeIngestUrl(value) {
17
+ const raw = value?.trim() || DEFAULT_INGEST_URL;
18
+ return raw.replace(/\/$/, "");
19
+ }
20
+ function assertClientConfig(config) {
21
+ const websiteId = config.websiteId?.trim();
22
+ if (!websiteId) {
23
+ throw new DumbledorConfigError("websiteId is required.");
24
+ }
25
+ if (!UUID_PATTERN.test(websiteId)) {
26
+ throw new DumbledorConfigError("websiteId must be a valid UUID.");
27
+ }
28
+ return {
29
+ websiteId,
30
+ ingestUrl: normalizeIngestUrl(config.ingestUrl),
31
+ honorDoNotTrack: config.honorDoNotTrack ?? false,
32
+ disabled: config.disabled ?? false,
33
+ fetch: config.fetch
34
+ };
35
+ }
36
+ function withWebsiteId(payload, websiteId) {
37
+ return {
38
+ ...payload,
39
+ websiteId
40
+ };
41
+ }
42
+ function resolveFetch(fetchImpl) {
43
+ const resolved = fetchImpl ?? globalThis.fetch;
44
+ if (!resolved) {
45
+ throw new DumbledorConfigError("fetch is not available in this runtime.");
46
+ }
47
+ return resolved;
48
+ }
49
+
50
+ // src/dnt.ts
51
+ function hasDoNotTrack() {
52
+ if (typeof window === "undefined") {
53
+ return false;
54
+ }
55
+ const win = window;
56
+ const dnt = win.doNotTrack ?? win.navigator.doNotTrack ?? win.navigator.msDoNotTrack;
57
+ return dnt === 1 || dnt === "1" || dnt === "yes";
58
+ }
59
+ function isTrackingDisabled(honorDoNotTrack) {
60
+ return honorDoNotTrack && hasDoNotTrack();
61
+ }
62
+
63
+ // src/client.ts
64
+ function defaultPageviewInput() {
65
+ if (typeof window === "undefined" || typeof document === "undefined") {
66
+ return {};
67
+ }
68
+ return {
69
+ url: window.location.href,
70
+ referrer: document.referrer || void 0,
71
+ title: document.title,
72
+ hostname: window.location.hostname,
73
+ language: navigator.language,
74
+ screen: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0
75
+ };
76
+ }
77
+ function createTransport(config) {
78
+ const endpoint = `${config.ingestUrl}/v1/collect`;
79
+ const fetchImpl = resolveFetch(config.fetch);
80
+ return async function send(payload) {
81
+ if (config.disabled || isTrackingDisabled(config.honorDoNotTrack)) {
82
+ return;
83
+ }
84
+ const body = withWebsiteId(payload, config.websiteId);
85
+ try {
86
+ const response = await fetchImpl(endpoint, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ body: JSON.stringify(body),
90
+ keepalive: true,
91
+ credentials: "omit",
92
+ mode: "cors"
93
+ });
94
+ if (!response.ok) {
95
+ return {
96
+ ok: false,
97
+ error: `Ingest request failed with status ${response.status}`
98
+ };
99
+ }
100
+ return await response.json();
101
+ } catch {
102
+ return {
103
+ ok: false,
104
+ error: "Ingest request failed"
105
+ };
106
+ }
107
+ };
108
+ }
109
+ function createClient(config) {
110
+ const resolved = assertClientConfig(config);
111
+ const send = createTransport(resolved);
112
+ return {
113
+ config: resolved,
114
+ page(input = {}) {
115
+ const defaults = defaultPageviewInput();
116
+ const url = input.url ?? defaults.url;
117
+ if (!url) {
118
+ return;
119
+ }
120
+ void send({
121
+ type: "pageview",
122
+ url,
123
+ referrer: input.referrer ?? defaults.referrer,
124
+ title: input.title ?? defaults.title,
125
+ hostname: input.hostname ?? defaults.hostname,
126
+ language: input.language ?? defaults.language,
127
+ screen: input.screen ?? defaults.screen
128
+ });
129
+ },
130
+ track(name, properties) {
131
+ const defaults = defaultPageviewInput();
132
+ void send({
133
+ type: "track",
134
+ name,
135
+ properties: properties ?? {},
136
+ url: defaults.url,
137
+ hostname: defaults.hostname
138
+ });
139
+ },
140
+ identify(userId, traits) {
141
+ void send({
142
+ type: "identify",
143
+ userId,
144
+ traits: traits ?? {}
145
+ });
146
+ }
147
+ };
148
+ }
149
+ var defaultClient = null;
150
+ function init(config) {
151
+ defaultClient = createClient(config);
152
+ return defaultClient;
153
+ }
154
+ function getClient() {
155
+ if (!defaultClient) {
156
+ throw new Error(
157
+ "Dumbledor is not initialized. Call init(), wrap with DumbledorProvider, or createClient() first."
158
+ );
159
+ }
160
+ return defaultClient;
161
+ }
162
+
163
+ // src/react/context.tsx
164
+ var DumbledorContext = createContext(null);
165
+ function useDumbledor() {
166
+ const client = useContext(DumbledorContext);
167
+ if (client) {
168
+ return client;
169
+ }
170
+ return getClient();
171
+ }
172
+ function PageView({ pathname, trackInitialView = true }) {
173
+ const client = useDumbledor();
174
+ const lastPathRef = useRef(null);
175
+ useEffect(() => {
176
+ const nextPath = pathname ?? (typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}` : null);
177
+ if (!nextPath || lastPathRef.current === nextPath) {
178
+ return;
179
+ }
180
+ const isFirstView = lastPathRef.current === null;
181
+ lastPathRef.current = nextPath;
182
+ if (isFirstView && !trackInitialView) {
183
+ return;
184
+ }
185
+ client.page();
186
+ }, [client, pathname, trackInitialView]);
187
+ return null;
188
+ }
189
+ function useTrack() {
190
+ const client = useDumbledor();
191
+ return useCallback(
192
+ (name, properties) => {
193
+ client.track(name, properties);
194
+ },
195
+ [client]
196
+ );
197
+ }
198
+ function useIdentify() {
199
+ const client = useDumbledor();
200
+ return useCallback(
201
+ (userId, traits) => {
202
+ client.identify(userId, traits);
203
+ },
204
+ [client]
205
+ );
206
+ }
207
+ function usePage() {
208
+ const client = useDumbledor();
209
+ return useCallback(
210
+ (input) => {
211
+ client.page(input);
212
+ },
213
+ [client]
214
+ );
215
+ }
216
+ function DumbledorProvider({
217
+ children,
218
+ websiteId,
219
+ ingestUrl,
220
+ honorDoNotTrack,
221
+ disabled,
222
+ fetch
223
+ }) {
224
+ const client = useMemo(
225
+ () => init({
226
+ websiteId,
227
+ ingestUrl,
228
+ honorDoNotTrack,
229
+ disabled,
230
+ fetch
231
+ }),
232
+ [websiteId, ingestUrl, honorDoNotTrack, disabled, fetch]
233
+ );
234
+ return /* @__PURE__ */ jsx(DumbledorContext.Provider, { value: client, children });
235
+ }
236
+
237
+ export { DumbledorContext, DumbledorProvider, PageView, useDumbledor, useIdentify, usePage, useTrack };
238
+ //# sourceMappingURL=index.js.map
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/config.ts","../../src/dnt.ts","../../src/client.ts","../../src/react/context.tsx","../../src/react/page-view.tsx","../../src/react/hooks.ts","../../src/react/provider.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const DEFAULT_INGEST_URL = \"https://ingest.dumbledor.com\";\n\nexport const UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n","import { DEFAULT_INGEST_URL, UUID_PATTERN } from \"./constants\";\nimport type {\n CollectPayload,\n CollectPayloadInput,\n DumbledorClientConfig,\n ResolvedDumbledorConfig,\n} from \"./types\";\n\nexport class DumbledorConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DumbledorConfigError\";\n }\n}\n\nexport function normalizeIngestUrl(value?: string): string {\n const raw = value?.trim() || DEFAULT_INGEST_URL;\n return raw.replace(/\\/$/, \"\");\n}\n\nexport function assertClientConfig(config: DumbledorClientConfig): ResolvedDumbledorConfig {\n const websiteId = config.websiteId?.trim();\n\n if (!websiteId) {\n throw new DumbledorConfigError(\"websiteId is required.\");\n }\n\n if (!UUID_PATTERN.test(websiteId)) {\n throw new DumbledorConfigError(\"websiteId must be a valid UUID.\");\n }\n\n return {\n websiteId,\n ingestUrl: normalizeIngestUrl(config.ingestUrl),\n honorDoNotTrack: config.honorDoNotTrack ?? false,\n disabled: config.disabled ?? false,\n fetch: config.fetch,\n };\n}\n\nexport function withWebsiteId(\n payload: CollectPayloadInput,\n websiteId: string,\n): CollectPayload {\n return {\n ...payload,\n websiteId,\n };\n}\n\nexport function resolveFetch(fetchImpl?: typeof fetch): typeof fetch {\n const resolved = fetchImpl ?? globalThis.fetch;\n if (!resolved) {\n throw new DumbledorConfigError(\"fetch is not available in this runtime.\");\n }\n\n return resolved;\n}\n","type NavigatorWithMsDnt = Navigator & {\n msDoNotTrack?: string | number | null;\n};\n\ntype WindowWithDnt = Window & {\n doNotTrack?: string | number | null;\n};\n\nexport function hasDoNotTrack(): boolean {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n const win = window as WindowWithDnt;\n const dnt =\n win.doNotTrack ??\n win.navigator.doNotTrack ??\n (win.navigator as NavigatorWithMsDnt).msDoNotTrack;\n\n return dnt === 1 || dnt === \"1\" || dnt === \"yes\";\n}\n\nexport function isTrackingDisabled(honorDoNotTrack: boolean): boolean {\n return honorDoNotTrack && hasDoNotTrack();\n}\n","import { assertClientConfig, resolveFetch, withWebsiteId } from \"./config\";\nimport { isTrackingDisabled } from \"./dnt\";\nimport type {\n CollectPayloadInput,\n CollectResponse,\n DumbledorClient,\n DumbledorClientConfig,\n PageviewInput,\n ResolvedDumbledorConfig,\n} from \"./types\";\n\nfunction defaultPageviewInput(): PageviewInput {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return {};\n }\n\n return {\n url: window.location.href,\n referrer: document.referrer || undefined,\n title: document.title,\n hostname: window.location.hostname,\n language: navigator.language,\n screen: typeof screen !== \"undefined\" ? `${screen.width}x${screen.height}` : undefined,\n };\n}\n\nexport function createTransport(config: ResolvedDumbledorConfig) {\n const endpoint = `${config.ingestUrl}/v1/collect`;\n const fetchImpl = resolveFetch(config.fetch);\n\n return async function send(payload: CollectPayloadInput): Promise<CollectResponse | void> {\n if (config.disabled || isTrackingDisabled(config.honorDoNotTrack)) {\n return;\n }\n\n const body = withWebsiteId(payload, config.websiteId);\n\n try {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n keepalive: true,\n credentials: \"omit\",\n mode: \"cors\",\n });\n\n if (!response.ok) {\n return {\n ok: false,\n error: `Ingest request failed with status ${response.status}`,\n };\n }\n\n return (await response.json()) as CollectResponse;\n } catch {\n return {\n ok: false,\n error: \"Ingest request failed\",\n };\n }\n };\n}\n\nexport function createClient(config: DumbledorClientConfig): DumbledorClient {\n const resolved = assertClientConfig(config);\n const send = createTransport(resolved);\n\n return {\n config: resolved,\n page(input = {}) {\n const defaults = defaultPageviewInput();\n const url = input.url ?? defaults.url;\n if (!url) {\n return;\n }\n\n void send({\n type: \"pageview\",\n url,\n referrer: input.referrer ?? defaults.referrer,\n title: input.title ?? defaults.title,\n hostname: input.hostname ?? defaults.hostname,\n language: input.language ?? defaults.language,\n screen: input.screen ?? defaults.screen,\n });\n },\n track(name, properties) {\n const defaults = defaultPageviewInput();\n void send({\n type: \"track\",\n name,\n properties: properties ?? {},\n url: defaults.url,\n hostname: defaults.hostname,\n });\n },\n identify(userId, traits) {\n void send({\n type: \"identify\",\n userId,\n traits: traits ?? {},\n });\n },\n };\n}\n\nlet defaultClient: DumbledorClient | null = null;\n\nexport function init(config: DumbledorClientConfig): DumbledorClient {\n defaultClient = createClient(config);\n return defaultClient;\n}\n\nexport function getClient(): DumbledorClient {\n if (!defaultClient) {\n throw new Error(\n \"Dumbledor is not initialized. Call init(), wrap with DumbledorProvider, or createClient() first.\",\n );\n }\n\n return defaultClient;\n}\n\nexport function page(input?: PageviewInput): void {\n getClient().page(input);\n}\n\nexport function track(name: string, properties?: Record<string, unknown>): void {\n getClient().track(name, properties);\n}\n\nexport function identify(userId: string, traits?: Record<string, unknown>): void {\n getClient().identify(userId, traits);\n}\n","\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport { getClient } from \"../client\";\nimport type { DumbledorClient } from \"../types\";\n\nexport const DumbledorContext = createContext<DumbledorClient | null>(null);\n\nexport function useDumbledor(): DumbledorClient {\n const client = useContext(DumbledorContext);\n if (client) {\n return client;\n }\n\n return getClient();\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useDumbledor } from \"./context\";\n\nexport type PageViewProps = {\n /** Route pathname. When omitted, falls back to `window.location.pathname`. */\n pathname?: string;\n /** Track the first render. Default: `true`. */\n trackInitialView?: boolean;\n};\n\nexport function PageView({ pathname, trackInitialView = true }: PageViewProps) {\n const client = useDumbledor();\n const lastPathRef = useRef<string | null>(null);\n\n useEffect(() => {\n const nextPath =\n pathname ??\n (typeof window !== \"undefined\"\n ? `${window.location.pathname}${window.location.search}`\n : null);\n\n if (!nextPath || lastPathRef.current === nextPath) {\n return;\n }\n\n const isFirstView = lastPathRef.current === null;\n lastPathRef.current = nextPath;\n\n if (isFirstView && !trackInitialView) {\n return;\n }\n\n client.page();\n }, [client, pathname, trackInitialView]);\n\n return null;\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useDumbledor } from \"./context\";\n\nexport function useTrack() {\n const client = useDumbledor();\n return useCallback(\n (name: string, properties?: Record<string, unknown>) => {\n client.track(name, properties);\n },\n [client],\n );\n}\n\nexport function useIdentify() {\n const client = useDumbledor();\n return useCallback(\n (userId: string, traits?: Record<string, unknown>) => {\n client.identify(userId, traits);\n },\n [client],\n );\n}\n\nexport function usePage() {\n const client = useDumbledor();\n return useCallback(\n (input?: Parameters<typeof client.page>[0]) => {\n client.page(input);\n },\n [client],\n );\n}\n","\"use client\";\n\nimport { useMemo } from \"react\";\nimport { init } from \"../client\";\nimport type { DumbledorClientConfig } from \"../types\";\nimport { DumbledorContext } from \"./context\";\n\nexport type DumbledorProviderProps = DumbledorClientConfig & {\n children: React.ReactNode;\n};\n\nexport function DumbledorProvider({\n children,\n websiteId,\n ingestUrl,\n honorDoNotTrack,\n disabled,\n fetch,\n}: DumbledorProviderProps) {\n const client = useMemo(\n () =>\n init({\n websiteId,\n ingestUrl,\n honorDoNotTrack,\n disabled,\n fetch,\n }),\n [websiteId, ingestUrl, honorDoNotTrack, disabled, fetch],\n );\n\n return <DumbledorContext.Provider value={client}>{children}</DumbledorContext.Provider>;\n}\n"]}