@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 +434 -0
- package/adapters/angular.js +48 -0
- package/adapters/next.js +25 -0
- package/adapters/nuxt.js +38 -0
- package/adapters/react.js +87 -0
- package/adapters/vue.js +67 -0
- package/dist/nurdd-sdk.umd.js +1132 -0
- package/dist/package.json +3 -0
- package/integrations/shopify/README.md +172 -0
- package/integrations/shopify/checkout-pixel.js +63 -0
- package/integrations/shopify/orders-webhook.example.js +69 -0
- package/package.json +87 -0
- package/src/autotrack.js +88 -0
- package/src/deeplinks.js +79 -0
- package/src/device.js +78 -0
- package/src/env.js +21 -0
- package/src/identity.js +57 -0
- package/src/index.js +12 -0
- package/src/logger.js +12 -0
- package/src/queue.js +102 -0
- package/src/sdk.js +342 -0
- package/src/session.js +83 -0
- package/src/storage.js +48 -0
- package/src/transport.js +105 -0
- package/src/utils.js +56 -0
- package/src/utm.js +46 -0
- package/types/angular.d.ts +30 -0
- package/types/index.d.ts +172 -0
- package/types/next.d.ts +10 -0
- package/types/nuxt.d.ts +5 -0
- package/types/react.d.ts +28 -0
- package/types/vue.d.ts +21 -0
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 };
|
package/adapters/next.js
ADDED
|
@@ -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
|
+
}
|
package/adapters/nuxt.js
ADDED
|
@@ -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 };
|