@nebulr-group/bridge-auth-core 0.1.2 → 0.4.0-beta.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.
Files changed (131) hide show
  1. package/dist/billing/bridge-subscription.d.ts +71 -0
  2. package/dist/billing/bridge-subscription.d.ts.map +1 -0
  3. package/dist/billing/bridge-subscription.js +209 -0
  4. package/dist/billing/bridge-subscription.js.map +1 -0
  5. package/dist/billing/entitlements-store.d.ts +61 -0
  6. package/dist/billing/entitlements-store.d.ts.map +1 -0
  7. package/dist/billing/entitlements-store.js +127 -0
  8. package/dist/billing/entitlements-store.js.map +1 -0
  9. package/dist/billing/fetch-billing-state.d.ts +9 -0
  10. package/dist/billing/fetch-billing-state.d.ts.map +1 -0
  11. package/dist/billing/fetch-billing-state.js +22 -0
  12. package/dist/billing/fetch-billing-state.js.map +1 -0
  13. package/dist/billing/lock-signal.d.ts +6 -0
  14. package/dist/billing/lock-signal.d.ts.map +1 -0
  15. package/dist/billing/lock-signal.js +8 -0
  16. package/dist/billing/lock-signal.js.map +1 -0
  17. package/dist/billing/quota-store.d.ts +86 -0
  18. package/dist/billing/quota-store.d.ts.map +1 -0
  19. package/dist/billing/quota-store.js +180 -0
  20. package/dist/billing/quota-store.js.map +1 -0
  21. package/dist/billing/types.d.ts +64 -0
  22. package/dist/billing/types.d.ts.map +1 -0
  23. package/dist/billing/types.js +49 -0
  24. package/dist/billing/types.js.map +1 -0
  25. package/dist/billing/use-bridge.d.ts +131 -0
  26. package/dist/billing/use-bridge.d.ts.map +1 -0
  27. package/dist/billing/use-bridge.js +181 -0
  28. package/dist/billing/use-bridge.js.map +1 -0
  29. package/dist/bridge-auth.d.ts +36 -1
  30. package/dist/bridge-auth.d.ts.map +1 -1
  31. package/dist/bridge-auth.js +57 -3
  32. package/dist/bridge-auth.js.map +1 -1
  33. package/dist/direct-auth.d.ts +1 -0
  34. package/dist/direct-auth.d.ts.map +1 -1
  35. package/dist/direct-auth.js +7 -0
  36. package/dist/direct-auth.js.map +1 -1
  37. package/dist/errors.d.ts +11 -0
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js +14 -0
  40. package/dist/errors.js.map +1 -1
  41. package/dist/flags/attribute-providers.d.ts +229 -0
  42. package/dist/flags/attribute-providers.d.ts.map +1 -0
  43. package/dist/flags/attribute-providers.js +375 -0
  44. package/dist/flags/attribute-providers.js.map +1 -0
  45. package/dist/flags/dev-attribute-provider.d.ts +67 -0
  46. package/dist/flags/dev-attribute-provider.d.ts.map +1 -0
  47. package/dist/flags/dev-attribute-provider.js +200 -0
  48. package/dist/flags/dev-attribute-provider.js.map +1 -0
  49. package/dist/flags/evaluator.d.ts +58 -0
  50. package/dist/flags/evaluator.d.ts.map +1 -0
  51. package/dist/flags/evaluator.js +160 -0
  52. package/dist/flags/evaluator.js.map +1 -0
  53. package/dist/flags/flag.d.ts +199 -0
  54. package/dist/flags/flag.d.ts.map +1 -0
  55. package/dist/flags/flag.js +337 -0
  56. package/dist/flags/flag.js.map +1 -0
  57. package/dist/flags/identity.d.ts +77 -0
  58. package/dist/flags/identity.d.ts.map +1 -0
  59. package/dist/flags/identity.js +134 -0
  60. package/dist/flags/identity.js.map +1 -0
  61. package/dist/flags/index.d.ts +20 -0
  62. package/dist/flags/index.d.ts.map +1 -0
  63. package/dist/flags/index.js +21 -0
  64. package/dist/flags/index.js.map +1 -0
  65. package/dist/flags/operators.d.ts +63 -0
  66. package/dist/flags/operators.d.ts.map +1 -0
  67. package/dist/flags/operators.js +299 -0
  68. package/dist/flags/operators.js.map +1 -0
  69. package/dist/flags/propagation.d.ts +16 -0
  70. package/dist/flags/propagation.d.ts.map +1 -0
  71. package/dist/flags/propagation.js +107 -0
  72. package/dist/flags/propagation.js.map +1 -0
  73. package/dist/flags/realtime.d.ts +328 -0
  74. package/dist/flags/realtime.d.ts.map +1 -0
  75. package/dist/flags/realtime.js +504 -0
  76. package/dist/flags/realtime.js.map +1 -0
  77. package/dist/flags/runtime-mode.d.ts +32 -0
  78. package/dist/flags/runtime-mode.d.ts.map +1 -0
  79. package/dist/flags/runtime-mode.js +84 -0
  80. package/dist/flags/runtime-mode.js.map +1 -0
  81. package/dist/flags/telemetry.d.ts +79 -0
  82. package/dist/flags/telemetry.d.ts.map +1 -0
  83. package/dist/flags/telemetry.js +286 -0
  84. package/dist/flags/telemetry.js.map +1 -0
  85. package/dist/http.d.ts.map +1 -1
  86. package/dist/http.js +11 -1
  87. package/dist/http.js.map +1 -1
  88. package/dist/index.d.ts +14 -2
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +16 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/management-types.d.ts +38 -0
  93. package/dist/management-types.d.ts.map +1 -1
  94. package/dist/route-guard.d.ts.map +1 -1
  95. package/dist/route-guard.js +15 -5
  96. package/dist/route-guard.js.map +1 -1
  97. package/dist/token-manager.d.ts +3 -1
  98. package/dist/token-manager.d.ts.map +1 -1
  99. package/dist/token-manager.js +10 -6
  100. package/dist/token-manager.js.map +1 -1
  101. package/dist/types.d.ts +10 -2
  102. package/dist/types.d.ts.map +1 -1
  103. package/dist/usage/index.d.ts +3 -0
  104. package/dist/usage/index.d.ts.map +1 -0
  105. package/dist/usage/index.js +4 -0
  106. package/dist/usage/index.js.map +1 -0
  107. package/dist/usage/storage/durable-storage.d.ts +47 -0
  108. package/dist/usage/storage/durable-storage.d.ts.map +1 -0
  109. package/dist/usage/storage/durable-storage.js +27 -0
  110. package/dist/usage/storage/durable-storage.js.map +1 -0
  111. package/dist/usage/storage/index.d.ts +7 -0
  112. package/dist/usage/storage/index.d.ts.map +1 -0
  113. package/dist/usage/storage/index.js +28 -0
  114. package/dist/usage/storage/index.js.map +1 -0
  115. package/dist/usage/storage/indexeddb-storage.d.ts +18 -0
  116. package/dist/usage/storage/indexeddb-storage.d.ts.map +1 -0
  117. package/dist/usage/storage/indexeddb-storage.js +159 -0
  118. package/dist/usage/storage/indexeddb-storage.js.map +1 -0
  119. package/dist/usage/storage/node-fs-storage.d.ts +29 -0
  120. package/dist/usage/storage/node-fs-storage.d.ts.map +1 -0
  121. package/dist/usage/storage/node-fs-storage.js +180 -0
  122. package/dist/usage/storage/node-fs-storage.js.map +1 -0
  123. package/dist/usage/storage/noop-storage.d.ts +17 -0
  124. package/dist/usage/storage/noop-storage.d.ts.map +1 -0
  125. package/dist/usage/storage/noop-storage.js +67 -0
  126. package/dist/usage/storage/noop-storage.js.map +1 -0
  127. package/dist/usage/usage-reporter.d.ts +64 -0
  128. package/dist/usage/usage-reporter.d.ts.map +1 -0
  129. package/dist/usage/usage-reporter.js +234 -0
  130. package/dist/usage/usage-reporter.js.map +1 -0
  131. package/package.json +2 -1
