@barricador/react-client 0.2.0 → 0.3.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.
@@ -5,6 +5,7 @@ export interface BarricadorProviderProps {
5
5
  user: UserContext;
6
6
  baseUrl?: string;
7
7
  streaming?: boolean;
8
+ pollIntervalMs?: number;
8
9
  telemetry?: boolean;
9
10
  flushIntervalMs?: number;
10
11
  /** Optionally gate children until the first evaluation completes. */
@@ -17,4 +18,4 @@ export interface BarricadorProviderProps {
17
18
  * render immediately and flags resolve to their fallbacks until the first eval lands (unless a
18
19
  * `fallback` node is provided to gate on readiness).
19
20
  */
20
- export declare function BarricadorProvider({ clientKey, user, baseUrl, streaming, telemetry, flushIntervalMs, fallback, children, }: BarricadorProviderProps): import("react").JSX.Element;
21
+ export declare function BarricadorProvider({ clientKey, user, baseUrl, streaming, pollIntervalMs, telemetry, flushIntervalMs, fallback, children, }: BarricadorProviderProps): import("react").JSX.Element;
@@ -8,7 +8,7 @@ import { BarricadorStore } from "./store";
8
8
  * render immediately and flags resolve to their fallbacks until the first eval lands (unless a
9
9
  * `fallback` node is provided to gate on readiness).
10
10
  */
11
- export function BarricadorProvider({ clientKey, user, baseUrl, streaming, telemetry, flushIntervalMs, fallback, children, }) {
11
+ export function BarricadorProvider({ clientKey, user, baseUrl, streaming, pollIntervalMs, telemetry, flushIntervalMs, fallback, children, }) {
12
12
  const storeRef = useRef(null);
13
13
  if (storeRef.current === null) {
14
14
  storeRef.current = new BarricadorStore({
@@ -16,6 +16,7 @@ export function BarricadorProvider({ clientKey, user, baseUrl, streaming, teleme
16
16
  user,
17
17
  baseUrl,
18
18
  streaming,
19
+ pollIntervalMs,
19
20
  telemetry,
20
21
  flushIntervalMs,
21
22
  });
package/dist/store.d.ts CHANGED
@@ -17,6 +17,7 @@ export declare class BarricadorStore {
17
17
  private readonly streaming;
18
18
  private readonly telemetry;
19
19
  private readonly flushIntervalMs;
20
+ private readonly pollIntervalMs;
20
21
  private values;
21
22
  private status;
22
23
  /** Per-key listeners so a component only re-renders when *its* flag changes. */
@@ -25,7 +26,10 @@ export declare class BarricadorStore {
25
26
  private readonly evalCounts;
26
27
  private eventSource;
27
28
  private flushTimer;
29
+ private pollTimer;
28
30
  private closed;
31
+ /** Avoid spamming the console on every reconnect/refresh failure. */
32
+ private warnedEvalFailure;
29
33
  constructor(options: BarricadorClientOptions);
30
34
  start(): Promise<void>;
31
35
  /** Re-post the user context and swap in the freshly evaluated values. */
package/dist/store.js CHANGED
@@ -18,7 +18,10 @@ export class BarricadorStore {
18
18
  this.evalCounts = new Map();
19
19
  this.eventSource = null;
20
20
  this.flushTimer = null;
21
+ this.pollTimer = null;
21
22
  this.closed = false;
23
+ /** Avoid spamming the console on every reconnect/refresh failure. */
24
+ this.warnedEvalFailure = false;
22
25
  if (!options.clientKey)
23
26
  throw new Error("clientKey is required");
24
27
  if (!options.user?.key)
@@ -26,14 +29,22 @@ export class BarricadorStore {
26
29
  this.clientKey = options.clientKey;
27
30
  this.user = options.user;
28
31
  this.baseUrl = (options.baseUrl ?? "https://app.barricador.com").replace(/\/$/, "");
29
- this.streaming = options.streaming ?? true;
32
+ // Polling by default. An open EventSource is billed as continuous backend instance time, and a
33
+ // browser SDK opens one per tab — so streaming every session made idle users cost as much as
34
+ // active ones. Opt in with `streaming` where sub-second propagation matters.
35
+ this.streaming = options.streaming ?? false;
30
36
  this.telemetry = options.telemetry ?? true;
31
37
  this.flushIntervalMs = options.flushIntervalMs ?? 30000;
38
+ this.pollIntervalMs = options.pollIntervalMs ?? 30000;
32
39
  }
33
40
  async start() {
34
41
  await this.refresh();
35
- if (this.streaming)
42
+ if (this.streaming) {
36
43
  this.connectStream();
44
+ }
45
+ else if (typeof setInterval !== "undefined") {
46
+ this.pollTimer = setInterval(() => void this.refresh(), this.pollIntervalMs);
47
+ }
37
48
  if (this.telemetry && typeof setInterval !== "undefined") {
38
49
  this.flushTimer = setInterval(() => void this.flush(), this.flushIntervalMs);
39
50
  }
@@ -54,9 +65,23 @@ export class BarricadorStore {
54
65
  const body = (await res.json());
55
66
  this.applyValues(body.values ?? {});
56
67
  this.setStatus("ready");
68
+ this.warnedEvalFailure = false;
57
69
  }
58
- catch {
70
+ catch (err) {
59
71
  // Network/eval failure: keep last values; hooks fall back to defaults. Never throw.
72
+ // CORS preflight rejection (origin not on BARRICADOR_CORS_ORIGINS) surfaces as a
73
+ // TypeError/"Failed to fetch" with no response — identical to a missing key from the
74
+ // caller's perspective — so surface a one-shot console hint.
75
+ if (!this.warnedEvalFailure && typeof console !== "undefined") {
76
+ this.warnedEvalFailure = true;
77
+ const origin = typeof globalThis !== "undefined" && "location" in globalThis
78
+ ? String(globalThis.location?.origin ?? "")
79
+ : "";
80
+ console.warn(`[barricador] flag eval failed (${err instanceof Error ? err.message : String(err)}). ` +
81
+ `Flags will use their fallback defaults. If this is a browser app, confirm that ` +
82
+ `${origin || "this page's Origin"} is listed in BARRICADOR_CORS_ORIGINS on the ` +
83
+ `Barricador backend (CORS preflight 403 looks like a network failure here).`);
84
+ }
60
85
  this.setStatus(this.status === "initializing" ? "offline" : this.status);
61
86
  }
62
87
  }
@@ -154,6 +179,8 @@ export class BarricadorStore {
154
179
  this.closed = true;
155
180
  if (this.flushTimer)
156
181
  clearInterval(this.flushTimer);
182
+ if (this.pollTimer)
183
+ clearInterval(this.pollTimer);
157
184
  this.eventSource?.close();
158
185
  void this.flush();
159
186
  }
package/dist/types.d.ts CHANGED
@@ -14,7 +14,14 @@ export interface BarricadorClientOptions {
14
14
  user: UserContext;
15
15
  baseUrl?: string;
16
16
  /** Enable the live SSE connection (default true). */
17
+ /**
18
+ * Hold an EventSource open for near-instant flag propagation. Defaults to false: a held-open
19
+ * stream is billed as continuous backend instance time, per browser tab. When false the store
20
+ * re-evaluates every `pollIntervalMs`.
21
+ */
17
22
  streaming?: boolean;
23
+ /** How often to re-evaluate flags when `streaming` is false. Default 30000ms. */
24
+ pollIntervalMs?: number;
18
25
  /** Enable async telemetry flushing (default true). */
19
26
  telemetry?: boolean;
20
27
  /** Telemetry flush cadence in ms (default 30000). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barricador/react-client",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Barricador client-side React SDK (pre-evaluated context model, SSE sync, telemetry)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",