flowgrid-sdk 1.7.3 → 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.
@@ -0,0 +1,531 @@
1
+ /**
2
+ * @fileoverview FlowGrid Server SDK — Node/edge-safe tracking client.
3
+ * @module server
4
+ *
5
+ * @description
6
+ * Server-side counterpart to the browser `FlowGrid` facade, deliberately
7
+ * scoped to the operations that make sense off-browser:
8
+ *
9
+ * - **Identity** — identify/alias/reset users and auth-level events
10
+ * (signup/login/active-user) from server auth flows.
11
+ * - **Feature usage** — `feature_usage` events from API routes and jobs.
12
+ * - **Ecommerce / revenue** — purchases, refunds, order lifecycle and
13
+ * subscription MRR events from payment webhooks, where money is actually
14
+ * confirmed (immune to ad blockers and closed checkout tabs).
15
+ * - **Error tracking** — API and server-side exceptions as `error_event`s.
16
+ *
17
+ * It speaks the exact same wire contract as the browser SDK (per-op
18
+ * `?call=<type>` routing, suffixed event names like `identify_user`,
19
+ * camelCase properties), so flowgrid-core ingests both identically.
20
+ *
21
+ * Never touches `window`/`document`/`localStorage` and imports no Node
22
+ * built-ins — only global `fetch` (Node ≥ 18) — so it is safe in Next.js
23
+ * API routes and middleware, Express services, workers, and edge runtimes.
24
+ * Like the browser transport, it **never throws** for delivery problems:
25
+ * failures resolve `{ ok: false }` and are debug-noted once per signature.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { FlowGridServer } from "flowgrid-sdk/server";
30
+ *
31
+ * const fg = FlowGridServer.init({
32
+ * webId: process.env.NEXT_PUBLIC_WEB_ID!,
33
+ * apiKey: process.env.FLOWGRID_API_KEY!,
34
+ * });
35
+ *
36
+ * // Identity (auth-level, server-side)
37
+ * await fg.trackAuth({ event: "login", userId: user.id, email: user.email });
38
+ *
39
+ * // Feature usage
40
+ * await fg.trackFeature({ featureId: "team_update", featureName: "Team update", userId: user.id });
41
+ *
42
+ * // Revenue — from a payment webhook (e.g. Stripe checkout.session.completed)
43
+ * await fg.trackPurchase({ order, userId: session.client_reference_id });
44
+ *
45
+ * // API / server error tracking
46
+ * try { … } catch (err) {
47
+ * await fg.trackError(err, { route: "/api/teams/edit", method: "PATCH", statusCode: 500 });
48
+ * throw err;
49
+ * }
50
+ * ```
51
+ */
52
+ import type { CartItem, MoneyValue, Order, ShippingInfo } from "../types";
53
+ export interface FlowGridServerConfig {
54
+ /** Public web/site identifier (e.g. `"wx_abc123"`). Required. */
55
+ webId: string;
56
+ /**
57
+ * Private API key, sent as a Bearer token. Required on the server — there
58
+ * is no `data-site` body-auth fallback for server ingestion.
59
+ */
60
+ apiKey: string;
61
+ /** API base URL. Defaults to `"https://core.flow-grid.xyz"`. */
62
+ endpoint?: string;
63
+ /**
64
+ * Deployment environment stamped on every event as `_environment`
65
+ * (defaults to `process.env.NODE_ENV`). Lets the dashboard separate
66
+ * production traffic from staging/preview.
67
+ */
68
+ environment?: string;
69
+ /** Request timeout in ms (default 10 000). */
70
+ timeoutMs?: number;
71
+ /** Max retries for network failures / 5xx responses (default 2). */
72
+ maxRetries?: number;
73
+ }
74
+ /** Per-call options shared by every server tracking method. */
75
+ export interface ServerCallOptions {
76
+ /**
77
+ * The browser visitor id for the request (read it from the `visitor_id`
78
+ * cookie — see {@link visitorIdFromCookies}; in payment webhooks, from
79
+ * checkout metadata via {@link attributionFromMetadata}). Passing it
80
+ * links server events to the visitor's web session. When omitted, a
81
+ * stable synthetic visitor id is derived from the `userId` so events
82
+ * still attribute to the user; fully anonymous events share one
83
+ * per-process visitor.
84
+ */
85
+ visitorId?: string;
86
+ /**
87
+ * The browser session id — same sources as `visitorId`
88
+ * ({@link sessionIdFromCookies} / {@link attributionFromMetadata}).
89
+ * Carried on ecommerce/revenue events so a purchase joins the exact
90
+ * session (and therefore the campaign/landing page) that produced it.
91
+ */
92
+ sessionId?: string;
93
+ }
94
+ /** Every server tracking method resolves to this — it never rejects. */
95
+ export interface ServerTrackResult {
96
+ ok: boolean;
97
+ /** HTTP status when a response was received. */
98
+ status?: number;
99
+ /** Why the event was not delivered (validation failure, network, …). */
100
+ reason?: string;
101
+ }
102
+ export interface ServerAuthInput {
103
+ event: "login" | "signup";
104
+ userId: string;
105
+ email?: string;
106
+ name?: string;
107
+ method?: string;
108
+ source?: string;
109
+ traits?: Record<string, unknown>;
110
+ }
111
+ export interface ServerFeatureUsageInput {
112
+ featureId: string;
113
+ featureName: string;
114
+ /** Lifecycle action — defaults to `"used"` (Adopted). */
115
+ action?: "viewed" | "used" | "completed" | "abandoned" | string;
116
+ userId?: string;
117
+ category?: string;
118
+ duration?: number;
119
+ success?: boolean;
120
+ properties?: Record<string, unknown>;
121
+ }
122
+ /** Input for {@link FlowGridServer.trackPurchase}. */
123
+ export interface ServerPurchaseInput {
124
+ /** Full order — same {@link Order} shape the browser SDK sends. */
125
+ order: Order;
126
+ /** The purchasing user — required by the ingestion contract. */
127
+ userId: string;
128
+ /**
129
+ * Campaign attribution for the purchase. Server webhooks have no UTM
130
+ * context of their own, so pass what you stored at checkout if you
131
+ * want revenue attributed to a campaign.
132
+ */
133
+ attribution?: {
134
+ source?: string;
135
+ medium?: string;
136
+ campaign?: string;
137
+ affiliate?: string;
138
+ };
139
+ properties?: Record<string, unknown>;
140
+ }
141
+ /** Input for {@link FlowGridServer.trackRefund}. */
142
+ export interface ServerRefundInput {
143
+ /** Original order id. */
144
+ orderId: string;
145
+ /** Refund id (e.g. the Stripe refund id). */
146
+ refundId: string;
147
+ /** Refunded amount (same unit you report order totals in). */
148
+ amount: number;
149
+ /** ISO 4217 currency code. */
150
+ currency: string;
151
+ userId?: string;
152
+ /** Items being refunded (partial refunds). */
153
+ items?: CartItem[];
154
+ reason?: string;
155
+ isFullRefund?: boolean;
156
+ initiatedBy?: "customer" | "merchant" | "system" | string;
157
+ properties?: Record<string, unknown>;
158
+ }
159
+ /**
160
+ * Input for {@link FlowGridServer.trackSubscription} — discriminated on
161
+ * `event`, mirroring the browser `subscriptions.*` methods one-to-one.
162
+ */
163
+ export type ServerSubscriptionInput = {
164
+ event: "start";
165
+ subscriptionId: string;
166
+ userId: string;
167
+ plan: string;
168
+ /** New MRR (amount in the smallest currency unit, e.g. cents). */
169
+ mrr: MoneyValue;
170
+ billingCycle?: "monthly" | "yearly" | string;
171
+ trialDays?: number;
172
+ coupon?: string;
173
+ properties?: Record<string, unknown>;
174
+ } | {
175
+ event: "change";
176
+ subscriptionId: string;
177
+ userId: string;
178
+ previousPlan: string;
179
+ newPlan: string;
180
+ previousMrr?: MoneyValue;
181
+ newMrr?: MoneyValue;
182
+ changeType?: "upgrade" | "downgrade" | string;
183
+ properties?: Record<string, unknown>;
184
+ } | {
185
+ event: "cancel";
186
+ subscriptionId: string;
187
+ userId: string;
188
+ /** Cancellation reason — required by the ingestion contract. */
189
+ reason: string;
190
+ plan?: string;
191
+ /** Churned MRR. */
192
+ lostMrr?: MoneyValue;
193
+ feedback?: string;
194
+ immediate?: boolean;
195
+ properties?: Record<string, unknown>;
196
+ } | {
197
+ event: "renew";
198
+ subscriptionId: string;
199
+ userId: string;
200
+ mrr: MoneyValue;
201
+ properties?: Record<string, unknown>;
202
+ } | {
203
+ event: "trial_converted";
204
+ subscriptionId: string;
205
+ userId: string;
206
+ plan: string;
207
+ mrr: MoneyValue;
208
+ properties?: Record<string, unknown>;
209
+ } | {
210
+ event: "trial_expired";
211
+ subscriptionId: string;
212
+ userId: string;
213
+ properties?: Record<string, unknown>;
214
+ };
215
+ export interface ServerErrorOptions extends ServerCallOptions {
216
+ userId?: string;
217
+ /** Free-form origin label, e.g. `"stripe-webhook"` or a job name. */
218
+ context?: string;
219
+ /** API route the error occurred in, e.g. `"/api/teams/edit/[teamId]"`. */
220
+ route?: string;
221
+ /** HTTP method of the failing request. */
222
+ method?: string;
223
+ /** HTTP status the route responded with (or would have). */
224
+ statusCode?: number;
225
+ /** Marks process-fatal errors (uncaught exceptions). */
226
+ fatal?: boolean;
227
+ properties?: Record<string, unknown>;
228
+ }
229
+ /**
230
+ * A feature bound to its id/name via {@link FlowGridServer.defineFeature}.
231
+ * Server twin of the browser `FeatureHandle` — verbs accept per-call
232
+ * `userId`/`visitorId` since one server process serves many users.
233
+ */
234
+ export interface ServerFeatureHandle {
235
+ readonly featureId: string;
236
+ readonly featureName: string;
237
+ viewed(properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
238
+ used(properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
239
+ completed(properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
240
+ abandoned(properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
241
+ track(action: string, properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
242
+ }
243
+ /**
244
+ * Minimal cookie reader — compatible with Next.js `cookies()` and Nuxt
245
+ * `useCookie()`. Mirrors `ServerCookieReader` in `utils/identity` (kept
246
+ * local so this module stays free of browser-leaning imports).
247
+ */
248
+ export interface ServerCookieSource {
249
+ get(name: string): {
250
+ value: string;
251
+ } | string | undefined | null;
252
+ }
253
+ /**
254
+ * Read the FlowGrid visitor id from a request's cookies so server events
255
+ * link to the visitor's web session. Checks the tracking-script cookie
256
+ * (`visitor_id`) first, then the SDK's own `fg_visitor_id`.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * import { cookies } from "next/headers";
261
+ * const visitorId = visitorIdFromCookies(await cookies());
262
+ * await fg.identifyUser(user.id, { email: user.email }, { visitorId });
263
+ * ```
264
+ */
265
+ export declare function visitorIdFromCookies(source: ServerCookieSource): string | undefined;
266
+ /**
267
+ * Read the FlowGrid session id from a request's cookies. Both trackers
268
+ * mirror it to a cookie (`session_id` for the no-code script,
269
+ * `fg_session_id` for the npm SDK) precisely so checkout-creation routes
270
+ * can read it for revenue attribution.
271
+ */
272
+ export declare function sessionIdFromCookies(source: ServerCookieSource): string | undefined;
273
+ /**
274
+ * The **checkout leg** of the revenue-attribution cycle. Returns
275
+ * `{ flowgrid_visitor_id, flowgrid_session_id }` read from the request
276
+ * cookies, ready to drop into Stripe's `metadata` or Lemon Squeezy's
277
+ * `checkoutData.custom` when you create the checkout. Your payment
278
+ * webhook reads them back with {@link attributionFromMetadata} — that
279
+ * round-trip is what ties the payment to the visitor's session, campaign
280
+ * and journey, since the webhook itself arrives with no cookies.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * // app/api/checkout/route.ts
285
+ * import { cookies } from "next/headers";
286
+ * import { checkoutAttributionFromCookies } from "flowgrid-sdk/server";
287
+ *
288
+ * const session = await stripe.checkout.sessions.create({
289
+ * // …
290
+ * metadata: { ...checkoutAttributionFromCookies(await cookies()) },
291
+ * });
292
+ * ```
293
+ */
294
+ export declare function checkoutAttributionFromCookies(source: ServerCookieSource): {
295
+ flowgrid_visitor_id?: string;
296
+ flowgrid_session_id?: string;
297
+ };
298
+ /**
299
+ * The **webhook leg** of the revenue-attribution cycle: extract the
300
+ * visitor/session ids your checkout route embedded via
301
+ * {@link checkoutAttributionFromCookies} (or the browser's
302
+ * `fg.checkoutAttribution()`). Accepts the metadata map itself, or a
303
+ * provider object containing it — Stripe objects (`.metadata`) and
304
+ * Lemon Squeezy webhook payloads (`.meta.custom_data`) are unwrapped
305
+ * automatically. Recognises `flowgrid_*`, `fg_*` and bare key spellings.
306
+ * Never throws; missing ids come back `undefined`.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * // Stripe: checkout.session.completed
311
+ * const { visitorId, sessionId } = attributionFromMetadata(event.data.object);
312
+ * await fg.trackPurchase({ order, userId }, { visitorId, sessionId });
313
+ *
314
+ * // Lemon Squeezy: order_created
315
+ * const { visitorId, sessionId } = attributionFromMetadata(payload);
316
+ * ```
317
+ */
318
+ export declare function attributionFromMetadata(metadata: unknown): {
319
+ visitorId?: string;
320
+ sessionId?: string;
321
+ };
322
+ export declare class FlowGridServer {
323
+ /** Singleton created by {@link FlowGridServer.init}. */
324
+ private static _instance;
325
+ private readonly webId;
326
+ private readonly endpoint;
327
+ private readonly apiKey;
328
+ private readonly environment?;
329
+ private readonly timeoutMs;
330
+ private readonly maxRetries;
331
+ /** Per-process visitor for events with no user and no explicit visitorId. */
332
+ private anonymousVisitorId;
333
+ /** One quiet debug note per failure signature — mirrors the browser transport. */
334
+ private static failureNoted;
335
+ /**
336
+ * One-line init. Idempotent — calling twice returns the same instance
337
+ * (matching the browser `FlowGrid.init` contract).
338
+ */
339
+ static init(config: FlowGridServerConfig): FlowGridServer;
340
+ /** Returns the singleton created by {@link FlowGridServer.init}, if any. */
341
+ static instance(): FlowGridServer | null;
342
+ /** Reset the singleton — primarily for tests / hot-reload. */
343
+ static reset(): void;
344
+ /** Explicit constructor for multi-tenant setups; most apps use {@link init}. */
345
+ constructor(config: FlowGridServerConfig);
346
+ /**
347
+ * Identify a user server-side (e.g. inside an auth callback). Writes the
348
+ * visitor identity store exactly like the browser `fg.identifyUser`.
349
+ */
350
+ identifyUser(userId: string, traits?: Record<string, unknown>, opts?: ServerCallOptions & {
351
+ anonymousId?: string;
352
+ }): Promise<ServerTrackResult>;
353
+ /** Update traits on an already-identified user. */
354
+ updateTraits(userId: string, traits: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
355
+ /** Alias a previous user id to a new one (e.g. after an ID migration). */
356
+ aliasUser(previousId: string, userId: string, opts?: ServerCallOptions): Promise<ServerTrackResult>;
357
+ /** Reset a user's identity on logout (server twin of `fg.logout`). */
358
+ logout(userId: string, opts?: ServerCallOptions): Promise<ServerTrackResult>;
359
+ /**
360
+ * Record a completed signup (populates the Acquisition Funnel's
361
+ * "Signed up" stage, same as the browser `fg.signup`).
362
+ */
363
+ signup(userId: string, method: string, source?: string, properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
364
+ /** Record a returning-user login (retention signal, not a funnel conversion). */
365
+ login(userId: string, method?: string, properties?: Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
366
+ /**
367
+ * Emit an "active user" signal for DAU/WAU/MAU by identity — server twin
368
+ * of `fg.pingActiveUser` (rides the same `custom_event` pipe).
369
+ */
370
+ pingActiveUser(userId: string, traits?: {
371
+ email?: string;
372
+ name?: string;
373
+ } & Record<string, unknown>, opts?: ServerCallOptions): Promise<ServerTrackResult>;
374
+ /**
375
+ * One-call server-side auth tracking: identify + signup/login event +
376
+ * active-user ping — exact parity with the browser `fg.trackAuth`, for
377
+ * auth flows that resolve on the server (OAuth callbacks, session
378
+ * endpoints, credential logins).
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * // In an auth callback / route handler:
383
+ * await fg.trackAuth(
384
+ * { event: "signup", userId: user.id, email: user.email, method: "google" },
385
+ * { visitorId: visitorIdFromCookies(await cookies()) },
386
+ * );
387
+ * ```
388
+ */
389
+ trackAuth(input: ServerAuthInput, opts?: ServerCallOptions): Promise<ServerTrackResult>;
390
+ /**
391
+ * Track a feature-usage event from the server (API-triggered features,
392
+ * background jobs, webhooks). Same `feature_usage` contract as the
393
+ * browser `fg.track("feature_usage", …)`.
394
+ */
395
+ trackFeature(input: ServerFeatureUsageInput, opts?: ServerCallOptions): Promise<ServerTrackResult>;
396
+ /**
397
+ * Bind a feature's id + name once and get semantic verbs back — server
398
+ * twin of the browser `fg.defineFeature`. Verbs accept per-call
399
+ * `{ userId, visitorId }` since a server process serves many users.
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * const teamUpdate = fg.defineFeature("team_update", "Team update", { category: "teams" });
404
+ * await teamUpdate.used({ userId: user.id });
405
+ * ```
406
+ */
407
+ defineFeature(featureId: string, featureName: string, defOpts?: {
408
+ category?: string;
409
+ }): ServerFeatureHandle;
410
+ /**
411
+ * Track a confirmed purchase from the server. Fire this from your payment
412
+ * webhook (Stripe `checkout.session.completed`, PayPal capture, order
413
+ * service) — the place where money is actually confirmed — instead of the
414
+ * browser thank-you page, which loses orders to ad blockers, closed tabs
415
+ * and interrupted redirects. Same `ecommerce`/`purchase` wire contract as
416
+ * the browser `purchases.track()`, so revenue dashboards see one stream.
417
+ *
418
+ * @example
419
+ * ```ts
420
+ * // Stripe webhook route
421
+ * await fg.trackPurchase({
422
+ * order: {
423
+ * orderId: session.id,
424
+ * items, subtotal, discountTotal: 0, shippingTotal: 0,
425
+ * taxTotal, total: session.amount_total / 100, currency: "USD",
426
+ * payment: { method: "credit_card", provider: "stripe" },
427
+ * },
428
+ * userId: session.client_reference_id,
429
+ * });
430
+ * ```
431
+ */
432
+ trackPurchase(input: ServerPurchaseInput, opts?: ServerCallOptions): Promise<ServerTrackResult>;
433
+ /** Track an order status change from fulfilment systems / order webhooks. */
434
+ trackOrderStatus(orderId: string, newStatus: NonNullable<Order["status"]> | string, previousStatus?: string, opts?: ServerCallOptions & {
435
+ userId?: string;
436
+ }): Promise<ServerTrackResult>;
437
+ /** Track an order shipped event (carrier/fulfilment webhook). */
438
+ trackOrderShipped(orderId: string, shipping: ShippingInfo, opts?: ServerCallOptions & {
439
+ userId?: string;
440
+ }): Promise<ServerTrackResult>;
441
+ /** Track an order delivered event. */
442
+ trackOrderDelivered(orderId: string, deliveredAt?: string, opts?: ServerCallOptions & {
443
+ userId?: string;
444
+ }): Promise<ServerTrackResult>;
445
+ /**
446
+ * Track a processed refund — fire from your payment provider's refund
447
+ * webhook (e.g. Stripe `charge.refunded`) so net revenue stays accurate.
448
+ */
449
+ trackRefund(input: ServerRefundInput, opts?: ServerCallOptions): Promise<ServerTrackResult>;
450
+ /**
451
+ * Track subscription lifecycle / MRR events — start, plan change, cancel,
452
+ * renew, trial conversion and trial expiry — from your billing webhooks.
453
+ * These power the MRR/churn dashboards; billing state lives on your
454
+ * server, so this is the authoritative place to emit them.
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * // customer.subscription.created
459
+ * await fg.trackSubscription({
460
+ * event: "start",
461
+ * subscriptionId: sub.id,
462
+ * userId: customer.metadata.userId,
463
+ * plan: "professional",
464
+ * mrr: { amount: 9900, currency: "USD" },
465
+ * billingCycle: "monthly",
466
+ * });
467
+ *
468
+ * // customer.subscription.deleted
469
+ * await fg.trackSubscription({
470
+ * event: "cancel",
471
+ * subscriptionId: sub.id,
472
+ * userId,
473
+ * reason: sub.cancellation_details?.reason ?? "unknown",
474
+ * lostMrr: { amount: 9900, currency: "USD" },
475
+ * });
476
+ * ```
477
+ */
478
+ trackSubscription(input: ServerSubscriptionInput, opts?: ServerCallOptions): Promise<ServerTrackResult>;
479
+ /**
480
+ * Track a server-side error or exception as an `error_event`. Accepts a
481
+ * thrown value of any shape; pass `route`/`method`/`statusCode` for API
482
+ * errors so the dashboard can break failures down per endpoint.
483
+ *
484
+ * FlowGrid-internal notes (messages starting with `[FlowGrid]`) are
485
+ * filtered out, matching the browser SDK's own error filtering.
486
+ */
487
+ trackError(error: unknown, opts?: ServerErrorOptions): Promise<ServerTrackResult>;
488
+ /**
489
+ * Wrap an async function (API route handler, job, webhook) so any thrown
490
+ * error is tracked before being rethrown — the failure still surfaces to
491
+ * the caller/framework exactly as before.
492
+ *
493
+ * @example
494
+ * ```ts
495
+ * export const PATCH = fg.withErrorTracking(
496
+ * async (req: NextRequest) => { … },
497
+ * { route: "/api/teams/edit/[teamId]", method: "PATCH" },
498
+ * );
499
+ * ```
500
+ */
501
+ withErrorTracking<A extends unknown[], R>(fn: (...args: A) => Promise<R> | R, opts?: Omit<ServerErrorOptions, "fatal">): (...args: A) => Promise<R>;
502
+ /**
503
+ * Opt-in process-level capture: reports `uncaughtException` and
504
+ * `unhandledRejection` as fatal `error_event`s. Observes only — never
505
+ * swallows; Node's default crash behaviour is unchanged. Returns a
506
+ * detach function. No-op outside Node (edge runtimes have no `process.on`).
507
+ */
508
+ captureUncaught(opts?: Omit<ServerErrorOptions, "fatal">): () => void;
509
+ /**
510
+ * Visitor resolution, in preference order:
511
+ * 1. explicit per-call `visitorId` (the real browser visitor — best),
512
+ * 2. stable synthetic id derived from the userId (`fg_srv_<hash>`), so a
513
+ * user's server events always land on one visitor,
514
+ * 3. one per-process anonymous visitor (events with no identity at all).
515
+ */
516
+ private resolveVisitorId;
517
+ /** Context stamped on every server event — parallel to the browser device context. */
518
+ private serverContext;
519
+ /**
520
+ * POST one event to the gateway (`?call=<type>`), with timeout, jittered
521
+ * retry on 5xx/network failure, and the never-throw guarantee.
522
+ */
523
+ private send;
524
+ /**
525
+ * One quiet, non-throwing note per failure signature (same policy as the
526
+ * browser transport): a failed tracking request is FlowGrid's problem,
527
+ * not the host app's — it must never reach the customer's error tracking.
528
+ */
529
+ private noteFailure;
530
+ }
531
+ export default FlowGridServer;