flowgrid-sdk 1.7.7 → 1.7.10

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