@nurdd/web-sdk 1.0.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/README.md ADDED
@@ -0,0 +1,434 @@
1
+ # @nurdd/web-sdk
2
+
3
+ Framework-agnostic **attribution & analytics** SDK for the browser, powered by
4
+ [Nurdd](https://nurdd.club). One package, one integration, every popular
5
+ frontend — measure where your traffic, signups and revenue actually come from
6
+ without building a data pipeline.
7
+
8
+ | Framework | Import path | Helper |
9
+ | ------------------------- | ------------------------ | -------------------------------------- |
10
+ | Vanilla JS | `<script>` UMD or ESM | `Nurdd.initSdk({...})` |
11
+ | React | `@nurdd/web-sdk/react` | `<NurddProvider>`, `useNurdd()` |
12
+ | Next.js (App Router) | `@nurdd/web-sdk/next` | `<NurddProvider>`, `<NurddPageView />` |
13
+ | Vue 3 | `@nurdd/web-sdk/vue` | `NurddPlugin`, `useNurdd()` |
14
+ | Nuxt 3 | `@nurdd/web-sdk/nuxt` | `plugins/nurdd.client.ts` |
15
+ | Angular | `@nurdd/web-sdk/angular` | `provideNurdd()`, `NurddService` |
16
+ | Remix / SvelteKit / Solid | core import | `createSdk({...})` |
17
+
18
+ ---
19
+
20
+ ## Table of contents
21
+
22
+ - [Features](#features)
23
+ - [Prerequisites](#prerequisites)
24
+ - [Getting your API key](#getting-your-api-key)
25
+ - [Install](#install)
26
+ - [Quick start](#quick-start)
27
+ - [Configuration](#configuration)
28
+ - [Framework guides](#framework-guides)
29
+ - [Public API](#public-api)
30
+ - [How it works](#how-it-works)
31
+ - [Best practices](#best-practices)
32
+ - [Troubleshooting](#troubleshooting)
33
+ - [FAQ](#faq)
34
+ - [Versioning](#versioning)
35
+ - [License](#license)
36
+
37
+ ## Features
38
+
39
+ - **Event tracking** with offline buffering and automatic retries
40
+ - **Session tracking** with automatic idle expiry
41
+ - **Conversion tracking** (purchase, signup, custom goals)
42
+ - **Deep-link attribution**, including deferred (post-install) flows
43
+ - **Campaign & UTM tracking** with auto-strip after capture
44
+ - **Cross-platform identity** mapping
45
+ - **Auto-tracking**: SPA page views, outbound clicks, unload-time flush
46
+ - **SSR-safe**: no `window` access during server rendering
47
+ - **Zero runtime dependencies**
48
+
49
+ Everything shows up in your [Nurdd dashboard](https://nurdd.club), ready to slice
50
+ by campaign, cohort and conversion.
51
+
52
+ ## Prerequisites
53
+
54
+ - A **Nurdd account**.
55
+ - A **public SDK key** and your **API URL** — see [Getting your API key](#getting-your-api-key).
56
+ - A modern browser build target (ES2019+). Framework adapters require the
57
+ matching framework as a peer dependency (React ≥ 16.8, Vue ≥ 3, Angular ≥ 12).
58
+
59
+ ## Getting your API key
60
+
61
+ API keys are issued to you by a **Brand** from inside the Nurdd app — you cannot
62
+ generate them yourself. To receive a key:
63
+
64
+ 1. The Brand logs in to the Nurdd app.
65
+ 2. They open **Settings → SDK Setup**.
66
+ 3. They enter your (the developer's) email address.
67
+ 4. Nurdd shares the API key with you.
68
+
69
+ Use the key you receive as your `sdkKey`. If you don't have one yet, ask your
70
+ Brand contact to complete the **SDK Setup** step above.
71
+
72
+ ## Install
73
+
74
+ ```sh
75
+ npm install @nurdd/web-sdk
76
+ # or
77
+ yarn add @nurdd/web-sdk
78
+ # or
79
+ pnpm add @nurdd/web-sdk
80
+ ```
81
+
82
+ For frameworkless `<script>` use, load the prebuilt UMD bundle from the Nurdd
83
+ CDN:
84
+
85
+ ```html
86
+ <script src="https://cdn.nurdd.club/web-sdk/nurdd-sdk.umd.js"></script>
87
+ ```
88
+
89
+ ## Quick start
90
+
91
+ ```js
92
+ import { initSdk } from "@nurdd/web-sdk";
93
+
94
+ const sdk = initSdk({
95
+ sdkKey: "sdk_pk_xxx", // the key your Brand shared with you
96
+ baseUrl: "https://api.nurdd.com", // your Nurdd API URL
97
+ });
98
+
99
+ sdk.track("button_clicked", { button: "hero_cta" });
100
+ sdk.identify("user_42", { email: "user@example.com" });
101
+ sdk.conversion("purchase", { value: 49.99, currency: "USD" });
102
+ ```
103
+
104
+ Page views, sessions and campaign attribution are tracked automatically from
105
+ here — see the [framework guides](#framework-guides) for the idiomatic setup in
106
+ your stack.
107
+
108
+ ## Configuration
109
+
110
+ | Option | Default | What it does |
111
+ | -------------------------- | --------- | ------------------------------------------------- |
112
+ | `sdkKey` (required) | — | Your public SDK key from the Nurdd dashboard |
113
+ | `baseUrl` (required) | — | Your Nurdd API URL (e.g. `https://api.nurdd.com`) |
114
+ | `appVersion` | `null` | Forwarded with every event for cohort analysis |
115
+ | `debug` | `false` | Verbose console output |
116
+ | `flushIntervalMs` | `5000` | Background queue flush cadence |
117
+ | `maxBatchSize` | `50` | Max events per batched request |
118
+ | `sessionTimeoutMs` | `1800000` | Idle-session expiry (30 min) |
119
+ | `autotrack.pageViews` | `true` | Wire history navigation to `page_view` |
120
+ | `autotrack.outboundClicks` | `false` | Track cross-origin anchor clicks |
121
+ | `autotrack.flushOnUnload` | `true` | Flush pending events on page hide |
122
+ | `stripUtmFromUrl` | `true` | Remove UTM params from the URL after first read |
123
+
124
+ ## Framework guides
125
+
126
+ ### Vanilla JS / HTML
127
+
128
+ ```html
129
+ <!doctype html>
130
+ <html>
131
+ <body>
132
+ <script src="https://cdn.nurdd.club/web-sdk/nurdd-sdk.umd.js"></script>
133
+ <script>
134
+ const sdk = Nurdd.initSdk({
135
+ sdkKey: "sdk_pk_xxx",
136
+ baseUrl: "https://api.nurdd.com",
137
+ });
138
+
139
+ sdk.track("button_clicked", { button: "hero_cta" });
140
+ sdk.identify("user_42", { email: "user@example.com" });
141
+ sdk.conversion("purchase", { value: 49.99, currency: "USD" });
142
+
143
+ sdk.onDeepLink((dl) => {
144
+ if (dl.deep_link_path) location.href = dl.deep_link_path;
145
+ });
146
+ </script>
147
+ </body>
148
+ </html>
149
+ ```
150
+
151
+ ESM equivalent (with a bundler):
152
+
153
+ ```js
154
+ import { initSdk } from "@nurdd/web-sdk";
155
+ const sdk = initSdk({ sdkKey: "sdk_pk_xxx", baseUrl: "https://api.nurdd.com" });
156
+ sdk.track("page_loaded");
157
+ ```
158
+
159
+ ### React
160
+
161
+ ```tsx
162
+ // app.tsx
163
+ import { NurddProvider } from "@nurdd/web-sdk/react";
164
+
165
+ export function App() {
166
+ return (
167
+ <NurddProvider
168
+ config={{ sdkKey: "sdk_pk_xxx", baseUrl: "https://api.nurdd.com" }}
169
+ >
170
+ <Routes />
171
+ </NurddProvider>
172
+ );
173
+ }
174
+
175
+ // any component
176
+ import { useTrack, useIdentity, useDeepLink } from "@nurdd/web-sdk/react";
177
+
178
+ function CheckoutButton({ priceId }) {
179
+ const track = useTrack();
180
+ return (
181
+ <button onClick={() => track("checkout_started", { priceId })}>Buy</button>
182
+ );
183
+ }
184
+
185
+ function Boot() {
186
+ const { identify } = useIdentity();
187
+ useDeepLink((dl) => {
188
+ if (dl.deep_link_path) navigate(dl.deep_link_path);
189
+ });
190
+ useEffect(() => {
191
+ identify("user_42", { email: "user@example.com" });
192
+ }, []);
193
+ return null;
194
+ }
195
+ ```
196
+
197
+ ### Next.js (App Router)
198
+
199
+ ```tsx
200
+ // app/providers.tsx — Client Component
201
+ "use client";
202
+ import { NurddProvider } from "@nurdd/web-sdk/next";
203
+ export default function Providers({ children }) {
204
+ return (
205
+ <NurddProvider
206
+ config={{ sdkKey: "sdk_pk_xxx", baseUrl: "https://api.nurdd.com" }}
207
+ >
208
+ {children}
209
+ </NurddProvider>
210
+ );
211
+ }
212
+
213
+ // app/page-view.tsx
214
+ ("use client");
215
+ import { usePathname, useSearchParams } from "next/navigation";
216
+ import { NurddPageView } from "@nurdd/web-sdk/next";
217
+ export default function PageView() {
218
+ return (
219
+ <NurddPageView pathname={usePathname()} searchParams={useSearchParams()} />
220
+ );
221
+ }
222
+
223
+ // app/layout.tsx
224
+ import Providers from "./providers";
225
+ import PageView from "./page-view";
226
+ export default function RootLayout({ children }) {
227
+ return (
228
+ <html>
229
+ <body>
230
+ <Providers>
231
+ <PageView />
232
+ {children}
233
+ </Providers>
234
+ </body>
235
+ </html>
236
+ );
237
+ }
238
+ ```
239
+
240
+ ### Vue 3
241
+
242
+ ```ts
243
+ // main.ts
244
+ import { createApp } from "vue";
245
+ import { NurddPlugin } from "@nurdd/web-sdk/vue";
246
+ import App from "./App.vue";
247
+
248
+ createApp(App)
249
+ .use(NurddPlugin, {
250
+ sdkKey: "sdk_pk_xxx",
251
+ baseUrl: "https://api.nurdd.com",
252
+ })
253
+ .mount("#app");
254
+ ```
255
+
256
+ ```vue
257
+ <!-- any component -->
258
+ <script setup>
259
+ import { useRoute } from "vue-router";
260
+ import { useNurdd, trackPageViews, useIdentity } from "@nurdd/web-sdk/vue";
261
+
262
+ const sdk = useNurdd();
263
+ trackPageViews(useRoute());
264
+ const { identify } = useIdentity();
265
+
266
+ function buy() {
267
+ sdk.conversion("purchase", { value: 19.0, currency: "USD" });
268
+ }
269
+ </script>
270
+ ```
271
+
272
+ ### Nuxt 3
273
+
274
+ ```ts
275
+ // nuxt.config.ts
276
+ export default defineNuxtConfig({
277
+ runtimeConfig: {
278
+ public: {
279
+ nurdd: { sdkKey: "sdk_pk_xxx", baseUrl: "https://api.nurdd.com" },
280
+ },
281
+ },
282
+ });
283
+ ```
284
+
285
+ ```ts
286
+ // plugins/nurdd.client.ts
287
+ export { default } from "@nurdd/web-sdk/nuxt";
288
+ ```
289
+
290
+ ```vue
291
+ <!-- any component -->
292
+ <script setup>
293
+ const { $nurdd } = useNuxtApp();
294
+ $nurdd.track("hero_viewed");
295
+ </script>
296
+ ```
297
+
298
+ ### Angular (standalone)
299
+
300
+ ```ts
301
+ // app.config.ts
302
+ import { ApplicationConfig } from "@angular/core";
303
+ import { provideNurdd } from "@nurdd/web-sdk/angular";
304
+
305
+ export const appConfig: ApplicationConfig = {
306
+ providers: [
307
+ provideNurdd({ sdkKey: "sdk_pk_xxx", baseUrl: "https://api.nurdd.com" }),
308
+ ],
309
+ };
310
+ ```
311
+
312
+ ```ts
313
+ // any.component.ts
314
+ import { Component, inject } from "@angular/core";
315
+ import { NurddService } from "@nurdd/web-sdk/angular";
316
+
317
+ @Component({
318
+ selector: "buy-btn",
319
+ template: `<button (click)="buy()">Buy</button>`,
320
+ })
321
+ export class BuyButton {
322
+ private nurdd = inject(NurddService);
323
+ buy() {
324
+ this.nurdd.conversion("purchase", { value: 9.99, currency: "USD" });
325
+ }
326
+ }
327
+ ```
328
+
329
+ ### Remix / SvelteKit / Solid
330
+
331
+ The core works anywhere — import `createSdk` and store the instance somewhere
332
+ global (a Remix loader context, a Svelte store, a Solid signal, etc.). Every
333
+ server-only render is a no-op.
334
+
335
+ ```ts
336
+ import { createSdk } from "@nurdd/web-sdk";
337
+ export const nurdd = createSdk({
338
+ sdkKey: "sdk_pk_xxx",
339
+ baseUrl: "https://api.nurdd.com",
340
+ });
341
+ if (typeof window !== "undefined") nurdd.init();
342
+ ```
343
+
344
+ ## Public API
345
+
346
+ ```ts
347
+ sdk.init(); // idempotent; safe to call anytime
348
+ sdk.track(eventName, properties?, options?);
349
+ sdk.page(name?, properties?);
350
+ sdk.identify(externalUserId, traits?);
351
+ sdk.reset(); // clear the current user on logout
352
+ sdk.conversion(type, { value?, currency?, campaignId?, attribution? });
353
+ sdk.onDeepLink(listener); // returns an unsubscribe fn
354
+ sdk.flush(); // deliver pending events now
355
+ sdk.endSession();
356
+ sdk.destroy();
357
+ sdk.info(); // diagnostics
358
+ ```
359
+
360
+ Every method is safe to call before `init()` resolves — events are queued locally
361
+ and shipped on the next flush.
362
+
363
+ ### React / Vue hooks
364
+
365
+ ```ts
366
+ useNurdd(); // the SDK instance
367
+ useTrack(); // (event, properties?) => void
368
+ useIdentity(); // { identify(userId, traits?), reset() }
369
+ useDeepLink(cb); // run a callback when the user arrives from a campaign link
370
+ ```
371
+
372
+ ## How it works
373
+
374
+ The SDK is **offline-first** and designed to never lose an event:
375
+
376
+ - Events are persisted locally **before** the network call, so a tab crash or a
377
+ dropped connection can't lose them — they retry automatically on reconnect.
378
+ - On page hide, pending events are flushed reliably so last-mile events still
379
+ ship.
380
+ - Attribution captured from a landing URL (UTM/campaign) is remembered and
381
+ stamped onto later conversions, so a signup or purchase still credits the
382
+ original campaign even after the URL is cleaned up.
383
+
384
+ ## Best practices
385
+
386
+ - **Initialise once** near the root of your app and reuse the single instance.
387
+ - **Call `identify()` on login/signup** so anonymous activity is stitched to the
388
+ known user, and **`reset()` on logout**.
389
+ - **Let attribution flow automatically** — record a `conversion()` and the SDK
390
+ credits the campaign the visitor arrived from, with no extra wiring.
391
+ - **Keep only your public key in the browser.** The public key is safe to ship;
392
+ never embed a server key in client code.
393
+ - **Add `flushOnUnload`-friendly events** rather than blocking navigation to send
394
+ data — the SDK already handles unload flushing.
395
+
396
+ ## Troubleshooting
397
+
398
+ | Symptom | Likely cause & fix |
399
+ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
400
+ | `sdkKey is required` / `baseUrl is required` | Pass both `sdkKey` and `baseUrl` to `initSdk`/`createSdk`. |
401
+ | Nothing appears in the dashboard | Enable `debug: true` and check the console; confirm the key and API URL. |
402
+ | CORS / origin errors in the browser | Add your site's origin to the key's allow-list in the dashboard. |
403
+ | Conversions not attributed to a campaign | Ensure the visitor arrived via a Nurdd campaign link before converting. |
404
+ | Duplicate page views in an SPA | Use the framework's page-view helper instead of tracking manually. |
405
+ | Events delayed | This is expected — events batch and flush every `flushIntervalMs`; call `sdk.flush()` to force delivery. |
406
+
407
+ ## FAQ
408
+
409
+ **Is the SDK safe to render on the server?**
410
+ Yes. It never touches `window` during SSR; browser-only work is deferred to the
411
+ client.
412
+
413
+ **Does it add runtime dependencies?**
414
+ No. The core has zero runtime dependencies. Framework adapters use the framework
415
+ you already have as an optional peer dependency.
416
+
417
+ **Can I run more than one instance?**
418
+ Yes. Use `createSdk()` for isolated instances (e.g. multi-tenant), or `initSdk()`
419
+ for the common single-instance case.
420
+
421
+ **Where do I get my key and API URL?**
422
+ Your key is issued to you by a Brand from the Nurdd app — see
423
+ [Getting your API key](#getting-your-api-key). You cannot generate one yourself.
424
+
425
+ ## Versioning
426
+
427
+ This package follows [semantic versioning](https://semver.org). The SDK talks to
428
+ a versioned Nurdd API, so a new major version of the SDK can adopt a new API
429
+ version while existing apps keep working unchanged. Pin a major version and
430
+ upgrade deliberately.
431
+
432
+ ## License
433
+
434
+ MIT
@@ -0,0 +1,48 @@
1
+ // Angular adapter. Angular's decorators rely on TypeScript metadata
2
+ // emit, so instead of decorating the class here we expose a plain class
3
+ // plus a `provideNurdd()` factory the consumer can register in their
4
+ // app config. This works in both standalone and NgModule apps and
5
+ // stays compatible with the AOT compiler.
6
+ import { InjectionToken } from "@angular/core";
7
+ import { createSdk, getSdk, hasDefaultSdk, initSdk, setDefaultSdk } from "../src/sdk.js";
8
+
9
+ export const NURDD_SDK = new InjectionToken("NURDD_SDK");
10
+
11
+ // Lightweight service. Consumer DI's it like any other Angular service.
12
+ export class NurddService {
13
+ constructor(sdk) {
14
+ this.sdk = sdk;
15
+ this.sdk.init().catch((e) => console.error("[nurdd] init failed", e));
16
+ }
17
+ track(event, properties, options) { return this.sdk.track(event, properties, options); }
18
+ page(name, properties) { return this.sdk.page(name, properties); }
19
+ identify(externalUserId, traits) { return this.sdk.identify(externalUserId, traits); }
20
+ reset() { return this.sdk.reset(); }
21
+ conversion(type, payload) { return this.sdk.conversion(type, payload); }
22
+ click(payload) { return this.sdk.click(payload); }
23
+ onDeepLink(listener) { return this.sdk.onDeepLink(listener); }
24
+ flush() { return this.sdk.flush(); }
25
+ raw() { return this.sdk; }
26
+ }
27
+
28
+ // Wires the SDK into Angular's DI graph.
29
+ //
30
+ // // app.config.ts (standalone)
31
+ // import { provideNurdd } from '@nurdd/web-sdk/angular';
32
+ // export const appConfig = {
33
+ // providers: [provideNurdd({ sdkKey: '...', baseUrl: '...' })],
34
+ // };
35
+ //
36
+ // For module-based apps, add the returned providers to NgModule.providers.
37
+ export function provideNurdd(config) {
38
+ const factory = () => {
39
+ if (hasDefaultSdk()) return getSdk();
40
+ return initSdk(config);
41
+ };
42
+ return [
43
+ { provide: NURDD_SDK, useFactory: factory },
44
+ { provide: NurddService, useFactory: () => new NurddService(factory()) },
45
+ ];
46
+ }
47
+
48
+ export { createSdk, initSdk, getSdk, setDefaultSdk };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ // Next.js adapter. Next is just React under the hood, so we re-export
3
+ // the React adapter and add Next-specific helpers.
4
+ //
5
+ // The "use client" pragma at the top means this module is safe to
6
+ // import from Server Components — Next will bundle it for the client
7
+ // when it's actually rendered. The SDK itself short-circuits whenever
8
+ // it's evaluated server-side (see env.js / storage.js fallbacks).
9
+ import { useEffect } from "react";
10
+ import { useNurdd } from "./react.js";
11
+
12
+ export * from "./react.js";
13
+
14
+ // Drop-in <NurddPageView /> for the Next.js App Router. Place it inside
15
+ // a Client Component layout and pass it the current pathname + query
16
+ // from `usePathname()` / `useSearchParams()`.
17
+ export function NurddPageView({ pathname, searchParams, properties }) {
18
+ const sdk = useNurdd();
19
+ useEffect(() => {
20
+ if (!pathname) return;
21
+ const search = searchParams ? `?${searchParams.toString()}` : "";
22
+ sdk.page(`${pathname}${search}`, { pathname, ...(properties || {}) });
23
+ }, [sdk, pathname, searchParams?.toString?.(), JSON.stringify(properties || {})]);
24
+ return null;
25
+ }
@@ -0,0 +1,38 @@
1
+ // Nuxt 3 adapter. Provides the SDK on the Nuxt app instance so it's
2
+ // available everywhere via `useNuxtApp().$nurdd` (and via the Vue
3
+ // `useNurdd()` composable when running inside a component).
4
+ //
5
+ // Drop this in `plugins/nurdd.client.ts` (note the `.client.` suffix —
6
+ // the SDK only runs in the browser; SSR pages can still import it
7
+ // because every browser-only call is gated server-side).
8
+ import { defineNuxtPlugin, useRouter } from "#imports";
9
+ import { initSdk, getSdk, hasDefaultSdk } from "../src/sdk.js";
10
+
11
+ export default defineNuxtPlugin((nuxtApp) => {
12
+ const runtimeConfig = nuxtApp.$config?.public?.nurdd || {};
13
+ const config = {
14
+ sdkKey: runtimeConfig.sdkKey,
15
+ baseUrl: runtimeConfig.baseUrl,
16
+ debug: runtimeConfig.debug,
17
+ appVersion: runtimeConfig.appVersion,
18
+ };
19
+
20
+ const sdk = hasDefaultSdk() ? getSdk() : initSdk(config);
21
+ sdk.init().catch((err) => console.error("[nurdd] init failed", err));
22
+
23
+ // Route-change page views.
24
+ try {
25
+ const router = useRouter();
26
+ router.afterEach((to) => {
27
+ sdk.page(to.fullPath, { name: to.name?.toString?.() || null });
28
+ });
29
+ } catch {
30
+ // Router not installed — that's fine, autotrack still covers history changes.
31
+ }
32
+
33
+ return { provide: { nurdd: sdk } };
34
+ });
35
+
36
+ // Re-export everything from the Vue adapter so app code can pull
37
+ // composables from `#imports` or directly from `@nurdd/web-sdk/nuxt`.
38
+ export * from "./vue.js";
@@ -0,0 +1,87 @@
1
+ // React adapter: provider + hooks. React is a peer dependency — this
2
+ // file imports it lazily so the core SDK has no React in its graph.
3
+ import { createElement, createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
4
+ import { createSdk, getSdk, hasDefaultSdk, initSdk, setDefaultSdk } from "../src/sdk.js";
5
+
6
+ const NurddContext = createContext(null);
7
+
8
+ // <NurddProvider config={{ sdkKey, baseUrl }}>...</NurddProvider>
9
+ //
10
+ // Initialises the SDK once (StrictMode-safe via the ref + bool guard)
11
+ // and exposes it to every descendant.
12
+ export function NurddProvider({ config, sdk: external, children }) {
13
+ const sdkRef = useRef(null);
14
+ if (!sdkRef.current) {
15
+ if (external) {
16
+ sdkRef.current = external;
17
+ } else if (hasDefaultSdk()) {
18
+ sdkRef.current = getSdk();
19
+ } else if (config) {
20
+ sdkRef.current = initSdk(config);
21
+ } else {
22
+ throw new Error("[nurdd] NurddProvider requires either `config` or an existing `sdk` prop");
23
+ }
24
+ }
25
+
26
+ useEffect(() => {
27
+ // No-op if init() already ran. Returns a cleanup that flushes
28
+ // the queue but does NOT destroy the SDK (other parts of the app
29
+ // may still need it on unmount of a single provider).
30
+ sdkRef.current?.init?.().catch(() => {});
31
+ return () => { sdkRef.current?.flush?.().catch(() => {}); };
32
+ }, []);
33
+
34
+ return createElement(NurddContext.Provider, { value: sdkRef.current }, children);
35
+ }
36
+
37
+ export function useNurdd() {
38
+ const ctx = useContext(NurddContext);
39
+ if (ctx) return ctx;
40
+ if (hasDefaultSdk()) return getSdk();
41
+ throw new Error("[nurdd] useNurdd() called outside of <NurddProvider>");
42
+ }
43
+
44
+ // Convenience: stable track callback that closes over no rendering state.
45
+ export function useTrack() {
46
+ const sdk = useNurdd();
47
+ return useMemo(() => (event, props, opts) => sdk.track(event, props, opts), [sdk]);
48
+ }
49
+
50
+ // Fires a page_view whenever `path` changes. Works with any router
51
+ // (react-router, Next.js useRouter, TanStack Router, etc.) — just pass
52
+ // the current location to it.
53
+ export function usePageView(path, properties) {
54
+ const sdk = useNurdd();
55
+ useEffect(() => {
56
+ if (!path) return;
57
+ sdk.page(path, properties || {});
58
+ }, [sdk, path, JSON.stringify(properties || {})]);
59
+ }
60
+
61
+ // Subscribes to deferred deep link resolutions. The callback receives
62
+ // `{ short_code, deep_link_path, ... }` so the app can route.
63
+ export function useDeepLink(callback) {
64
+ const sdk = useNurdd();
65
+ const cbRef = useRef(callback);
66
+ cbRef.current = callback;
67
+ useEffect(() => {
68
+ const off = sdk.onDeepLink((payload) => cbRef.current?.(payload));
69
+ return off;
70
+ }, [sdk]);
71
+ }
72
+
73
+ // React state mirror of the latest identity, useful for analytics
74
+ // dashboards or feature flags keyed on user id.
75
+ export function useIdentity() {
76
+ const sdk = useNurdd();
77
+ const [identity, setIdentity] = useState(() => sdk.identity.current());
78
+ const identify = useMemo(() => async (id, traits) => {
79
+ const r = await sdk.identify(id, traits);
80
+ setIdentity(sdk.identity.current());
81
+ return r;
82
+ }, [sdk]);
83
+ const reset = useMemo(() => () => { sdk.reset(); setIdentity(null); }, [sdk]);
84
+ return { identity, identify, reset };
85
+ }
86
+
87
+ export { createSdk, initSdk, getSdk, setDefaultSdk };