flowgrid-sdk 1.7.7 → 1.7.8
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 +225 -9
- package/dist/flowgrid.min.js +1 -1
- package/dist/flowgrid.min.js.map +1 -1
- package/dist/lib/client/transport.js +5 -0
- package/dist/lib/client/transport.js.map +1 -1
- package/dist/lib/features/analytics/authIntent.d.ts +35 -0
- package/dist/lib/features/analytics/authIntent.js +89 -0
- package/dist/lib/features/analytics/authIntent.js.map +1 -0
- package/dist/lib/flowgrid.d.ts +123 -1
- package/dist/lib/flowgrid.js +183 -2
- package/dist/lib/flowgrid.js.map +1 -1
- package/dist/lib/server/index.d.ts +531 -0
- package/dist/lib/server/index.js +811 -0
- package/dist/lib/server/index.js.map +1 -0
- package/dist/lib/tracking/analytics/identify.d.ts +2 -0
- package/dist/lib/tracking/analytics/identify.js +4 -0
- package/dist/lib/tracking/analytics/identify.js.map +1 -1
- package/dist/lib/utils/identity.d.ts +9 -0
- package/dist/lib/utils/identity.js +14 -0
- package/dist/lib/utils/identity.js.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -62,7 +62,54 @@ Requires Node `>= 18.12`. No peer dependencies.
|
|
|
62
62
|
|
|
63
63
|
## Quick Start
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
FlowGrid is one platform with two runtimes. Initialise **once per runtime** —
|
|
66
|
+
the browser client for everything a visitor does on the page, the server
|
|
67
|
+
client for what happens in your backend — and both feed the same dashboard
|
|
68
|
+
under the same identity:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// lib/flowgrid.client.ts — BROWSER: init once, import everywhere client-side
|
|
72
|
+
import { FlowGrid } from "flowgrid-sdk";
|
|
73
|
+
|
|
74
|
+
export const fg = FlowGrid.init({
|
|
75
|
+
webId: process.env.NEXT_PUBLIC_FLOWGRID_WEB_ID!, // public site id (wx_…)
|
|
76
|
+
apiKey: process.env.NEXT_PUBLIC_FLOWGRID_API_KEY!, // public API key
|
|
77
|
+
|
|
78
|
+
// Who is this? (optional — identifies up-front so the very first events attribute)
|
|
79
|
+
user: { userId: "u_1", email: "alice@example.com", name: "Alice" },
|
|
80
|
+
|
|
81
|
+
// What gets auto-tracked? engagement + replay are on by default;
|
|
82
|
+
// pageviews/sessions/heatmaps/performance come from the script tag install.
|
|
83
|
+
autoTrack: {
|
|
84
|
+
engagement: true,
|
|
85
|
+
replay: { sampleRate: 1.0, maskAllInputs: true },
|
|
86
|
+
passiveIdentity: true, // recognise visitors from forms (PII hashed in-browser)
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// What is the visitor consenting to? (see “Cookie Consent” below —
|
|
90
|
+
// pair with renderConsentBanner() for an opt-in banner in one call)
|
|
91
|
+
consent: { analytics: true, marketing: true },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// lib/flowgrid.server.ts — SERVER (Node ≥ 18 / edge): identity, feature usage, revenue, errors
|
|
97
|
+
import { FlowGridServer } from "flowgrid-sdk/server";
|
|
98
|
+
|
|
99
|
+
export const fgServer = FlowGridServer.init({
|
|
100
|
+
webId: process.env.NEXT_PUBLIC_FLOWGRID_WEB_ID!, // same site id
|
|
101
|
+
apiKey: process.env.FLOWGRID_API_KEY!, // private key — server env only
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
That's the whole setup. Browser calls (`fg.track`, `fg.identifyUser`,
|
|
106
|
+
`fg.cart.add`, …) and server calls (`fgServer.trackAuth`,
|
|
107
|
+
`fgServer.trackFeature`, `fgServer.trackPurchase`, `fgServer.trackError`, …) speak the same wire
|
|
108
|
+
contract, so a user identified in an OAuth callback on the server and the
|
|
109
|
+
same user clicking around in the browser resolve to **one identity** in the
|
|
110
|
+
dashboard.
|
|
111
|
+
|
|
112
|
+
### Unified client (browser)
|
|
66
113
|
|
|
67
114
|
```ts
|
|
68
115
|
import { FlowGrid } from "flowgrid-sdk";
|
|
@@ -202,25 +249,193 @@ to the server-side identity store; the event stream sees hashes.
|
|
|
202
249
|
|
|
203
250
|
## Server-side / Edge Usage
|
|
204
251
|
|
|
205
|
-
|
|
252
|
+
Use the dedicated **`flowgrid-sdk/server`** entry point on the server. It is a
|
|
253
|
+
Node/edge-safe client (global `fetch` only — no `window`, `document`, or
|
|
254
|
+
storage) scoped to what makes sense off-browser: **identity**, **feature
|
|
255
|
+
usage**, **ecommerce / revenue**, and **API / server error tracking**. It speaks the same wire contract
|
|
256
|
+
as the browser SDK, so events land in the same pipes. Works in Node ≥ 18, Bun,
|
|
257
|
+
Deno, Cloudflare Workers, and Vercel Edge/serverless.
|
|
206
258
|
|
|
207
259
|
```ts
|
|
208
|
-
//
|
|
209
|
-
import {
|
|
260
|
+
// lib/flowgrid-server.ts — init once, import anywhere server-side
|
|
261
|
+
import { FlowGridServer } from "flowgrid-sdk/server";
|
|
262
|
+
|
|
263
|
+
export const fg = FlowGridServer.init({
|
|
264
|
+
webId: process.env.NEXT_PUBLIC_WEB_ID!,
|
|
265
|
+
apiKey: process.env.FLOWGRID_API_KEY!, // required on the server
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Identity — auth-level events** (identify a visitor server-side, e.g. in an
|
|
270
|
+
OAuth callback or session endpoint). Pass the browser's visitor id when you
|
|
271
|
+
have the request cookies so server events link to the web session; without it,
|
|
272
|
+
a stable synthetic visitor is derived from the `userId`:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { visitorIdFromCookies } from "flowgrid-sdk/server";
|
|
276
|
+
import { cookies } from "next/headers";
|
|
277
|
+
|
|
278
|
+
const visitorId = visitorIdFromCookies(await cookies());
|
|
279
|
+
|
|
280
|
+
await fg.trackAuth(
|
|
281
|
+
{ event: "login", userId: user.id, email: user.email, method: "google" },
|
|
282
|
+
{ visitorId },
|
|
283
|
+
); // identify + login_completed + active_user ping, in one call
|
|
284
|
+
|
|
285
|
+
// Or the individual operations:
|
|
286
|
+
await fg.identifyUser(user.id, { email: user.email, plan: "pro" }, { visitorId });
|
|
287
|
+
await fg.updateTraits(user.id, { plan: "enterprise" });
|
|
288
|
+
await fg.pingActiveUser(user.id, { email: user.email }); // DAU/WAU/MAU by identity
|
|
289
|
+
await fg.logout(user.id);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Feature usage** from API routes, jobs, and webhooks:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
await fg.trackFeature({ featureId: "csv_export", featureName: "CSV Export", userId: user.id });
|
|
296
|
+
|
|
297
|
+
// Or bind once and use semantic verbs (server twin of fg.defineFeature):
|
|
298
|
+
const teamUpdate = fg.defineFeature("team_update", "Team update", { category: "teams" });
|
|
299
|
+
await teamUpdate.used({ userId: user.id, teamId });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Ecommerce / revenue** — record purchases, refunds, order lifecycle, and
|
|
303
|
+
subscription MRR events from your **payment webhooks**, where the money is
|
|
304
|
+
actually confirmed. Browser purchase events are structurally lossy (ad
|
|
305
|
+
blockers, closed tabs on the checkout redirect); the server is the
|
|
306
|
+
authoritative source for revenue:
|
|
210
307
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
308
|
+
```ts
|
|
309
|
+
// Stripe webhook: checkout.session.completed
|
|
310
|
+
await fg.trackPurchase({
|
|
311
|
+
order: {
|
|
312
|
+
orderId: session.id,
|
|
313
|
+
items, // CartItem[] — same shape as the browser SDK
|
|
314
|
+
subtotal, discountTotal: 0, shippingTotal: 0, taxTotal,
|
|
315
|
+
total: session.amount_total / 100,
|
|
316
|
+
currency: "USD",
|
|
317
|
+
payment: { method: "credit_card", provider: "stripe" },
|
|
318
|
+
},
|
|
319
|
+
userId: session.client_reference_id,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// charge.refunded — keeps net revenue accurate
|
|
323
|
+
await fg.trackRefund({
|
|
324
|
+
orderId, refundId: refund.id,
|
|
325
|
+
amount: refund.amount / 100, currency: "USD",
|
|
326
|
+
reason: "customer_request", isFullRefund: true, userId,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Fulfilment webhooks
|
|
330
|
+
await fg.trackOrderStatus(orderId, "processing", "pending", { userId });
|
|
331
|
+
await fg.trackOrderShipped(orderId, { method: "express", cost: 5, trackingNumber });
|
|
332
|
+
await fg.trackOrderDelivered(orderId);
|
|
333
|
+
|
|
334
|
+
// Subscription / MRR lifecycle — one method, discriminated on `event`:
|
|
335
|
+
await fg.trackSubscription({
|
|
336
|
+
event: "start", // "change" | "cancel" | "renew" | "trial_converted" | "trial_expired"
|
|
337
|
+
subscriptionId: sub.id,
|
|
338
|
+
userId,
|
|
339
|
+
plan: "professional",
|
|
340
|
+
mrr: { amount: 9900, currency: "USD" }, // smallest currency unit
|
|
341
|
+
billingCycle: "monthly",
|
|
342
|
+
trialDays: 14,
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Revenue attribution** — payment webhooks arrive with no cookies, so the
|
|
347
|
+
visitor/session ids ride through the provider's checkout metadata and come
|
|
348
|
+
back out in the webhook. Two legs:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
// LEG 1 — checkout creation: embed the ids from the request cookies.
|
|
352
|
+
import { checkoutAttributionFromCookies } from "flowgrid-sdk/server";
|
|
353
|
+
import { cookies } from "next/headers";
|
|
354
|
+
|
|
355
|
+
// Stripe
|
|
356
|
+
const session = await stripe.checkout.sessions.create({
|
|
357
|
+
// …
|
|
358
|
+
metadata: { ...checkoutAttributionFromCookies(await cookies()) },
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Lemon Squeezy
|
|
362
|
+
const checkout = await createCheckout(storeId, variantId, {
|
|
363
|
+
checkoutData: { custom: { ...checkoutAttributionFromCookies(await cookies()) } },
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// (Creating the checkout client-side instead? Use fg.checkoutAttribution()
|
|
367
|
+
// from the browser SDK — same keys.)
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// LEG 2 — webhook: read the ids back and attach them to the revenue event.
|
|
372
|
+
import { attributionFromMetadata } from "flowgrid-sdk/server";
|
|
373
|
+
|
|
374
|
+
// Stripe checkout.session.completed — unwraps `.metadata` automatically
|
|
375
|
+
const { visitorId, sessionId } = attributionFromMetadata(event.data.object);
|
|
376
|
+
|
|
377
|
+
// Lemon Squeezy order_created — unwraps `.meta.custom_data` automatically
|
|
378
|
+
// const { visitorId, sessionId } = attributionFromMetadata(payload);
|
|
379
|
+
|
|
380
|
+
await fg.trackPurchase({ order, userId }, { visitorId, sessionId });
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
With the real `visitorId` + `sessionId` on the purchase, revenue joins the
|
|
384
|
+
visitor's full journey — the session, landing page and UTM campaign that
|
|
385
|
+
earned it — instead of a synthetic server visitor.
|
|
386
|
+
|
|
387
|
+
**API + server-side error tracking** — errors become `error_event`s with
|
|
388
|
+
route/method/status context; FlowGrid-internal noise is filtered out:
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
try { … } catch (err) {
|
|
392
|
+
await fg.trackError(err, { route: "/api/teams/edit/[teamId]", method: "PATCH", statusCode: 500, userId });
|
|
393
|
+
throw err;
|
|
215
394
|
}
|
|
395
|
+
|
|
396
|
+
// Or wrap a handler — tracks and rethrows, framework behaviour unchanged:
|
|
397
|
+
export const PATCH = fg.withErrorTracking(handler, { route: "/api/teams/edit/[teamId]", method: "PATCH" });
|
|
398
|
+
|
|
399
|
+
// Opt-in process-level capture (Node only; observes, never swallows):
|
|
400
|
+
const detach = fg.captureUncaught();
|
|
216
401
|
```
|
|
217
402
|
|
|
218
|
-
|
|
403
|
+
Like the browser transport, the server client **never throws** for delivery
|
|
404
|
+
problems — every method resolves `{ ok, status?, reason? }`.
|
|
219
405
|
|
|
220
406
|
---
|
|
221
407
|
|
|
222
408
|
## Cookie Consent
|
|
223
409
|
|
|
410
|
+
### What you're asking consent for
|
|
411
|
+
|
|
412
|
+
When a visitor accepts cookies, this is exactly what each category permits
|
|
413
|
+
FlowGrid to do (this is what the SDK enforces, not aspirational copy — each
|
|
414
|
+
category maps to a hard gate in the transport).
|
|
415
|
+
|
|
416
|
+
**Default posture: implied consent.** Every category starts granted, and
|
|
417
|
+
tracking runs out of the box — the "declined" behaviour below only applies
|
|
418
|
+
when you've installed a consent gate (`requireExplicitConsent: true` or the
|
|
419
|
+
opt-in banner) and the visitor actively rejects a category:
|
|
420
|
+
|
|
421
|
+
| Category | Can the visitor decline? | What it covers |
|
|
422
|
+
| ------------- | ------------------------ | -------------- |
|
|
423
|
+
| `necessary` | No — always on | The visitor/session identifiers (`visitor_id` cookie, `fg_session_id`) and the consent cookies themselves (`fg_consent`, `fg_tracking_consent`). Required for the service to function; no behavioural profiling. |
|
|
424
|
+
| `analytics` | Yes | **All event tracking.** Page views, sessions, clicks, forms, searches, feature usage, funnels, performance/Web Vitals, engagement, identity events (`identifyUser`, active-user pings), and **session replay** recordings — plus the device/page context attached to each event (screen size, device type, language, URL, path, title, referrer). Declined → no events leave the browser. |
|
|
425
|
+
| `marketing` | Yes | **Campaign attribution.** The `utm_source/medium/campaign/term/content` and `ref`/`via`/`source` parameters read from the URL and the `__flowgrid_*` landing-page cookies, attached to events as `_utm_*`/`_ref` properties. Declined → events (if analytics is granted) are sent **without** any campaign attribution. |
|
|
426
|
+
| `preferences` | Yes | UI preferences your own app stores (locale, theme). FlowGrid itself stores nothing in this category — it exists so your banner can offer it. |
|
|
427
|
+
|
|
428
|
+
Independent of the banner, visitors sending **Do-Not-Track** or **Global
|
|
429
|
+
Privacy Control** are treated as having declined non-essential tracking by
|
|
430
|
+
default (`respectDNT: true`), and localhost traffic is never tracked unless
|
|
431
|
+
explicitly enabled.
|
|
432
|
+
|
|
433
|
+
Server-side events (`flowgrid-sdk/server`) are outside the browser consent
|
|
434
|
+
gates by design: they represent your backend's own records (auth events,
|
|
435
|
+
API feature usage, server errors) under your contractual/legitimate-interest
|
|
436
|
+
basis. If you want browser consent to extend to server calls, check your
|
|
437
|
+
stored consent state before calling the server client.
|
|
438
|
+
|
|
224
439
|
### No-code (one tag) — recommended
|
|
225
440
|
|
|
226
441
|
Drop a single element on the page and FlowGrid renders a styled, **opt-in**,
|
|
@@ -339,6 +554,7 @@ FlowGridTransport.hasConsent("analytics"); // true
|
|
|
339
554
|
| `flowgrid-sdk/ecommerce` | Ecommerce modules only |
|
|
340
555
|
| `flowgrid-sdk/core` | Core modules (activation, experiments, prompts) |
|
|
341
556
|
| `flowgrid-sdk/consent` | `ConsentManager` + types |
|
|
557
|
+
| `flowgrid-sdk/server` | `FlowGridServer` — Node/edge tracking (identity, feature usage, revenue, errors) |
|
|
342
558
|
| `flowgrid-sdk/types` | Shared TypeScript type definitions |
|
|
343
559
|
|
|
344
560
|
---
|