@@ -0,0 +1,504 @@
1
+ // SDK realtime client (TBP-150).
2
+ //
3
+ // Auto-discovers the workspace's pub/sub protocol from `GET /realtime/config`
4
+ // (TBP-147), authorizes via `POST /realtime/authorize` (TBP-151), and connects
5
+ // to receive live flag updates + per-user identity changes.
6
+ //
7
+ // Two protocols, one shape:
8
+ // - `centrifugo`: WebSocket to a Centrifugo server using a signed connect token
9
+ // - `appsync`: planned (TBP-148); not implemented here
10
+ //
11
+ // `noop` server-side → realtime is disabled; the SDK falls back to periodic
12
+ // poll or simply doesn't get live updates.
13
+ //
14
+ // Messages received on the workspace channel update the BridgeFlags cache
15
+ // (`upsert` / `remove`). Per-user channel messages are handled via callbacks
16
+ // the framework SDK supplies (token refresh, attribute changes — see TBP-90).
17
+ export class RealtimeClient {
18
+ cfg;
19
+ ws;
20
+ state = 'idle';
21
+ reconnectDelayMs;
22
+ reconnectTimer;
23
+ bridge;
24
+ onUserStateHook;
25
+ onSubscriptionPlanChangedHook;
26
+ onBillingLifecycleHook;
27
+ onQuotaUpdatedHook;
28
+ onEntitlementsChangedHook;
29
+ // Phase 3 (TBP-287/314) — fans `session.snapshot` out to whichever slices
30
+ // the consumer has wired (app.branding, tenant.subscription, etc.).
31
+ onSnapshotHook;
32
+ onOpenHook;
33
+ onCloseHook;
34
+ stopped = false;
35
+ constructor(cfg) {
36
+ const defaultWs = ((url, protocols) =>
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ new globalThis.WebSocket(url, protocols));
39
+ this.cfg = {
40
+ apiBaseUrl: cfg.apiBaseUrl.replace(/\/+$/, ''),
41
+ apiKey: cfg.apiKey,
42
+ appId: cfg.appId,
43
+ workspaceId: cfg.workspaceId,
44
+ userId: cfg.userId,
45
+ enabled: cfg.enabled !== false,
46
+ reconnectBaseMs: cfg.reconnectBaseMs ?? 1000,
47
+ reconnectMaxMs: cfg.reconnectMaxMs ?? 30_000,
48
+ websocketFactory: cfg.websocketFactory ?? defaultWs,
49
+ fetchFn: cfg.fetchFn ?? (typeof fetch !== 'undefined' ? fetch : undefined),
50
+ getAuthToken: cfg.getAuthToken,
51
+ };
52
+ this.reconnectDelayMs = this.cfg.reconnectBaseMs;
53
+ }
54
+ /** Attach to a BridgeFlags instance — flag updates auto-apply to its cache. */
55
+ attach(bridge) {
56
+ this.bridge = bridge;
57
+ }
58
+ /** Register a hook for per-user channel messages. */
59
+ setOnUserState(hook) {
60
+ this.onUserStateHook = hook;
61
+ }
62
+ /**
63
+ * Billing 2.0 US-3 — register a hook for canonical subscription plan-change
64
+ * events. The billing reactive surface (`BridgeSubscription.attach(rt)`)
65
+ * wires this up; framework SDKs typically don't call it directly.
66
+ */
67
+ setOnSubscriptionPlanChanged(hook) {
68
+ this.onSubscriptionPlanChangedHook = hook;
69
+ }
70
+ /**
71
+ * Billing 2.0 US-5+ — register a hook for all canonical lifecycle events
72
+ * (payment.*, subscription.*, dunning.*, entitlements.*). `BridgeSubscription.attach(rt)`
73
+ * wires this; user-level event handlers can also register here via
74
+ * `useBridge().handle({ "payment.failed": ... })`.
75
+ */
76
+ setOnBillingLifecycle(hook) {
77
+ this.onBillingLifecycleHook = hook;
78
+ }
79
+ /**
80
+ * Billing 2.0 US-11 — register a hook for `quota.updated` payloads on the
81
+ * workspace channel. `useBridge().quota(metric)` consumers wire this up
82
+ * so live counter UI reflects server-side ingest without polling.
83
+ */
84
+ setOnQuotaUpdated(hook) {
85
+ this.onQuotaUpdatedHook = hook;
86
+ }
87
+ /**
88
+ * Billing 2.0 US-12 — register a hook for `entitlements.changed` payloads
89
+ * on the workspace channel that carry the full entitlements map.
90
+ * `useBridge().entitlements.can(...)` consumers wire this up so the cache
91
+ * replaces wholesale on every diff.
92
+ *
93
+ * Note: the legacy `BillingLifecycleMessage` `entitlements.changed` kind
94
+ * (signal-only, no payload) keeps firing through `setOnBillingLifecycle`
95
+ * — this hook ONLY fires when the wire payload includes the `entitlements`
96
+ * field. Allows both old and new consumers to coexist during rollout.
97
+ */
98
+ setOnEntitlementsChanged(hook) {
99
+ this.onEntitlementsChangedHook = hook;
100
+ }
101
+ /**
102
+ * Phase 3 (TBP-287/314) — register a hook for `session.snapshot`. The server
103
+ * publishes one on every successful per-user channel subscribe (initial
104
+ * connect AND reconnect). Framework SDKs use this to pre-populate the
105
+ * `bridge.app.branding` / `bridge.tenant.{subscription,entitlements}` /
106
+ * `bridge.user` slices on first paint, eliminating the per-slice REST
107
+ * hydrate round-trips that the legacy bootstrap path required.
108
+ *
109
+ * Composition is fixed (no consumer config). If a later release promotes
110
+ * another slice into the snapshot, that slice's `.load()` becomes a no-op
111
+ * automatically — consumers don't need code changes.
112
+ */
113
+ setOnSnapshot(hook) {
114
+ this.onSnapshotHook = hook;
115
+ }
116
+ /**
117
+ * Register a hook fired when the WebSocket transitions to `'open'` —
118
+ * fires on initial connect AND on every successful reconnect. Framework
119
+ * SDKs use this to re-fire startup tasks (e.g. cache hydration) that
120
+ * may have been missed during an outage.
121
+ */
122
+ setOnOpen(hook) {
123
+ this.onOpenHook = hook;
124
+ }
125
+ /**
126
+ * Register a hook fired when the WebSocket transitions to `'closed'` —
127
+ * use for surfacing connection status to consumers (e.g. an "offline"
128
+ * indicator). Fires on intentional close as well; check `getState()`
129
+ * if you need to distinguish.
130
+ */
131
+ setOnClose(hook) {
132
+ this.onCloseHook = hook;
133
+ }
134
+ /**
135
+ * Re-run the authorize step against the current `getAuthToken()` value and
136
+ * re-open the WebSocket. Used by the framework SDK on every token rotation
137
+ * where the userId is unchanged but the JWT value rotated (post-refresh).
138
+ *
139
+ * Without this, the existing connection keeps riding the OLD token until
140
+ * Centrifugo's own connection-token TTL drops it — a strictly-larger
141
+ * blast-radius window than necessary.
142
+ *
143
+ * Behavior:
144
+ * - Disabled / stopped → no-op.
145
+ * - Mid-`connecting` → no-op; the in-flight authorize() reads the current
146
+ * token by closure, so it will already pick up the new value.
147
+ * - Open → drop the ws ref (so the old socket's onclose treats itself as
148
+ * stale via the identity guard in `openWebSocket`), close it with
149
+ * `1000 / sdk.reauthorize`, reset backoff, then `start()` immediately.
150
+ */
151
+ async reauthorize() {
152
+ if (!this.cfg.enabled || this.stopped)
153
+ return;
154
+ if (this.state === 'connecting')
155
+ return;
156
+ if (this.reconnectTimer) {
157
+ clearTimeout(this.reconnectTimer);
158
+ this.reconnectTimer = undefined;
159
+ }
160
+ this.reconnectDelayMs = this.cfg.reconnectBaseMs;
161
+ if (this.state === 'open' && this.ws) {
162
+ const oldWs = this.ws;
163
+ this.ws = undefined;
164
+ this.state = 'closed';
165
+ try {
166
+ oldWs.close(1000, 'sdk.reauthorize');
167
+ }
168
+ catch {
169
+ // ignore
170
+ }
171
+ }
172
+ await this.start();
173
+ }
174
+ /**
175
+ * Phase 2 (TBP-307) — Set/update the appId after initial start. Framework
176
+ * SDKs call this when the app context first lands (e.g. after the first
177
+ * authorize round-trip exposes the JWT `aid` claim). Triggers a reconnect
178
+ * so the new `app:<appId>` channel is included in the next authorize.
179
+ */
180
+ setAppId(appId) {
181
+ if (this.cfg.appId === appId)
182
+ return;
183
+ this.cfg.appId = appId;
184
+ if (this.state === 'open' && this.ws) {
185
+ this.ws.close(1000, 'sdk.setAppId');
186
+ // onclose → scheduleReconnect → start() picks up updated channelsToSubscribe()
187
+ }
188
+ }
189
+ /**
190
+ * Phase 2 (TBP-307) — Set/update the workspaceId after initial start. Used
191
+ * by framework SDKs when the tenant context lands or changes (workspace
192
+ * switcher, tenant join/leave). Triggers a reconnect.
193
+ */
194
+ setWorkspaceId(workspaceId) {
195
+ if (this.cfg.workspaceId === workspaceId)
196
+ return;
197
+ this.cfg.workspaceId = workspaceId;
198
+ if (this.state === 'open' && this.ws) {
199
+ this.ws.close(1000, 'sdk.setWorkspaceId');
200
+ }
201
+ }
202
+ /**
203
+ * Update the userId after initial start — used by the framework SDK to
204
+ * subscribe to the per-user channel when the user logs in post-bootstrap.
205
+ * Triggers a reconnect so the new channel is included in the next authorize.
206
+ */
207
+ setUserId(userId) {
208
+ if (this.cfg.userId === userId)
209
+ return;
210
+ this.cfg.userId = userId;
211
+ if (this.state === 'open' && this.ws) {
212
+ this.ws.close(1000, 'sdk.setUserId');
213
+ // onclose fires → scheduleReconnect → start() picks up updated channelsToSubscribe()
214
+ }
215
+ }
216
+ /** Begin connecting. Idempotent. */
217
+ async start() {
218
+ if (!this.cfg.enabled || this.stopped)
219
+ return;
220
+ if (this.state !== 'idle' && this.state !== 'closed')
221
+ return;
222
+ this.state = 'connecting';
223
+ try {
224
+ const serverConfig = await this.fetchServerConfig();
225
+ if (serverConfig.kind === 'noop' || !serverConfig.endpoint) {
226
+ this.state = 'closed';
227
+ return;
228
+ }
229
+ if (serverConfig.kind !== 'centrifugo') {
230
+ // appsync impl is deferred (TBP-148). Silently no-op for now.
231
+ this.state = 'closed';
232
+ return;
233
+ }
234
+ const channels = this.channelsToSubscribe();
235
+ const auth = await this.authorize(channels);
236
+ this.openWebSocket(serverConfig.endpoint, auth);
237
+ }
238
+ catch {
239
+ this.state = 'closed';
240
+ this.scheduleReconnect();
241
+ }
242
+ }
243
+ /** Close the connection. Idempotent. */
244
+ async stop() {
245
+ this.stopped = true;
246
+ if (this.reconnectTimer) {
247
+ clearTimeout(this.reconnectTimer);
248
+ this.reconnectTimer = undefined;
249
+ }
250
+ if (this.ws) {
251
+ try {
252
+ this.ws.close(1000, 'sdk.stop');
253
+ }
254
+ catch {
255
+ // ignore
256
+ }
257
+ this.ws = undefined;
258
+ }
259
+ this.state = 'closed';
260
+ }
261
+ /** Read connection state. */
262
+ getState() {
263
+ return this.state;
264
+ }
265
+ /**
266
+ * Channels this client subscribes to — the three canonical channels:
267
+ * - `app:<appId>` — flag mutations, app config (app-scoped)
268
+ * - `workspace:<wsId>` — subscription, quota, entitlement (tenant-scoped)
269
+ * - `user:<userId>` — user-state, role/attr change (user-scoped)
270
+ *
271
+ * Each id is optional; the SDK skips the channel if its id isn't configured.
272
+ * The anonymous-only standalone case ends up with just `app:<appId>`.
273
+ */
274
+ channelsToSubscribe() {
275
+ const out = [];
276
+ if (this.cfg.appId)
277
+ out.push(`app:${this.cfg.appId}`);
278
+ if (this.cfg.workspaceId)
279
+ out.push(`workspace:${this.cfg.workspaceId}`);
280
+ if (this.cfg.userId)
281
+ out.push(`user:${this.cfg.userId}`);
282
+ return out;
283
+ }
284
+ // ── private ───────────────────────────────────────────────────────────────
285
+ async fetchServerConfig() {
286
+ const res = await this.cfg.fetchFn(`${this.cfg.apiBaseUrl}/realtime/config`, {
287
+ method: 'GET',
288
+ headers: { 'x-api-key': this.cfg.apiKey },
289
+ });
290
+ if (!res.ok) {
291
+ throw new Error(`realtime config fetch failed: ${res.status}`);
292
+ }
293
+ return (await res.json());
294
+ }
295
+ async authorize(channels) {
296
+ const token = this.cfg.getAuthToken?.() ?? this.cfg.apiKey;
297
+ const res = await this.cfg.fetchFn(`${this.cfg.apiBaseUrl}/realtime/authorize`, {
298
+ method: 'POST',
299
+ headers: {
300
+ Authorization: `Bearer ${token}`,
301
+ 'Content-Type': 'application/json',
302
+ },
303
+ body: JSON.stringify({ channels }),
304
+ });
305
+ if (!res.ok) {
306
+ throw new Error(`realtime authorize failed: ${res.status}`);
307
+ }
308
+ return (await res.json());
309
+ }
310
+ openWebSocket(endpoint, auth) {
311
+ const ws = this.cfg.websocketFactory(endpoint);
312
+ this.ws = ws;
313
+ ws.onopen = () => {
314
+ if (this.ws !== ws)
315
+ return;
316
+ this.state = 'open';
317
+ this.reconnectDelayMs = this.cfg.reconnectBaseMs;
318
+ // Send connect with the signed token + channels. Centrifugo expects a
319
+ // command frame like `{ "connect": { "token": "..." }, "id": 1 }` and
320
+ // separate subscribe frames per channel. For v1 we send one connect
321
+ // and let the server-side token's `channels` claim handle subscription.
322
+ try {
323
+ ws.send(JSON.stringify({ id: 1, connect: { token: auth.signedToken } }));
324
+ }
325
+ catch {
326
+ // ignore
327
+ }
328
+ try {
329
+ this.onOpenHook?.();
330
+ }
331
+ catch {
332
+ // hook errors must not break the connection
333
+ }
334
+ };
335
+ ws.onmessage = (ev) => {
336
+ if (this.ws !== ws)
337
+ return;
338
+ // Centrifugo v5 JSON protocol keepalive: the server periodically sends
339
+ // an empty `{}` frame and expects an empty `{}` reply. Without this
340
+ // echo the server closes the connection on its pong-timeout and the
341
+ // SDK ends up in a perpetual reconnect loop.
342
+ if (typeof ev.data === 'string' && ev.data === '{}') {
343
+ try {
344
+ ws.send('{}');
345
+ }
346
+ catch {
347
+ // ignore — onclose will pick up a broken socket
348
+ }
349
+ return;
350
+ }
351
+ try {
352
+ const parsed = parseMessage(ev.data);
353
+ if (parsed)
354
+ this.handleMessage(parsed);
355
+ }
356
+ catch {
357
+ // ignore malformed
358
+ }
359
+ };
360
+ ws.onclose = () => {
361
+ // Identity guard — if we've already replaced this ws (e.g. via
362
+ // reauthorize() dropping the ref before close), the late-firing onclose
363
+ // is from a stale socket. Don't flap state or fire hooks.
364
+ if (this.ws !== ws)
365
+ return;
366
+ this.state = 'closed';
367
+ try {
368
+ this.onCloseHook?.();
369
+ }
370
+ catch {
371
+ // hook errors must not block reconnect scheduling
372
+ }
373
+ this.scheduleReconnect();
374
+ };
375
+ ws.onerror = () => {
376
+ // Let onclose handle reconnect — errors are noisy but not actionable.
377
+ };
378
+ }
379
+ handleMessage(msg) {
380
+ switch (msg.kind) {
381
+ case 'flag.updated':
382
+ if (this.bridge && msg.flag)
383
+ this.bridge.upsert(msg.flag);
384
+ break;
385
+ case 'flag.removed':
386
+ if (this.bridge && msg.key)
387
+ this.bridge.remove(msg.key);
388
+ break;
389
+ case 'user.state_changed':
390
+ if (this.onUserStateHook) {
391
+ try {
392
+ this.onUserStateHook(msg);
393
+ }
394
+ catch {
395
+ // ignore
396
+ }
397
+ }
398
+ break;
399
+ case 'subscription.plan_changed':
400
+ if (this.onSubscriptionPlanChangedHook) {
401
+ try {
402
+ this.onSubscriptionPlanChangedHook(msg);
403
+ }
404
+ catch {
405
+ // ignore
406
+ }
407
+ }
408
+ break;
409
+ case 'payment.failed':
410
+ case 'payment.succeeded':
411
+ case 'subscription.created':
412
+ case 'subscription.updated':
413
+ case 'subscription.canceled':
414
+ case 'subscription.reactivated':
415
+ case 'subscription.trial_started':
416
+ case 'subscription.trial_ending_soon':
417
+ case 'subscription.trial_converted':
418
+ case 'subscription.trial_expired':
419
+ case 'dunning.entered':
420
+ case 'dunning.retry_scheduled':
421
+ case 'dunning.recovered':
422
+ case 'dunning.exhausted':
423
+ if (this.onBillingLifecycleHook) {
424
+ try {
425
+ this.onBillingLifecycleHook(msg);
426
+ }
427
+ catch {
428
+ // ignore
429
+ }
430
+ }
431
+ break;
432
+ case 'entitlements.changed':
433
+ // bridge-api always publishes the payload-carrying shape (US-12);
434
+ // the pre-prod signal-only fallback was removed at milestone close-out.
435
+ if (this.onEntitlementsChangedHook) {
436
+ try {
437
+ this.onEntitlementsChangedHook(msg);
438
+ }
439
+ catch {
440
+ // ignore
441
+ }
442
+ }
443
+ break;
444
+ case 'quota.updated':
445
+ if (this.onQuotaUpdatedHook) {
446
+ try {
447
+ this.onQuotaUpdatedHook(msg);
448
+ }
449
+ catch {
450
+ // ignore
451
+ }
452
+ }
453
+ break;
454
+ case 'session.snapshot':
455
+ // Phase 3 (TBP-287/314) — first-paint snapshot. Defensive check on
456
+ // `data` because the wire shape is deeper than the other kinds and
457
+ // a partial server might omit it; we never want to call the hook
458
+ // with `undefined`.
459
+ if (this.onSnapshotHook && msg.data) {
460
+ try {
461
+ this.onSnapshotHook(msg);
462
+ }
463
+ catch {
464
+ // ignore
465
+ }
466
+ }
467
+ break;
468
+ }
469
+ }
470
+ scheduleReconnect() {
471
+ if (this.stopped)
472
+ return;
473
+ if (this.reconnectTimer)
474
+ return;
475
+ this.reconnectTimer = setTimeout(() => {
476
+ this.reconnectTimer = undefined;
477
+ this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, this.cfg.reconnectMaxMs);
478
+ void this.start();
479
+ }, this.reconnectDelayMs);
480
+ if (this.reconnectTimer?.unref)
481
+ this.reconnectTimer.unref();
482
+ }
483
+ }
484
+ function parseMessage(raw) {
485
+ if (typeof raw !== 'string')
486
+ return null;
487
+ let parsed;
488
+ try {
489
+ parsed = JSON.parse(raw);
490
+ }
491
+ catch {
492
+ return null;
493
+ }
494
+ // Centrifugo wraps publish data in `{ push: { channel, pub: { data: {...} } } }`.
495
+ // We accept both that shape and a flat `{ kind: ... }` shape so the same
496
+ // client works with simpler transports too.
497
+ const data = parsed?.push?.pub?.data ??
498
+ parsed?.pub?.data ??
499
+ parsed;
500
+ if (!data || typeof data !== 'object' || typeof data.kind !== 'string')
501
+ return null;
502
+ return data;
503
+ }
504
+ //# sourceMappingURL=realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../src/flags/realtime.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,4DAA4D;AAC5D,EAAE;AACF,4BAA4B;AAC5B,kFAAkF;AAClF,yDAAyD;AACzD,EAAE;AACF,4EAA4E;AAC5E,2CAA2C;AAC3C,EAAE;AACF,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAuO9E,MAAM,OAAO,cAAc;IACR,GAAG,CASlB;IACM,EAAE,CAAiB;IACnB,KAAK,GAAoB,MAAM,CAAC;IAChC,gBAAgB,CAAS;IACzB,cAAc,CAAiC;IAC/C,MAAM,CAAe;IACrB,eAAe,CAAmC;IAClD,6BAA6B,CAAiD;IAC9E,sBAAsB,CAA0C;IAChE,kBAAkB,CAAsC;IACxD,yBAAyB,CAA6C;IAC9E,0EAA0E;IAC1E,oEAAoE;IAC5D,cAAc,CAAyC;IACvD,UAAU,CAAc;IACxB,WAAW,CAAc;IACzB,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,GAAyB;QACnC,MAAM,SAAS,GAAG,CAAC,CAAC,GAAW,EAAE,SAA6B,EAAE,EAAE;QAChE,8DAA8D;QAC9D,IAAK,UAAkB,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAGjC,CAAC;QACnB,IAAI,CAAC,GAAG,GAAG;YACT,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,KAAK;YAC9B,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,IAAI;YAC5C,cAAc,EAAE,GAAG,CAAC,cAAc,IAAI,MAAM;YAC5C,gBAAgB,EAAE,GAAG,CAAC,gBAAgB,IAAI,SAAS;YACnD,OAAO,EAAE,GAAG,CAAC,OAAO,IAAK,CAAC,OAAO,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAkB;YAC5F,YAAY,EAAE,GAAG,CAAC,YAAY;SAC/B,CAAC;QACF,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC;IACnD,CAAC;IAED,+EAA+E;IAC/E,MAAM,CAAC,MAAmB;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,qDAAqD;IACrD,cAAc,CAAC,IAAqC;QAClD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,4BAA4B,CAAC,IAAmD;QAC9E,IAAI,CAAC,6BAA6B,GAAG,IAAI,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,qBAAqB,CAAC,IAA4C;QAChE,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,IAAwC;QACxD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;IACjC,CAAC;IAED;;;;;;;;;;OAUG;IACH,wBAAwB,CAAC,IAA+C;QACtE,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;IACxC,CAAC;IAED;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,IAA2C;QACvD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,IAAgB;QACxB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,IAAgB;QACzB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,WAAW;QACf,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC9C,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY;YAAE,OAAO;QACxC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC;QACjD,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;YACpB,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;YACtB,IAAI,CAAC;gBACH,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,KAAyB;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,KAAK;YAAE,OAAO;QACrC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;QACvB,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YACpC,+EAA+E;QACjF,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,WAA+B;QAC5C,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,KAAK,WAAW;YAAE,OAAO;QACjD,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC;QACnC,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,MAA0B;QAClC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO;QACvC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;YACrC,qFAAqF;QACvF,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC9C,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO;QAE7D,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACpD,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;gBAC3D,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;gBACtB,OAAO;YACT,CAAC;YACD,IAAI,YAAY,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBACvC,8DAA8D;gBAC9D,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;gBACtB,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;YACtB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,wCAAwC;IACxC,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAClC,CAAC;QACD,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;IACxB,CAAC;IAED,6BAA6B;IAC7B,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;;;;;;OAQG;IACH,mBAAmB;QACjB,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK;YAAE,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW;YAAE,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACzD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,6EAA6E;IAErE,KAAK,CAAC,iBAAiB;QAC7B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,kBAAkB,EAAE;YAC3E,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE;SAC1C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;IACpD,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,QAAkB;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;QAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,qBAAqB,EAAE;YAC9E,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;SACnC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsB,CAAC;IACjD,CAAC;IAEO,aAAa,CAAC,QAAgB,EAAE,IAAuB;QAC7D,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,EAAE,CAAC,MAAM,GAAG,GAAG,EAAE;YACf,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;gBAAE,OAAO;YAC3B,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;YACpB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC;YACjD,sEAAsE;YACtE,sEAAsE;YACtE,oEAAoE;YACpE,wEAAwE;YACxE,IAAI,CAAC;gBACH,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;YAC3E,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,4CAA4C;YAC9C,CAAC;QACH,CAAC,CAAC;QACF,EAAE,CAAC,SAAS,GAAG,CAAC,EAAE,EAAE,EAAE;YACpB,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;gBAAE,OAAO;YAC3B,uEAAuE;YACvE,oEAAoE;YACpE,oEAAoE;YACpE,6CAA6C;YAC7C,IAAI,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ,IAAI,EAAE,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACpD,IAAI,CAAC;oBACH,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC;gBAAC,MAAM,CAAC;oBACP,gDAAgD;gBAClD,CAAC;gBACD,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;gBACrC,IAAI,MAAM;oBAAE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,mBAAmB;YACrB,CAAC;QACH,CAAC,CAAC;QACF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,+DAA+D;YAC/D,wEAAwE;YACxE,0DAA0D;YAC1D,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;gBAAE,OAAO;YAC3B,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;YACtB,IAAI,CAAC;gBACH,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,kDAAkD;YACpD,CAAC;YACD,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC,CAAC;QACF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;YAChB,sEAAsE;QACxE,CAAC,CAAC;IACJ,CAAC;IAEO,aAAa,CAAC,GAAoB;QACxC,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,cAAc;gBACjB,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC1D,MAAM;YACR,KAAK,cAAc;gBACjB,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACxD,MAAM;YACR,KAAK,oBAAoB;gBACvB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;oBACzB,IAAI,CAAC;wBACH,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;oBAC5B,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,KAAK,2BAA2B;gBAC9B,IAAI,IAAI,CAAC,6BAA6B,EAAE,CAAC;oBACvC,IAAI,CAAC;wBACH,IAAI,CAAC,6BAA6B,CAAC,GAAG,CAAC,CAAC;oBAC1C,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,KAAK,gBAAgB,CAAC;YACtB,KAAK,mBAAmB,CAAC;YACzB,KAAK,sBAAsB,CAAC;YAC5B,KAAK,sBAAsB,CAAC;YAC5B,KAAK,uBAAuB,CAAC;YAC7B,KAAK,0BAA0B,CAAC;YAChC,KAAK,4BAA4B,CAAC;YAClC,KAAK,gCAAgC,CAAC;YACtC,KAAK,8BAA8B,CAAC;YACpC,KAAK,4BAA4B,CAAC;YAClC,KAAK,iBAAiB,CAAC;YACvB,KAAK,yBAAyB,CAAC;YAC/B,KAAK,mBAAmB,CAAC;YACzB,KAAK,mBAAmB;gBACtB,IAAI,IAAI,CAAC,sBAAsB,EAAE,CAAC;oBAChC,IAAI,CAAC;wBACH,IAAI,CAAC,sBAAsB,CAAC,GAA8B,CAAC,CAAC;oBAC9D,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,KAAK,sBAAsB;gBACzB,kEAAkE;gBAClE,wEAAwE;gBACxE,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;oBACnC,IAAI,CAAC;wBACH,IAAI,CAAC,yBAAyB,CAAC,GAAiC,CAAC,CAAC;oBACpE,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,KAAK,eAAe;gBAClB,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;oBAC5B,IAAI,CAAC;wBACH,IAAI,CAAC,kBAAkB,CAAC,GAA0B,CAAC,CAAC;oBACtD,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM;YACR,KAAK,kBAAkB;gBACrB,mEAAmE;gBACnE,mEAAmE;gBACnE,iEAAiE;gBACjE,oBAAoB;gBACpB,IAAI,IAAI,CAAC,cAAc,IAAK,GAA8B,CAAC,IAAI,EAAE,CAAC;oBAChE,IAAI,CAAC;wBACH,IAAI,CAAC,cAAc,CAAC,GAA6B,CAAC,CAAC;oBACrD,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBACD,MAAM;QACV,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QAChC,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;YAChC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YACrF,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1B,IAAK,IAAI,CAAC,cAAsB,EAAE,KAAK;YAAG,IAAI,CAAC,cAAsB,CAAC,KAAK,EAAE,CAAC;IAChF,CAAC;CACF;AAED,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,kFAAkF;IAClF,yEAAyE;IACzE,4CAA4C;IAC5C,MAAM,IAAI,GACR,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI;QACvB,MAAM,EAAE,GAAG,EAAE,IAAI;QACjB,MAAM,CAAC;IACT,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpF,OAAO,IAAuB,CAAC;AACjC,CAAC"}
@@ -0,0 +1,32 @@
1
+ export type BridgeRuntimeMode = 'channel' | 'pull';
2
+ export interface PullCacheOptions {
3
+ /** TTL for cached values in ms. Default 30000 (30s). */
4
+ ttlMs?: number;
5
+ /** Optional override for the default "now" clock — useful in tests. */
6
+ now?: () => number;
7
+ }
8
+ /**
9
+ * Tiny TTL-bounded read cache for pull-mode SDKs.
10
+ *
11
+ * Semantics:
12
+ * - `get(key, fetcher)` returns the cached value if not expired; otherwise
13
+ * awaits `fetcher()` to populate and returns the fresh value.
14
+ * - Concurrent `get(key, ...)` calls share the in-flight promise (no
15
+ * thundering-herd against the upstream REST endpoint).
16
+ * - `invalidate(key?)` clears a specific key or the entire cache.
17
+ *
18
+ * Best-effort: a fetcher rejection clears the in-flight promise so the next
19
+ * `get()` retries instead of returning a stale value forever.
20
+ */
21
+ export declare class BridgePullCache {
22
+ private readonly _entries;
23
+ private readonly _ttlMs;
24
+ private readonly _now;
25
+ constructor(opts?: PullCacheOptions);
26
+ get<T>(key: string, fetcher: () => Promise<T>): Promise<T>;
27
+ /** Force a re-fetch on the next get(). Pass no args to clear all keys. */
28
+ invalidate(key?: string): void;
29
+ /** Number of cached entries currently held (test/diagnostic). */
30
+ size(): number;
31
+ }
32
+ //# sourceMappingURL=runtime-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-mode.d.ts","sourceRoot":"","sources":["../../src/flags/runtime-mode.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAQD;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;gBAExB,IAAI,CAAC,EAAE,gBAAgB;IAK7B,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA6BhE,0EAA0E;IAC1E,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ9B,iEAAiE;IACjE,IAAI,IAAI,MAAM;CAGf"}
@@ -0,0 +1,84 @@
1
+ // Phase 6 (TBP-290/340) — runtime-mode abstraction.
2
+ //
3
+ // `BridgeRuntimeMode` is orthogonal to `BridgeFlagsMode` (which governs the
4
+ // flag-eval semantics on backend vs frontend). Runtime mode governs HOW the
5
+ // SDK stays fresh:
6
+ //
7
+ // 'channel' — open a WebSocket and consume push events (live updates).
8
+ // Use for long-running processes: SvelteKit SSR servers, NestJS
9
+ // services, Express APIs serving many users from one process.
10
+ //
11
+ // 'pull' — never open a WebSocket. Every read may hit REST with a small
12
+ // TTL cache to deduplicate close-in-time reads. Use for
13
+ // ephemeral runtimes: cron jobs, serverless functions, webhook
14
+ // handlers, CLI scripts.
15
+ //
16
+ // In 'pull' mode `bridge.events.handle()` is a no-op (push events don't exist
17
+ // — for server-side event reactions, use Bridge webhooks instead).
18
+ //
19
+ // In 'channel' mode `bridge.refresh()` is supported but rarely needed (the
20
+ // channel keeps state fresh automatically); in 'pull' mode `bridge.refresh()`
21
+ // explicitly invalidates the cache so the next read re-fetches.
22
+ /**
23
+ * Tiny TTL-bounded read cache for pull-mode SDKs.
24
+ *
25
+ * Semantics:
26
+ * - `get(key, fetcher)` returns the cached value if not expired; otherwise
27
+ * awaits `fetcher()` to populate and returns the fresh value.
28
+ * - Concurrent `get(key, ...)` calls share the in-flight promise (no
29
+ * thundering-herd against the upstream REST endpoint).
30
+ * - `invalidate(key?)` clears a specific key or the entire cache.
31
+ *
32
+ * Best-effort: a fetcher rejection clears the in-flight promise so the next
33
+ * `get()` retries instead of returning a stale value forever.
34
+ */
35
+ export class BridgePullCache {
36
+ _entries = new Map();
37
+ _ttlMs;
38
+ _now;
39
+ constructor(opts) {
40
+ this._ttlMs = opts?.ttlMs ?? 30_000;
41
+ this._now = opts?.now ?? (() => Date.now());
42
+ }
43
+ async get(key, fetcher) {
44
+ const existing = this._entries.get(key);
45
+ if (existing) {
46
+ if (existing.inflight)
47
+ return existing.inflight;
48
+ if (existing.expiresAt > this._now())
49
+ return existing.value;
50
+ }
51
+ const inflight = fetcher()
52
+ .then((v) => {
53
+ this._entries.set(key, {
54
+ value: v,
55
+ expiresAt: this._now() + this._ttlMs,
56
+ });
57
+ return v;
58
+ })
59
+ .catch((err) => {
60
+ // Clear so the next call retries instead of returning stale forever.
61
+ this._entries.delete(key);
62
+ throw err;
63
+ });
64
+ this._entries.set(key, {
65
+ value: existing?.value,
66
+ expiresAt: 0,
67
+ inflight,
68
+ });
69
+ return inflight;
70
+ }
71
+ /** Force a re-fetch on the next get(). Pass no args to clear all keys. */
72
+ invalidate(key) {
73
+ if (key === undefined) {
74
+ this._entries.clear();
75
+ return;
76
+ }
77
+ this._entries.delete(key);
78
+ }
79
+ /** Number of cached entries currently held (test/diagnostic). */
80
+ size() {
81
+ return this._entries.size;
82
+ }
83
+ }
84
+ //# sourceMappingURL=runtime-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-mode.js","sourceRoot":"","sources":["../../src/flags/runtime-mode.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,mBAAmB;AACnB,EAAE;AACF,yEAAyE;AACzE,8EAA8E;AAC9E,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,sEAAsE;AACtE,6EAA6E;AAC7E,uCAAuC;AACvC,EAAE;AACF,8EAA8E;AAC9E,mEAAmE;AACnE,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,gEAAgE;AAiBhE;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,eAAe;IACT,QAAQ,GAAG,IAAI,GAAG,EAA+B,CAAC;IAClD,MAAM,CAAS;IACf,IAAI,CAAe;IAEpC,YAAY,IAAuB;QACjC,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,KAAK,IAAI,MAAM,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,OAAyB;QACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAA8B,CAAC;QACrE,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,QAAQ;gBAAE,OAAO,QAAQ,CAAC,QAAQ,CAAC;YAChD,IAAI,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE;gBAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;QAC9D,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,EAAE;aACvB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;YACV,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE;gBACrB,KAAK,EAAE,CAAC;gBACR,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM;aACrC,CAAC,CAAC;YACH,OAAO,CAAC,CAAC;QACX,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,qEAAqE;YACrE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,GAAG,CAAC;QACZ,CAAC,CAAC,CAAC;QAEL,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE;YACrB,KAAK,EAAE,QAAQ,EAAE,KAAU;YAC3B,SAAS,EAAE,CAAC;YACZ,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,0EAA0E;IAC1E,UAAU,CAAC,GAAY;QACrB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,iEAAiE;IACjE,IAAI;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;CACF"}