@burdenoff/fe-libs 2026.1021.10 → 2026.1021.12

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.
@@ -1,17 +1,18 @@
1
1
  "use client";
2
2
  import { useShellLabel as e } from "../../providers/shell/useShellLabel.js";
3
- import { useEventBus as t } from "../../../shared-events.js";
4
- import { useEffect as n, useRef as r, useState as i } from "react";
5
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
3
+ import { isRybbitSkippedRoute as t } from "./rybbitRoutes.js";
4
+ import { useEventBus as n } from "../../../shared-events.js";
5
+ import { useEffect as r, useRef as i, useState as a } from "react";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
6
7
  //#region src/shared/events/sinks/RybbitSink.tsx
7
- var s = /* @__PURE__ */ "nav.route.changed,auth.signed_in,auth.signed_out,support.ticket.created,support.ticket.closed,support.ticket.reopened,support.kb.article_rated,notification.item.opened,notification.preference.updated,notification.admin.sent,notification.template.saved,notification.schedule.updated,bigconsole.dashboard.import_clicked,bigconsole.dashboard.imported,bigconsole.dashboard.share_clicked,bigconsole.dashboard.shared,bigconsole.data_sink.import_clicked,bigconsole.data_sink.imported,bigconsole.widget.create_clicked,bigconsole.widget.created,bigconsole.widget.duplicate_clicked,bigconsole.widget.duplicated,bigconsole.ai.generate_clicked,bigconsole.ai.dashboard_generated,bigconsole.ai.create_dashboard_clicked,bigconsole.ai.dashboard_created".split(","), c = /(token|password|secret|authorization|cookie|session|email|name|stack|message|subject|description|query|search|prompt|formula|reason|body|content)/i, l = /(^id$|Id$|_id$|_ID$)/, u = [
8
+ var c = /* @__PURE__ */ "nav.route.changed,auth.signed_in,auth.signed_out,support.ticket.created,support.ticket.closed,support.ticket.reopened,support.kb.article_rated,notification.item.opened,notification.preference.updated,notification.admin.sent,notification.template.saved,notification.schedule.updated,bigconsole.dashboard.import_clicked,bigconsole.dashboard.imported,bigconsole.dashboard.share_clicked,bigconsole.dashboard.shared,bigconsole.data_sink.import_clicked,bigconsole.data_sink.imported,bigconsole.widget.create_clicked,bigconsole.widget.created,bigconsole.widget.duplicate_clicked,bigconsole.widget.duplicated,bigconsole.ai.generate_clicked,bigconsole.ai.dashboard_generated,bigconsole.ai.create_dashboard_clicked,bigconsole.ai.dashboard_created".split(","), l = /(token|password|secret|authorization|cookie|session|email|name|stack|message|subject|description|query|search|prompt|formula|reason|body|content)/i, u = /(^id$|Id$|_id$|_ID$)/, d = [
8
9
  "ticketId",
9
10
  "categoryId",
10
11
  "workspaceId"
11
- ], d = ["workspaceId"], f = {
12
- "support.ticket.created": u,
13
- "support.ticket.closed": u,
14
- "support.ticket.reopened": u,
12
+ ], f = ["workspaceId"], p = {
13
+ "support.ticket.created": d,
14
+ "support.ticket.closed": d,
15
+ "support.ticket.reopened": d,
15
16
  "support.kb.article_rated": ["articleId"],
16
17
  "bigconsole.dashboard.imported": ["dashboardId"],
17
18
  "bigconsole.dashboard.share_clicked": ["dashboardId"],
@@ -37,54 +38,54 @@ var s = /* @__PURE__ */ "nav.route.changed,auth.signed_in,auth.signed_out,suppor
37
38
  "bigconsole.ai.dashboard_generated": ["generationId"],
38
39
  "bigconsole.ai.create_dashboard_clicked": ["generationId"],
39
40
  "bigconsole.ai.dashboard_created": ["dashboardId", "generationId"],
40
- "healthybowl.farm.created": d,
41
- "healthybowl.crop.created": d,
42
- "healthybowl.crop.updated": d,
43
- "healthybowl.harvest.recorded": d
44
- }, p = {
41
+ "healthybowl.farm.created": f,
42
+ "healthybowl.crop.created": f,
43
+ "healthybowl.crop.updated": f,
44
+ "healthybowl.harvest.recorded": f
45
+ }, m = {
45
46
  "auth.signed_out": ["reason"],
46
47
  "psw.search.performed": ["queryLength"]
47
- }, m = {}, h = "bf-analytics-consent", g = "burdenoff:analytics-consent";
48
- function _() {
48
+ }, h = {}, g = [], _ = "bf-analytics-consent", v = "burdenoff:analytics-consent";
49
+ function y() {
49
50
  return navigator.globalPrivacyControl === !0;
50
51
  }
51
- function v() {
52
+ function b() {
52
53
  if (typeof window > "u") return null;
53
- let e = window.localStorage.getItem(h);
54
+ let e = window.localStorage.getItem(_);
54
55
  return e === "granted" || e === "denied" ? e : null;
55
56
  }
56
- function y() {
57
- return typeof window > "u" || _() ? !1 : v() === "granted";
57
+ function x() {
58
+ return typeof window > "u" || y() ? !1 : b() === "granted";
58
59
  }
59
- function b(e) {
60
- typeof window > "u" || (window.localStorage.setItem(h, e ? "granted" : "denied"), window.dispatchEvent(new CustomEvent(g, { detail: { granted: e } })));
60
+ function S(e) {
61
+ typeof window > "u" || (window.localStorage.setItem(_, e ? "granted" : "denied"), window.dispatchEvent(new CustomEvent(v, { detail: { granted: e } })));
61
62
  }
62
- function x() {
63
+ function C() {
63
64
  let e = window;
64
65
  return navigator.doNotTrack === "1" || e.doNotTrack === "1";
65
66
  }
66
- function S(e) {
67
+ function w(e) {
67
68
  let t = 2166136261;
68
69
  for (let n = 0; n < e.length; n += 1) t ^= e.charCodeAt(n), t = Math.imul(t, 16777619);
69
70
  return `u_${(t >>> 0).toString(16).padStart(8, "0")}`;
70
71
  }
71
- async function C(e) {
72
+ async function T(e) {
72
73
  if (crypto.subtle) {
73
74
  let t = new TextEncoder().encode(e), n = await crypto.subtle.digest("SHA-256", t);
74
75
  return Array.from(new Uint8Array(n), (e) => e.toString(16).padStart(2, "0")).join("");
75
76
  }
76
- return S(e);
77
+ return w(e);
77
78
  }
78
- function w(e, t) {
79
- return c.test(e) ? !(p[t] ?? []).includes(e) : l.test(e) ? !(f[t] ?? []).includes(e) : !1;
79
+ function E(e, t) {
80
+ return l.test(e) ? !(m[t] ?? []).includes(e) : u.test(e) ? !(p[t] ?? []).includes(e) : !1;
80
81
  }
81
- function T(e, t) {
82
+ function D(e, t) {
82
83
  if (!e || typeof e != "object" || Array.isArray(e)) return {};
83
84
  let n = {};
84
- for (let [r, i] of Object.entries(e)) w(r, t) || (typeof i == "string" || typeof i == "number" || typeof i == "boolean") && (n[r] = i);
85
+ for (let [r, i] of Object.entries(e)) E(r, t) || (typeof i == "string" || typeof i == "number" || typeof i == "boolean") && (n[r] = i);
85
86
  return n;
86
87
  }
87
- function E(e) {
88
+ function O(e) {
88
89
  let t = {};
89
90
  for (let [n, r] of Object.entries(e)) {
90
91
  let e = {
@@ -96,86 +97,89 @@ function E(e) {
96
97
  }
97
98
  return t;
98
99
  }
99
- function D({ siteId: e, scriptUrl: a = "https://app.rybbit.io/api/script.js", rename: o = m, allowlist: c = s, identifyOn: l = "auth.signed_in" }) {
100
- let u = t(), d = r(!1), [f, p] = i(y);
101
- return n(() => {
102
- let e = () => p(y()), t = (t) => {
103
- t.key === h && e();
100
+ function k({ siteId: e, scriptUrl: o = "https://app.rybbit.io/api/script.js", rename: s = h, allowlist: l = c, identifyOn: u = "auth.signed_in", skipRoutes: d = g, pathname: f }) {
101
+ let p = n(), m = i(!1), b = i(null), [S, w] = a(x), E = t(f ?? (typeof window > "u" ? void 0 : window.location.pathname), d);
102
+ return r(() => {
103
+ let e = () => w(x()), t = (t) => {
104
+ t.key === _ && e();
104
105
  };
105
- return window.addEventListener(g, e), window.addEventListener("storage", t), () => {
106
- window.removeEventListener(g, e), window.removeEventListener("storage", t);
106
+ return window.addEventListener(v, e), window.addEventListener("storage", t), () => {
107
+ window.removeEventListener(v, e), window.removeEventListener("storage", t);
107
108
  };
108
- }, []), n(() => {
109
- if (!e || !f || _() || d.current) return;
110
- d.current = !0;
109
+ }, []), r(() => {
110
+ if (!e || !S || E || y() || m.current) return;
111
+ m.current = !0;
111
112
  let t = document.createElement("script");
112
- return t.src = a, t.defer = !0, t.dataset.siteId = e, document.head.appendChild(t), () => {
113
- document.head.removeChild(t);
114
- };
113
+ t.src = o, t.defer = !0, t.dataset.siteId = e, d.length > 0 && (t.dataset.skipPatterns = JSON.stringify(d)), document.head.appendChild(t), b.current = t;
115
114
  }, [
116
115
  e,
117
- a,
118
- f
119
- ]), n(() => {
120
- if (!(!e || !f || _())) return u.onAny((e) => {
121
- if (x()) return;
122
- let t = window;
123
- if (!t.rybbit || !c.includes(e.name)) return;
124
- let n = E({
116
+ o,
117
+ S,
118
+ E,
119
+ d
120
+ ]), r(() => () => {
121
+ b.current?.remove(), b.current = null;
122
+ }, []), r(() => {
123
+ if (!(!e || !S || y())) return p.onAny((e) => {
124
+ if (C()) return;
125
+ let n = window;
126
+ if (!n.rybbit || !l.includes(e.name) || t(e.metadata.route.pathname, d)) return;
127
+ let r = O({
125
128
  route: e.metadata.route.pathname,
126
129
  env: e.metadata.app.env,
127
- ...T(e.payload, e.name)
130
+ ...D(e.payload, e.name)
128
131
  });
129
- if (e.name === l) {
130
- let r = e.payload, i = r?.userId ?? r?.actorId ?? r?.id;
131
- i && C(i).then((e) => {
132
- t.rybbit?.identify(e, n);
132
+ if (e.name === u) {
133
+ let t = e.payload, i = t?.userId ?? t?.actorId ?? t?.id;
134
+ i && T(i).then((e) => {
135
+ n.rybbit?.identify(e, r);
133
136
  });
134
137
  }
135
138
  if (e.name === "nav.route.changed") {
136
- t.rybbit.pageview();
139
+ n.rybbit.pageview();
137
140
  return;
138
141
  }
139
- let r = o[e.name] ?? e.name;
140
- t.rybbit.event(r, n);
142
+ let i = s[e.name] ?? e.name;
143
+ n.rybbit.event(i, r);
141
144
  });
142
145
  }, [
143
- u,
146
+ p,
144
147
  e,
145
- c,
146
148
  l,
147
- o,
148
- f
149
+ u,
150
+ s,
151
+ S,
152
+ d
149
153
  ]), null;
150
154
  }
151
- function O({ productName: t }) {
152
- let [n, r] = i(() => v() === null), s = e();
153
- return !n || _() ? null : /* @__PURE__ */ o("aside", {
155
+ function A({ productName: t }) {
156
+ let [n, r] = a(() => b() === null), i = e();
157
+ return !n || y() ? null : /* @__PURE__ */ s("aside", {
154
158
  className: "fixed bottom-4 left-4 z-[100] w-[calc(100%-2rem)] max-w-sm rounded-xl border border-border-seam bg-bg-surface p-4 text-text-primary shadow-pop",
155
- "aria-label": s("privacyConsent.ariaLabel", "Privacy choices"),
159
+ "aria-label": i("privacyConsent.ariaLabel", "Privacy choices"),
156
160
  "data-testid": "analytics-consent-banner",
157
- children: [/* @__PURE__ */ a("p", {
161
+ children: [/* @__PURE__ */ o("p", {
158
162
  className: "text-sm",
159
- children: s("privacyConsent.message", "Allow optional analytics to help improve {{productName}}? Essential product operation works either way.", { productName: t ?? s("privacyConsent.productFallback", "this product") })
160
- }), /* @__PURE__ */ o("div", {
163
+ children: i("privacyConsent.message", "Allow optional analytics to help improve {{productName}}? Essential product operation works either way.", { productName: t ?? i("privacyConsent.productFallback", "this product") })
164
+ }), /* @__PURE__ */ s("div", {
161
165
  className: "mt-3 flex gap-2",
162
- children: [/* @__PURE__ */ a("button", {
166
+ children: [/* @__PURE__ */ o("button", {
163
167
  type: "button",
164
168
  className: "rounded-md border border-border-seam px-3 py-2 text-sm",
165
169
  onClick: () => {
166
- b(!1), r(!1);
170
+ S(!1), r(!1);
167
171
  },
168
- children: s("privacyConsent.reject", "Reject optional")
169
- }), /* @__PURE__ */ a("button", {
172
+ children: i("privacyConsent.reject", "Reject optional")
173
+ }), /* @__PURE__ */ o("button", {
170
174
  type: "button",
171
175
  className: "rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text",
172
176
  onClick: () => {
173
- b(!0), r(!1);
177
+ S(!0), r(!1);
174
178
  },
175
- children: s("privacyConsent.allow", "Allow analytics")
179
+ children: i("privacyConsent.allow", "Allow analytics")
176
180
  })]
177
181
  })]
178
182
  });
179
183
  }
180
184
  //#endregion
181
- export { O as AnalyticsConsentBanner, D as RybbitSink, y as readAnalyticsConsent, v as readAnalyticsConsentChoice, b as setAnalyticsConsent };
185
+ export { A as AnalyticsConsentBanner, c as DEFAULT_RYBBIT_ALLOWLIST, k as RybbitSink, x as readAnalyticsConsent, b as readAnalyticsConsentChoice, S as setAnalyticsConsent };
@@ -3,4 +3,5 @@ export { BroadcastChannelSink } from './BroadcastChannelSink';
3
3
  export { BackendAuditSink } from './BackendAuditSink';
4
4
  export { RybbitSink, AnalyticsConsentBanner, readAnalyticsConsent, readAnalyticsConsentChoice, setAnalyticsConsent, } from './RybbitSink';
5
5
  export type { AnalyticsConsentChoice } from './RybbitSink';
6
+ export { compileRybbitRoutePattern, isRybbitSkippedRoute } from './rybbitRoutes';
6
7
  export { OtelBrowserSink } from './OtelBrowserSink';
@@ -1,5 +1,6 @@
1
1
  import "./DevtoolsSink.js";
2
2
  import "./BroadcastChannelSink.js";
3
3
  import "./BackendAuditSink.js";
4
+ import "./rybbitRoutes.js";
4
5
  import "./RybbitSink.js";
5
6
  import "./OtelBrowserSink.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Route patterns on which Rybbit must send nothing (G27).
3
+ *
4
+ * The syntax is Rybbit's own `data-skip-patterns` syntax, so one list drives
5
+ * both halves of the gate: RybbitSink hands it to the Rybbit script (which then
6
+ * sends no pageview or event while the URL matches) and applies it itself
7
+ * before injecting the script and before forwarding a bus event.
8
+ *
9
+ * - `*` matches one path segment: `/shared/*` matches `/shared/abc`, not `/shared/a/b`.
10
+ * - `**` matches any number of segments: `/share/**` matches `/share/a/b`.
11
+ * - `re:<regex>` is a raw regular expression tested against the pathname.
12
+ *
13
+ * Patterns are anchored at both ends, the same as Rybbit's matcher.
14
+ */
15
+ /** Compile one skip pattern exactly as the Rybbit script does. */
16
+ export declare function compileRybbitRoutePattern(pattern: string): RegExp;
17
+ /**
18
+ * True when `pathname` matches any pattern. An invalid pattern never matches
19
+ * (Rybbit logs and skips it too), so one typo cannot silence a whole shell.
20
+ */
21
+ export declare function isRybbitSkippedRoute(pathname: string | undefined, patterns: readonly string[] | undefined): boolean;
@@ -0,0 +1,20 @@
1
+ //#region src/shared/events/sinks/rybbitRoutes.ts
2
+ var e = "\0DS\0", t = "\0SS\0";
3
+ function n(n) {
4
+ if (n.startsWith("re:")) {
5
+ let e = n.slice(3);
6
+ if (!e) throw Error("Empty regex pattern");
7
+ return new RegExp(e);
8
+ }
9
+ let r = n.replace(/\*\*/g, e).replace(/\*/g, t).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
10
+ return r = r.split(`/${e}/`).join("/(?:.+/)?"), r = r.split(e).join(".*"), r = r.split(t).join("[^/]+"), RegExp(`^${r}$`);
11
+ }
12
+ function r(e, t) {
13
+ if (!e || !t || t.length === 0) return !1;
14
+ for (let r of t) try {
15
+ if (n(r).test(e)) return !0;
16
+ } catch {}
17
+ return !1;
18
+ }
19
+ //#endregion
20
+ export { n as compileRybbitRoutePattern, r as isRybbitSkippedRoute };
@@ -85,6 +85,24 @@ export interface AppShellConfig {
85
85
  * purpose is that a stranger can read it.
86
86
  */
87
87
  consentRouteDenylist?: (pathname: string) => boolean;
88
+ /**
89
+ * Extra event names this shell forwards to Rybbit (G27), ADDED to the
90
+ * platform allowlist (or to `observability.rybbit.allowlist` when the shell
91
+ * overrides it) instead of replacing it. Lets a shell ship a new product
92
+ * event without waiting for, or forking, the platform list. Adding a name is
93
+ * still the deliberate privacy act described in RYBBIT_FRONTEND_ANALYTICS.md;
94
+ * RybbitSink keeps stripping identifier and sensitive keys either way. Pass a
95
+ * module-level constant: a new array identity re-subscribes the sink.
96
+ */
97
+ rybbitEvents?: readonly BurdenoffEventName[];
98
+ /**
99
+ * Routes on which NOTHING is sent to Rybbit (G27): no script load, no
100
+ * pageview, no event. In Rybbit's skip-pattern syntax (`*` = one segment,
101
+ * `**` = any depth, `re:` = regex), e.g. `['/shared/*']` for FluidGrids'
102
+ * public workflow viewer. Independent of `consentRouteDenylist`, which only
103
+ * hides the consent prompt. Pass a module-level constant.
104
+ */
105
+ rybbitSkipRoutes?: readonly string[];
88
106
  /**
89
107
  * Maps a pathname to the tour `pageId` used when looking up tours.
90
108
  *
@@ -159,6 +177,14 @@ export interface AppShellObservabilityEnv {
159
177
  * element, they override sonner's stylesheet defaults.
160
178
  */
161
179
  export declare const RICH_TOAST_TOKEN_STYLE: CSSProperties;
180
+ /**
181
+ * Adds a shell's own Rybbit event names (`AppShellConfig.rybbitEvents`, G27) to
182
+ * the allowlist that resolved for it, keeping the base order and dropping
183
+ * duplicates. With no base allowlist RybbitSink would fall back to
184
+ * DEFAULT_RYBBIT_ALLOWLIST, so that fallback is the base the extras extend.
185
+ * Returns the input unchanged when there is nothing to add.
186
+ */
187
+ export declare function withShellRybbitEvents(config: AppShellObservabilityConfig, extraEvents: readonly BurdenoffEventName[] | undefined): AppShellObservabilityConfig;
162
188
  export declare function resolveAppShellObservabilityConfig(productSlug: string, env: AppShellObservabilityEnv): AppShellObservabilityConfig;
163
189
  export interface AppShellProviderProps {
164
190
  config: AppShellConfig;