@verbumia/feedback 0.2.5 → 0.2.7
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/dist/{chunk-5DZUKJ4M.js → chunk-7KWEI55W.js} +78 -3
- package/dist/chunk-7KWEI55W.js.map +1 -0
- package/dist/{client-CYgwLkiA.d.cts → client-D83qhH0O.d.cts} +85 -1
- package/dist/{client-CYgwLkiA.d.ts → client-D83qhH0O.d.ts} +85 -1
- package/dist/core/index.cjs +77 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/{keys-BPAFf8xR.d.ts → keys-BpVB1kcI.d.ts} +1 -1
- package/dist/{keys-BQpczt84.d.cts → keys-D4oJtn64.d.cts} +1 -1
- package/dist/native/index.cjs +184 -23
- package/dist/native/index.cjs.map +1 -1
- package/dist/native/index.d.cts +56 -4
- package/dist/native/index.d.ts +56 -4
- package/dist/native/index.js +108 -22
- package/dist/native/index.js.map +1 -1
- package/dist/react/index.cjs +184 -23
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +135 -5
- package/dist/react/index.d.ts +135 -5
- package/dist/react/index.js +108 -22
- package/dist/react/index.js.map +1 -1
- package/dist/svelte/index.cjs +77 -2
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.d.cts +2 -2
- package/dist/svelte/index.d.ts +2 -2
- package/dist/svelte/index.js +1 -1
- package/dist/vue/index.cjs +77 -2
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.cts +2 -2
- package/dist/vue/index.d.ts +2 -2
- package/dist/vue/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-5DZUKJ4M.js.map +0 -1
package/dist/core/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { B as BatchResponse, D as DeclaredKey, F as FeedbackClient, a as FeedbackConfig, b as FeedbackError, c as FeedbackString, R as RatingInput, S as StringsResponse, d as SuggestionInput, T as TokenBundle } from '../client-
|
|
2
|
-
export { f as filterByNamespace, h as hasKeyRegistry, r as resolveKeys } from '../keys-
|
|
1
|
+
export { B as BatchResponse, D as DeclaredKey, F as FeedbackClient, a as FeedbackConfig, b as FeedbackError, c as FeedbackString, R as RatingInput, S as StringsResponse, d as SuggestionInput, T as TokenBundle } from '../client-D83qhH0O.cjs';
|
|
2
|
+
export { f as filterByNamespace, h as hasKeyRegistry, r as resolveKeys } from '../keys-D4oJtn64.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* BUILD-TIME ToS version constant (task 616, human decision option B).
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { B as BatchResponse, D as DeclaredKey, F as FeedbackClient, a as FeedbackConfig, b as FeedbackError, c as FeedbackString, R as RatingInput, S as StringsResponse, d as SuggestionInput, T as TokenBundle } from '../client-
|
|
2
|
-
export { f as filterByNamespace, h as hasKeyRegistry, r as resolveKeys } from '../keys-
|
|
1
|
+
export { B as BatchResponse, D as DeclaredKey, F as FeedbackClient, a as FeedbackConfig, b as FeedbackError, c as FeedbackString, R as RatingInput, S as StringsResponse, d as SuggestionInput, T as TokenBundle } from '../client-D83qhH0O.js';
|
|
2
|
+
export { f as filterByNamespace, h as hasKeyRegistry, r as resolveKeys } from '../keys-BpVB1kcI.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* BUILD-TIME ToS version constant (task 616, human decision option B).
|
package/dist/core/index.js
CHANGED
package/dist/native/index.cjs
CHANGED
|
@@ -80,15 +80,81 @@ var FeedbackClient = class {
|
|
|
80
80
|
get hasConsented() {
|
|
81
81
|
return this.tokens !== null;
|
|
82
82
|
}
|
|
83
|
+
/** Alias of `hasConsented` exposed under the 0.2.7 naming used by the
|
|
84
|
+
* `controller.hasAcceptedTos` getter (the SDK's built-in modal and a
|
|
85
|
+
* host's external ToS page share the same persisted token bundle, so
|
|
86
|
+
* both flip this boolean once a session is bootstrapped). */
|
|
87
|
+
get hasAcceptedTos() {
|
|
88
|
+
return this.tokens !== null;
|
|
89
|
+
}
|
|
83
90
|
/** Server-minted sessionId / grouping_key (from the token bundle).
|
|
84
91
|
* Available only after `acceptTos()`; never client-generated. */
|
|
85
92
|
get sessionId() {
|
|
86
93
|
return this.tokens?.grouping_key;
|
|
87
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* 0.2.7 — `GET /v1/projects/{projectId}/feedback-addon/state`.
|
|
97
|
+
*
|
|
98
|
+
* Auth selection (matches backend's dual-acceptance after task 836's
|
|
99
|
+
* follow-up PR — see CONTRACT note in the response type):
|
|
100
|
+
* - If `cfg.apiKey` is set → `Authorization: ApiKey <key>` (so the
|
|
101
|
+
* plugin can fetch state at `setup()` time, before the user has
|
|
102
|
+
* accepted ToS).
|
|
103
|
+
* - Else if a user-session bundle exists → `Authorization: Bearer
|
|
104
|
+
* <access_token>` (the deferred-after-acceptTos path).
|
|
105
|
+
* - Else → throw `FeedbackError("addon state requires apiKey or
|
|
106
|
+
* acceptTos")`. The plugin's setup catches and surfaces a
|
|
107
|
+
* console.warn the first time, then re-attempts on the
|
|
108
|
+
* bundle-mint that follows `acceptTos()`.
|
|
109
|
+
*
|
|
110
|
+
* Best-effort: a transport error returns `null` instead of throwing,
|
|
111
|
+
* so the controller's `isActive` falls back to whatever was last
|
|
112
|
+
* known (or its initial value) without flipping the host's CTA into
|
|
113
|
+
* an inconsistent state on a transient blip.
|
|
114
|
+
*/
|
|
115
|
+
async getAddonState() {
|
|
116
|
+
let auth;
|
|
117
|
+
if (this.cfg.apiKey) {
|
|
118
|
+
auth = `ApiKey ${this.cfg.apiKey}`;
|
|
119
|
+
} else if (this.tokens) {
|
|
120
|
+
auth = `Bearer ${this.tokens.access_token}`;
|
|
121
|
+
} else {
|
|
122
|
+
throw new FeedbackError(
|
|
123
|
+
"addon state requires apiKey or acceptTos"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const url = `${this.base()}/v1/projects/${this.cfg.projectId}/feedback-addon/state`;
|
|
127
|
+
try {
|
|
128
|
+
const res = await this.fetchImpl(url, {
|
|
129
|
+
method: "GET",
|
|
130
|
+
headers: { Authorization: auth }
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) return null;
|
|
133
|
+
return await res.json();
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
88
138
|
/** BCP-47 language the widget is rating strings in. */
|
|
89
139
|
get language() {
|
|
90
140
|
return this.cfg.language;
|
|
91
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Re-target the widget's working language at runtime (#806 SeedSower
|
|
144
|
+
* lang-change propagation). Subsequent `getStrings` / `rate` /
|
|
145
|
+
* `suggest` use the new language without rebuilding the client. The
|
|
146
|
+
* server-minted ToS bundle has no `locale` field, so a language flip
|
|
147
|
+
* does NOT invalidate consent — only the next `?language=` query
|
|
148
|
+
* needs updating. The i18n provider plugin (feedback ≥0.2.6)
|
|
149
|
+
* subscribes to `@verbumia/react-i18next` ≥1.0.5's
|
|
150
|
+
* `VerbumiaPluginContext.onLanguageChange` and calls this on every
|
|
151
|
+
* runtime language change so the host never has to wire it manually.
|
|
152
|
+
*/
|
|
153
|
+
setLanguage(lang) {
|
|
154
|
+
if (typeof lang !== "string" || lang.length === 0) return;
|
|
155
|
+
if (lang === this.cfg.language) return;
|
|
156
|
+
this.cfg = { ...this.cfg, language: lang };
|
|
157
|
+
}
|
|
92
158
|
/** ToS version the end user is asked to accept — the SDK's
|
|
93
159
|
* build-time constant (task 616). NOT integrator/server set. */
|
|
94
160
|
get tosVersion() {
|
|
@@ -140,9 +206,18 @@ var FeedbackClient = class {
|
|
|
140
206
|
}
|
|
141
207
|
this.tokens = await res.json();
|
|
142
208
|
}
|
|
143
|
-
/** Authenticated fetch with a single transparent refresh-on-401 retry.
|
|
209
|
+
/** Authenticated fetch with a single transparent refresh-on-401 retry.
|
|
210
|
+
* When `cfg.autoAcceptTos === false` (the plugin's `tos: "skip"` opt-in
|
|
211
|
+
* / 0.2.7) and no token has been minted yet, throws a `not consented`
|
|
212
|
+
* error instead of silently calling `acceptTos()` — the host promises
|
|
213
|
+
* to handle consent externally via `controller.acceptTos()`. */
|
|
144
214
|
async authed(path, init, retry = true) {
|
|
145
|
-
if (!this.tokens)
|
|
215
|
+
if (!this.tokens) {
|
|
216
|
+
if (this.cfg.autoAcceptTos === false) {
|
|
217
|
+
throw new FeedbackError("not consented");
|
|
218
|
+
}
|
|
219
|
+
await this.acceptTos();
|
|
220
|
+
}
|
|
146
221
|
const res = await this.fetchImpl(`${this.base()}${path}`, {
|
|
147
222
|
...init,
|
|
148
223
|
headers: {
|
|
@@ -298,8 +373,10 @@ var C = {
|
|
|
298
373
|
emeraldSoft: "#34d399"
|
|
299
374
|
};
|
|
300
375
|
function FeedbackModal(props) {
|
|
301
|
-
const { client, visible, keys, namespace, onClose } = props;
|
|
302
|
-
const [consented, setConsented] = (0, import_react.useState)(
|
|
376
|
+
const { client, visible, keys, namespace, tos = "modal", onClose } = props;
|
|
377
|
+
const [consented, setConsented] = (0, import_react.useState)(
|
|
378
|
+
tos === "skip" ? true : client.hasConsented
|
|
379
|
+
);
|
|
303
380
|
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
304
381
|
const [error, setError] = (0, import_react.useState)(null);
|
|
305
382
|
const [strings, setStrings] = (0, import_react.useState)([]);
|
|
@@ -533,42 +610,73 @@ function StringRow(props) {
|
|
|
533
610
|
// src/native/plugin.tsx
|
|
534
611
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
535
612
|
function makeStore() {
|
|
536
|
-
let
|
|
613
|
+
let state = { isOpen: false, snapshotKeys: void 0 };
|
|
537
614
|
const listeners = /* @__PURE__ */ new Set();
|
|
538
615
|
return {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
open
|
|
543
|
-
|
|
616
|
+
getState: () => state,
|
|
617
|
+
setOpen(open, snapshotKeys) {
|
|
618
|
+
const next = {
|
|
619
|
+
isOpen: open,
|
|
620
|
+
snapshotKeys: open ? snapshotKeys : void 0
|
|
621
|
+
};
|
|
622
|
+
if (state.isOpen === next.isOpen && state.snapshotKeys === next.snapshotKeys) {
|
|
623
|
+
return;
|
|
544
624
|
}
|
|
625
|
+
state = next;
|
|
626
|
+
listeners.forEach((l) => l());
|
|
545
627
|
},
|
|
546
628
|
subscribe(l) {
|
|
547
629
|
listeners.add(l);
|
|
548
|
-
return () =>
|
|
630
|
+
return () => {
|
|
631
|
+
listeners.delete(l);
|
|
632
|
+
};
|
|
549
633
|
}
|
|
550
634
|
};
|
|
551
635
|
}
|
|
636
|
+
async function captureCurrentViewSnapshot(i18next) {
|
|
637
|
+
const reg = globalThis.__verbumia_key_registry__;
|
|
638
|
+
if (!reg) return [];
|
|
639
|
+
const lng = i18next?.language;
|
|
640
|
+
const change = i18next?.changeLanguage;
|
|
641
|
+
if (typeof change === "function" && typeof lng === "string" && lng) {
|
|
642
|
+
reg.reset?.();
|
|
643
|
+
try {
|
|
644
|
+
await change(lng);
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
await new Promise((r) => {
|
|
648
|
+
if (typeof requestAnimationFrame === "function") {
|
|
649
|
+
requestAnimationFrame(() => r());
|
|
650
|
+
} else {
|
|
651
|
+
setTimeout(() => r(), 16);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
return reg.snapshot();
|
|
655
|
+
}
|
|
656
|
+
return reg.snapshot();
|
|
657
|
+
}
|
|
552
658
|
function feedbackPlugin(options) {
|
|
553
659
|
const store = makeStore();
|
|
554
660
|
let client = null;
|
|
555
661
|
function Outlet() {
|
|
556
|
-
const
|
|
662
|
+
const state = (0, import_react2.useSyncExternalStore)(
|
|
557
663
|
store.subscribe,
|
|
558
|
-
store.
|
|
559
|
-
store.
|
|
664
|
+
store.getState,
|
|
665
|
+
store.getState
|
|
560
666
|
);
|
|
561
667
|
if (!client) return null;
|
|
562
668
|
const c = client;
|
|
669
|
+
const panelKeys = options.keys ?? state.snapshotKeys;
|
|
563
670
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
564
671
|
FeedbackModal,
|
|
565
672
|
{
|
|
566
673
|
client: c,
|
|
567
|
-
visible: isOpen,
|
|
568
|
-
keys:
|
|
674
|
+
visible: state.isOpen,
|
|
675
|
+
keys: panelKeys,
|
|
569
676
|
namespace: options.namespace,
|
|
677
|
+
tos: options.tos ?? "modal",
|
|
570
678
|
onClose: () => {
|
|
571
|
-
store.
|
|
679
|
+
store.setOpen(false);
|
|
572
680
|
void c.flush();
|
|
573
681
|
}
|
|
574
682
|
}
|
|
@@ -577,24 +685,77 @@ function feedbackPlugin(options) {
|
|
|
577
685
|
return {
|
|
578
686
|
name: "@verbumia/feedback",
|
|
579
687
|
setup(ctx) {
|
|
688
|
+
const initialLanguage = options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;
|
|
689
|
+
const tos = options.tos ?? "modal";
|
|
580
690
|
client = new FeedbackClient({
|
|
581
691
|
apiBase: options.apiBase ?? ctx.config.apiBase ?? "https://api.verbumia.dev",
|
|
582
692
|
projectId: options.projectId ?? ctx.config.projectUuid,
|
|
583
|
-
language:
|
|
693
|
+
language: initialLanguage,
|
|
584
694
|
endUserId: options.endUserId,
|
|
585
|
-
fetchImpl: options.fetchImpl
|
|
695
|
+
fetchImpl: options.fetchImpl,
|
|
696
|
+
apiKey: options.apiKey,
|
|
697
|
+
autoAcceptTos: tos !== "skip"
|
|
586
698
|
});
|
|
699
|
+
const clientRef = client;
|
|
700
|
+
let addonState = null;
|
|
701
|
+
const cta = options.cta ?? "auto";
|
|
702
|
+
const scope = options.scope ?? "current-view";
|
|
703
|
+
const ctxI18next = ctx.i18n?.i18next;
|
|
704
|
+
const refreshState = async () => {
|
|
705
|
+
try {
|
|
706
|
+
const next = await clientRef.getAddonState();
|
|
707
|
+
if (next !== null) addonState = next;
|
|
708
|
+
} catch {
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
if (options.apiKey) void refreshState();
|
|
712
|
+
let langUnsub;
|
|
713
|
+
if (typeof ctx.onLanguageChange === "function") {
|
|
714
|
+
langUnsub = ctx.onLanguageChange((lng) => {
|
|
715
|
+
clientRef.setLanguage(lng);
|
|
716
|
+
void refreshState();
|
|
717
|
+
});
|
|
718
|
+
}
|
|
587
719
|
const controller = {
|
|
588
|
-
open: () =>
|
|
720
|
+
open: async () => {
|
|
721
|
+
if (scope === "current-view") {
|
|
722
|
+
const snapshot = await captureCurrentViewSnapshot(ctxI18next);
|
|
723
|
+
store.setOpen(true, snapshot);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
store.setOpen(true);
|
|
727
|
+
},
|
|
589
728
|
close: () => {
|
|
590
|
-
store.
|
|
591
|
-
void
|
|
729
|
+
store.setOpen(false);
|
|
730
|
+
void clientRef?.flush();
|
|
731
|
+
},
|
|
732
|
+
client: clientRef,
|
|
733
|
+
get tosVersion() {
|
|
734
|
+
return clientRef.tosVersion;
|
|
735
|
+
},
|
|
736
|
+
get hasAcceptedTos() {
|
|
737
|
+
return clientRef.hasAcceptedTos;
|
|
592
738
|
},
|
|
593
|
-
|
|
739
|
+
acceptTos: async () => {
|
|
740
|
+
await clientRef.acceptTos();
|
|
741
|
+
if (!options.apiKey) await refreshState();
|
|
742
|
+
},
|
|
743
|
+
get isActive() {
|
|
744
|
+
if (cta === "show") return true;
|
|
745
|
+
if (cta === "hide") return false;
|
|
746
|
+
return addonState ? addonState.isActive : null;
|
|
747
|
+
},
|
|
748
|
+
get enabledLanguages() {
|
|
749
|
+
return addonState ? addonState.enabledLanguages : null;
|
|
750
|
+
},
|
|
751
|
+
get sku() {
|
|
752
|
+
return addonState ? addonState.sku : null;
|
|
753
|
+
}
|
|
594
754
|
};
|
|
595
755
|
options.onReady?.(controller);
|
|
596
756
|
if (options.controllerRef) options.controllerRef.current = controller;
|
|
597
757
|
return () => {
|
|
758
|
+
langUnsub?.();
|
|
598
759
|
if (options.controllerRef) options.controllerRef.current = null;
|
|
599
760
|
void client?.flush();
|
|
600
761
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/native/index.ts","../../src/native/plugin.tsx","../../src/core/types.ts","../../src/core/tos.ts","../../src/core/client.ts","../../src/native/panel.tsx","../../src/core/keys.ts"],"sourcesContent":["export {\n feedbackPlugin,\n type FeedbackController,\n type FeedbackPluginOptions,\n type I18nPlugin,\n type I18nPluginContext,\n} from \"./plugin\";\nexport { FeedbackModal } from \"./panel\";\nexport {\n FeedbackClient,\n hasKeyRegistry,\n resolveKeys,\n type BatchResponse,\n type DeclaredKey,\n type FeedbackConfig,\n FeedbackError,\n type FeedbackString,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"../core\";\n","/**\n * `@verbumia/feedback` (React Native / Expo) as a PLUGIN of the\n * `@verbumia/*-i18n` provider — same architecture as the web plugin\n * (task 599): no second context; isolated sibling outlet; imperative\n * controller for the host CTA; sessionId is server-minted. Native uses\n * an RN <Modal> (its own overlay) toggled by a private store, so the\n * host app never re-renders when feedback opens.\n */\nimport { useSyncExternalStore, type ReactNode } from \"react\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport type { DeclaredKey } from \"../core/types\";\nimport { FeedbackModal } from \"./panel\";\n\nexport interface I18nPluginContext {\n config: {\n apiBase?: string;\n projectUuid: string;\n defaultLocale: string;\n };\n}\nexport interface I18nPlugin {\n name: string;\n setup?: (ctx: I18nPluginContext) => void | (() => void);\n render?: () => ReactNode;\n}\n\nexport interface FeedbackController {\n open: () => void;\n close: () => void;\n client: FeedbackClient;\n}\n\nexport interface FeedbackPluginOptions {\n // No `tosVersion` — SDK build-time constant (SDK_TOS_VERSION, task 616).\n language?: string;\n apiBase?: string;\n projectId?: string;\n endUserId?: string;\n keys?: DeclaredKey[];\n /** Optional namespace filter (CONTRACT v6, additive). Single ns or\n * list; applied AFTER rendered-scoping (`rendered ∩ namespace`).\n * Unset ⇒ all resolved keys (v5 behaviour). */\n namespace?: string | string[];\n onReady?: (controller: FeedbackController) => void;\n controllerRef?: { current: FeedbackController | null };\n fetchImpl?: typeof fetch;\n}\n\nfunction makeStore() {\n let open = false;\n const listeners = new Set<() => void>();\n return {\n isOpen: () => open,\n set(v: boolean) {\n if (open !== v) {\n open = v;\n listeners.forEach((l) => l());\n }\n },\n subscribe(l: () => void) {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n };\n}\n\nexport function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin {\n const store = makeStore();\n let client: FeedbackClient | null = null;\n\n function Outlet() {\n const isOpen = useSyncExternalStore(\n store.subscribe,\n store.isOpen,\n store.isOpen,\n );\n if (!client) return null;\n const c = client;\n return (\n <FeedbackModal\n client={c}\n visible={isOpen}\n keys={options.keys}\n namespace={options.namespace}\n onClose={() => {\n store.set(false);\n void c.flush();\n }}\n />\n );\n }\n\n return {\n name: \"@verbumia/feedback\",\n setup(ctx) {\n client = new FeedbackClient({\n apiBase:\n options.apiBase ?? ctx.config.apiBase ?? \"https://api.verbumia.dev\",\n projectId: options.projectId ?? ctx.config.projectUuid,\n language: options.language ?? ctx.config.defaultLocale,\n endUserId: options.endUserId,\n fetchImpl: options.fetchImpl,\n });\n const controller: FeedbackController = {\n open: () => store.set(true),\n close: () => {\n store.set(false);\n void client?.flush();\n },\n client,\n };\n options.onReady?.(controller);\n if (options.controllerRef) options.controllerRef.current = controller;\n return () => {\n if (options.controllerRef) options.controllerRef.current = null;\n void client?.flush();\n };\n },\n render: () => <Outlet />,\n };\n}\n","/**\n * Wire types for the Verbumia End-User Translation Evaluation API.\n * Canonical reference: ./CONTRACT.md (frozen). Backend task 591.\n */\n\nexport interface FeedbackConfig {\n /** API base, no trailing slash needed. e.g. https://api.verbumia.dev */\n apiBase: string;\n /** Public project UUID the widget targets. */\n projectId: string;\n /**\n * sessionId / grouping_key is MINTED SERVER-SIDE by the Verbumia\n * backend at POST /v1/feedback/tos and returned in the token bundle\n * (`TokenBundle.grouping_key`). The widget receives + sends it; it\n * MUST NOT self-generate it. There is intentionally no client config\n * field for it.\n */\n /**\n * NOTE: there is intentionally NO `tosVersion` field. The ToS version\n * is a BUILD-TIME constant baked into @verbumia/feedback at release\n * (`SDK_TOS_VERSION`, task 616) — integrators do not set it; the SDK\n * sends it automatically.\n */\n /** Optional opaque end-user id; server generates one when absent. */\n endUserId?: string;\n /** BCP-47 language the widget rates strings in (e.g. \"fr\"). */\n language: string;\n /** Optional locale tag stored for analytics. */\n locale?: string;\n /** Debounce window (ms) before a queued batch is flushed. Default 1500. */\n flushDebounceMs?: number;\n /** Max queued items before an immediate flush. Default 20. */\n maxBatch?: number;\n /** Injected fetch (tests / RN polyfills). Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface TokenBundle {\n access_token: string;\n token_type: \"Bearer\";\n expires_in: number;\n refresh_token: string;\n refresh_expires_in: number;\n end_user_id: string;\n tos_version: string;\n grouping_key: string;\n}\n\nexport interface FeedbackString {\n namespace: string;\n key: string;\n key_uuid: string;\n language_uuid: string;\n value: string;\n translation_hash: string;\n avg_stars: number | null;\n ratings_count: number;\n my_rating: number | null;\n}\n\nexport interface StringsResponse {\n project_id: string;\n language: string;\n strings: FeedbackString[];\n}\n\n/** A 5-star rating queued for a rendered string variant. */\nexport interface RatingInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n stars: number; // 1..5\n}\n\n/** A suggested alternative translation queued for moderation. */\nexport interface SuggestionInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n suggested_text: string;\n comment?: string;\n}\n\nexport interface BatchResponse {\n accepted: number;\n rejected: number;\n items: Array<Record<string, unknown>>;\n}\n\n/** A key the host app declares as on-screen. */\nexport interface DeclaredKey {\n namespace: string;\n key: string;\n}\n\nexport class FeedbackError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"FeedbackError\";\n }\n}\n","/**\n * BUILD-TIME ToS version constant (task 616, human decision option B).\n *\n * This is baked into the @verbumia/feedback PACKAGE at release. It is\n * NOT an integrator-set config field and NOT server-driven: a stale\n * (e.g. native) app must record consent to the ToS version IT shipped\n * and displayed, for legal correctness — never backend-latest.\n *\n * Releasing a ToS-TEXT change ⇒ bump this constant + cut a new\n * @verbumia/feedback release (per the human/real-CI publish handoff).\n * The backend accepts any version in its acceptable set and records it.\n */\nexport const SDK_TOS_VERSION = \"2026-05-18\";\n","/**\n * Framework-agnostic Verbumia feedback client.\n *\n * Responsibilities:\n * - versioned-ToS acceptance -> scoped end-user JWT bootstrap\n * - transparent access-token refresh (rotating refresh token)\n * - fetch on-screen strings for the widget\n * - debounced + size-capped batched POST of ratings / suggestions,\n * mirroring the missing-handler transport contract (ltm 280):\n * best-effort, never throws into the host app render path.\n *\n * The React and React Native entry points are thin UI shells over this.\n */\n\nimport {\n type BatchResponse,\n type FeedbackConfig,\n FeedbackError,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"./types\";\nimport { SDK_TOS_VERSION } from \"./tos\";\n\nconst SDK_LIB = \"@verbumia/feedback\";\nconst SDK_VER = \"0.2.0\";\n\ntype QueueItem =\n | { kind: \"rating\"; payload: RatingInput }\n | { kind: \"suggestion\"; payload: SuggestionInput };\n\nexport class FeedbackClient {\n private cfg: Required<\n Pick<FeedbackConfig, \"flushDebounceMs\" | \"maxBatch\">\n > &\n FeedbackConfig;\n private fetchImpl: typeof fetch;\n private tokens: TokenBundle | null = null;\n private queue: QueueItem[] = [];\n private timer: ReturnType<typeof setTimeout> | null = null;\n private bootstrapping: Promise<TokenBundle> | null = null;\n\n constructor(config: FeedbackConfig) {\n this.cfg = {\n flushDebounceMs: 1500,\n maxBatch: 20,\n ...config,\n };\n const f = config.fetchImpl ?? globalThis.fetch;\n if (!f) {\n throw new FeedbackError(\n \"no fetch implementation available; pass config.fetchImpl\",\n );\n }\n this.fetchImpl = f.bind(globalThis);\n }\n\n private base(): string {\n return this.cfg.apiBase.replace(/\\/+$/, \"\");\n }\n\n get endUserId(): string | undefined {\n return this.tokens?.end_user_id ?? this.cfg.endUserId;\n }\n\n get hasConsented(): boolean {\n return this.tokens !== null;\n }\n\n /** Server-minted sessionId / grouping_key (from the token bundle).\n * Available only after `acceptTos()`; never client-generated. */\n get sessionId(): string | undefined {\n return this.tokens?.grouping_key;\n }\n\n /** BCP-47 language the widget is rating strings in. */\n get language(): string {\n return this.cfg.language;\n }\n\n /** ToS version the end user is asked to accept — the SDK's\n * build-time constant (task 616). NOT integrator/server set. */\n get tosVersion(): string {\n return SDK_TOS_VERSION;\n }\n\n /**\n * Accept the ToS and bootstrap a scoped token. Idempotent: a second call\n * returns the in-flight / existing bundle rather than re-accepting.\n */\n async acceptTos(): Promise<TokenBundle> {\n if (this.tokens) return this.tokens;\n if (this.bootstrapping) return this.bootstrapping;\n this.bootstrapping = (async () => {\n const res = await this.fetchImpl(`${this.base()}/v1/feedback/tos`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n // NO grouping_key: server-minted (task 599). It comes back in\n // the token bundle and is bound into the JWT server-side.\n project_id: this.cfg.projectId,\n end_user_id: this.cfg.endUserId,\n tos_version: SDK_TOS_VERSION,\n locale: this.cfg.locale,\n }),\n });\n if (!res.ok) throw await this.problem(res, \"tos acceptance failed\");\n this.tokens = (await res.json()) as TokenBundle;\n return this.tokens;\n })();\n try {\n return await this.bootstrapping;\n } finally {\n this.bootstrapping = null;\n }\n }\n\n private async refresh(): Promise<void> {\n if (!this.tokens) throw new FeedbackError(\"not consented\");\n const res = await this.fetchImpl(\n `${this.base()}/v1/feedback/token/refresh`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: this.tokens.refresh_token }),\n },\n );\n if (!res.ok) {\n this.tokens = null; // force a fresh ToS step\n throw await this.problem(res, \"token refresh failed\");\n }\n this.tokens = (await res.json()) as TokenBundle;\n }\n\n /** Authenticated fetch with a single transparent refresh-on-401 retry. */\n private async authed(\n path: string,\n init: RequestInit,\n retry = true,\n ): Promise<Response> {\n if (!this.tokens) await this.acceptTos();\n const res = await this.fetchImpl(`${this.base()}${path}`, {\n ...init,\n headers: {\n ...(init.headers ?? {}),\n Authorization: `Bearer ${this.tokens!.access_token}`,\n },\n });\n if (res.status === 401 && retry) {\n await this.refresh();\n return this.authed(path, init, false);\n }\n return res;\n }\n\n /** Strings rendered on the current view, with this end user's prior rating. */\n async getStrings(opts?: {\n keys?: Array<{ namespace: string; key: string }>;\n namespace?: string;\n limit?: number;\n }): Promise<StringsResponse> {\n // #806 SeedSower P1 (Hermes URLSearchParams polyfill): React Native's\n // bundled URLSearchParams implements ONLY the constructor + toString().\n // `.set` / `.get` / `.append` / `.delete` / `.has` / `.entries` /\n // `.forEach` / `.keys` / `.values` / the iterator all throw on Hermes\n // (documented RN limitation since ~0.50, refused for bundle-size).\n // Build a plain Record<string, string> first, then pass to the\n // constructor — that variant is supported on Node + browser + Hermes\n // + JSC, and produces the same query string. NO mutation after\n // construction anywhere in the SDK.\n const params: Record<string, string> = { language: this.cfg.language };\n if (opts?.keys?.length) {\n params.keys = opts.keys.map((k) => `${k.namespace}:${k.key}`).join(\",\");\n }\n if (opts?.namespace) params.namespace = opts.namespace;\n if (opts?.limit) params.limit = String(opts.limit);\n const qs = new URLSearchParams(params);\n const res = await this.authed(`/v1/feedback/strings?${qs.toString()}`, {\n method: \"GET\",\n });\n if (!res.ok) throw await this.problem(res, \"failed to load strings\");\n return (await res.json()) as StringsResponse;\n }\n\n /** Queue a rating; flushed on debounce or when the batch fills. */\n rate(payload: RatingInput): void {\n this.enqueue({ kind: \"rating\", payload });\n }\n\n /** Queue a suggestion; flushed on debounce or when the batch fills. */\n suggest(payload: SuggestionInput): void {\n this.enqueue({ kind: \"suggestion\", payload });\n }\n\n private enqueue(item: QueueItem): void {\n this.queue.push(item);\n if (this.queue.length >= this.cfg.maxBatch) {\n void this.flush();\n return;\n }\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.flush(), this.cfg.flushDebounceMs);\n }\n\n /**\n * Flush queued items. Best-effort: a transport/auth failure re-queues the\n * batch once and swallows the error so the host app never sees a throw.\n */\n async flush(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n if (!this.queue.length) return;\n const batch = this.queue;\n this.queue = [];\n const ratings = batch\n .filter((b) => b.kind === \"rating\")\n .map((b) => b.payload as RatingInput);\n const suggestions = batch\n .filter((b) => b.kind === \"suggestion\")\n .map((b) => b.payload as SuggestionInput);\n try {\n if (ratings.length) {\n await this.postBatch(\"/v1/feedback/ratings\", { ratings });\n }\n if (suggestions.length) {\n await this.postBatch(\"/v1/feedback/suggestions\", { suggestions });\n }\n } catch {\n // Re-queue once; feedback must never break the host app.\n this.queue.unshift(...batch);\n }\n }\n\n private async postBatch(\n path: string,\n body: unknown,\n ): Promise<BatchResponse> {\n const res = await this.authed(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-SDK\": `${SDK_LIB}@${SDK_VER}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw await this.problem(res, \"batch post failed\");\n return (await res.json()) as BatchResponse;\n }\n\n private async problem(res: Response, fallback: string): Promise<FeedbackError> {\n let code: string | undefined;\n let detail = fallback;\n try {\n const body = (await res.json()) as { code?: string; detail?: string };\n code = body.code;\n if (body.detail) detail = body.detail;\n } catch {\n /* non-JSON body */\n }\n return new FeedbackError(detail, res.status, code);\n }\n}\n","import { createElement, useCallback, useEffect, useState, type ComponentType, type ReactNode } from \"react\";\nimport {\n KeyboardAvoidingView,\n Modal,\n Platform,\n ScrollView,\n Text,\n TextInput,\n TouchableOpacity,\n View,\n} from \"react-native\";\n\nimport type { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\n/**\n * Soft-resolve `react-native-safe-area-context`'s `SafeAreaView` (#806\n * SeedSower Android panel-layout bug). When the host installed it (Expo\n * apps ship it by default; bare RN apps that follow the standard setup do\n * too), the panel's bottom edge respects the system gesture bar / home\n * indicator. When it isn't installed (web bundles, RN apps that opted\n * out), fall back to a plain `View` — same children, same style, no\n * insets. The dependency is listed as an OPTIONAL `peerDependency` so the\n * npm install never warns on web-only consumers.\n */\ntype SafeAreaProps = { edges?: ReadonlyArray<string>; style?: unknown; children?: ReactNode };\n// Local `require` declaration so we don't pull @types/node into this\n// package. Metro / Node both inject `require` at module scope; in the\n// ESM build path tsup polyfills it via createRequire(import.meta.url).\ndeclare const require: (id: string) => unknown;\nlet SafeAreaView: ComponentType<SafeAreaProps>;\ntry {\n const mod = require(\"react-native-safe-area-context\") as {\n SafeAreaView: ComponentType<SafeAreaProps>;\n };\n SafeAreaView = mod.SafeAreaView;\n} catch {\n SafeAreaView = ({ children, style }: SafeAreaProps) =>\n createElement(\n View as ComponentType<{ style?: unknown; children?: ReactNode }>,\n { style },\n children,\n );\n}\n\nconst C = {\n bg: \"#0b0f0e\",\n panel: \"#111714\",\n border: \"#1f2a25\",\n text: \"#e7f5ef\",\n dim: \"#8aa79b\",\n emerald: \"#10b981\",\n emeraldSoft: \"#34d399\",\n};\n\nexport function FeedbackModal(props: {\n client: FeedbackClient;\n visible: boolean;\n keys?: DeclaredKey[];\n namespace?: string | string[];\n onClose: () => void;\n}) {\n const { client, visible, keys, namespace, onClose } = props;\n const [consented, setConsented] = useState(client.hasConsented);\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [strings, setStrings] = useState<FeedbackString[]>([]);\n\n const loadStrings = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n // On-screen scoping is NORMATIVE (spec ltm 373, CONTRACT v5): show\n // ONLY rendered keys. Empty -> \"no strings on this view\"; never\n // fall back to fetching the whole project.\n const resolved = resolveKeys(keys, namespace);\n if (!resolved.length) {\n // #806 SeedSower P1 — same diagnostic as the web panel: an empty\n // registry resolve with no explicit `keys` prop almost always\n // means the host's `useTranslation` imports went to\n // `react-i18next` instead of `@verbumia/react-i18next`.\n if (!keys || keys.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[verbumia/feedback] registry empty — are your useTranslation imports coming from @verbumia/react-i18next?\",\n );\n }\n setStrings([]);\n return;\n }\n const res = await client.getStrings({ keys: resolved });\n setStrings(res.strings);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load strings\");\n } finally {\n setBusy(false);\n }\n }, [client, keys, namespace]);\n\n useEffect(() => {\n if (visible && consented) void loadStrings();\n }, [visible, consented, loadStrings]);\n\n const accept = useCallback(async () => {\n setBusy(true);\n try {\n await client.acceptTos();\n setConsented(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Could not accept the terms\");\n } finally {\n setBusy(false);\n }\n }, [client]);\n\n return (\n <Modal\n visible={visible}\n transparent\n animationType=\"slide\"\n onRequestClose={onClose}\n >\n {/*\n #806 SeedSower native panel layout — KeyboardAvoidingView wraps the\n whole overlay so the suggestion TextInput isn't covered by the soft\n keyboard. `padding` on iOS / `height` on Android matches the RN\n community convention; flex:1 keeps the overlay full-screen above\n the keyboard reservation.\n */}\n <KeyboardAvoidingView\n style={{ flex: 1 }}\n behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n >\n <View style={{ flex: 1, backgroundColor: \"rgba(0,0,0,.55)\", justifyContent: \"flex-end\" }}>\n {/*\n Panel sizing — `height:'85%'` gives a CONCRETE 85% of the\n modal's parent (the full-screen overlay), which is what the\n ToS step / strings ScrollView need to flex against. The prior\n `maxHeight:'85%'` was a ceiling only: without a height/flex\n child, the panel collapsed to its ToS intrinsic content (~15%\n of screen) on RN 0.77.3 Android. `maxHeight` is kept as a\n backstop in case a future child wants to render shorter (it\n never exceeds 85%; it can render shorter if the content does).\n\n SafeAreaView edges={['bottom']} keeps the CTA off the Android\n gesture bar / iOS home indicator. Falls back to plain View\n when react-native-safe-area-context isn't installed.\n */}\n <SafeAreaView\n edges={[\"bottom\"]}\n style={{\n height: \"85%\",\n maxHeight: \"85%\",\n backgroundColor: C.bg,\n borderTopWidth: 1,\n borderColor: C.border,\n }}\n >\n <View style={{ flex: 1, padding: 18 }}>\n <View\n style={{\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n marginBottom: 12,\n }}\n >\n <Text style={{ color: C.emeraldSoft, fontWeight: \"700\", fontSize: 16 }}>\n Translation feedback\n </Text>\n <TouchableOpacity onPress={onClose} accessibilityLabel=\"Close\">\n <Text style={{ color: C.dim, fontSize: 20 }}>×</Text>\n </TouchableOpacity>\n </View>\n\n {error ? (\n <Text style={{ color: \"#f87171\", marginBottom: 8 }}>{error}</Text>\n ) : null}\n\n {!consented ? (\n <View>\n <Text style={{ color: C.text, lineHeight: 21 }}>\n Help improve the translations in this app. Your ratings and\n suggestions are sent to the app owner via Verbumia. Don’t\n submit personal or sensitive information.\n </Text>\n <TouchableOpacity\n disabled={busy}\n onPress={accept}\n style={{\n marginTop: 14,\n backgroundColor: C.emerald,\n borderRadius: 8,\n padding: 14,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n {busy ? \"…\" : `I accept the terms (v${client.tosVersion})`}\n </Text>\n </TouchableOpacity>\n </View>\n ) : (\n // flex:1 makes the ScrollView fill the remaining panel\n // height after the header; without it the list collapsed\n // to its intrinsic height and the panel left a large empty\n // area below the last row (the second half of the\n // SeedSower repro).\n <ScrollView style={{ flex: 1 }}>\n {!strings.length ? (\n <Text style={{ color: C.dim }}>\n {busy ? \"Loading…\" : \"No strings to review on this view.\"}\n </Text>\n ) : (\n strings.map((s) => (\n <StringRow\n key={`${s.namespace}:${s.key}`}\n s={s}\n client={client}\n />\n ))\n )}\n </ScrollView>\n )}\n </View>\n </SafeAreaView>\n </View>\n </KeyboardAvoidingView>\n </Modal>\n );\n}\n\nfunction StringRow(props: { s: FeedbackString; client: FeedbackClient }) {\n const { s, client } = props;\n const [mine, setMine] = useState<number | null>(s.my_rating);\n const [show, setShow] = useState(false);\n const [text, setText] = useState(\"\");\n const [sent, setSent] = useState(false);\n\n return (\n <View\n style={{\n backgroundColor: C.panel,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 10,\n padding: 12,\n marginBottom: 10,\n }}\n >\n <Text style={{ color: C.dim, fontSize: 12 }}>\n {s.namespace} · {s.key}\n </Text>\n <Text style={{ color: C.text, fontSize: 15, marginVertical: 6 }}>\n {s.value}\n </Text>\n <View style={{ flexDirection: \"row\" }}>\n {[1, 2, 3, 4, 5].map((n) => (\n <TouchableOpacity\n key={n}\n accessibilityLabel={`${n} stars`}\n onPress={() => {\n setMine(n);\n client.rate({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars: n,\n });\n }}\n >\n <Text\n style={{\n fontSize: 22,\n marginRight: 4,\n color: mine && n <= mine ? C.emeraldSoft : C.border,\n }}\n >\n ★\n </Text>\n </TouchableOpacity>\n ))}\n <TouchableOpacity\n onPress={() => setShow(!show)}\n style={{ marginLeft: \"auto\" }}\n >\n <Text style={{ color: C.emeraldSoft }}>\n {sent ? \"Suggested ✓\" : \"Suggest\"}\n </Text>\n </TouchableOpacity>\n </View>\n {show ? (\n <View style={{ marginTop: 10 }}>\n <TextInput\n value={text}\n onChangeText={setText}\n multiline\n numberOfLines={3}\n placeholder=\"Your suggested translation…\"\n placeholderTextColor={C.dim}\n style={{\n backgroundColor: C.bg,\n color: C.text,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 6,\n padding: 8,\n }}\n />\n <TouchableOpacity\n onPress={() => {\n if (!text.trim()) return;\n client.suggest({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n suggested_text: text.trim(),\n });\n setSent(true);\n setShow(false);\n setText(\"\");\n }}\n style={{\n marginTop: 6,\n backgroundColor: C.emerald,\n borderRadius: 6,\n padding: 10,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n Send suggestion\n </Text>\n </TouchableOpacity>\n </View>\n ) : null}\n </View>\n );\n}\n","/**\n * On-screen key discovery.\n *\n * Preferred source: the `@verbumia/*-i18n` SDK exposes a lightweight key\n * registry of keys it has rendered. When that registry is present on the\n * global (the i18n SDK publishes `globalThis.__verbumia_key_registry__`),\n * we read the keys touched on the current view. Otherwise the host app\n * passes an explicit `keys` list — the always-available fallback.\n *\n * The registry contract is intentionally tiny so any framework port of the\n * i18n SDK can implement it without depending on this package.\n */\n\nimport type { DeclaredKey } from \"./types\";\n\nconst REGISTRY_GLOBAL = \"__verbumia_key_registry__\";\n\ninterface KeyRegistry {\n /** Returns the keys rendered since the last `reset()` (or ever). */\n snapshot(): DeclaredKey[];\n reset?(): void;\n}\n\nfunction getRegistry(): KeyRegistry | null {\n const g = globalThis as Record<string, unknown>;\n const reg = g[REGISTRY_GLOBAL];\n if (\n reg &&\n typeof (reg as KeyRegistry).snapshot === \"function\"\n ) {\n return reg as KeyRegistry;\n }\n return null;\n}\n\n/** True when a compatible `@verbumia/*-i18n` registry is detectable. */\nexport function hasKeyRegistry(): boolean {\n return getRegistry() !== null;\n}\n\n/**\n * Resolve the on-screen keys: explicit `keys` prop always wins (it is the\n * customer's authoritative declaration); otherwise fall back to the i18n\n * registry snapshot; otherwise an empty list (widget shows \"no strings\").\n *\n * Optional `namespace` filter (task 618, CONTRACT v6 — ADDITIVE): when\n * set (a single namespace or a list), the resolved set is narrowed to\n * keys whose `namespace` is in the filter. The filter is applied AFTER\n * resolution, so it composes with v5 rendered-scoping as\n * `rendered ∩ namespace`. UNSET / empty ⇒ unchanged v5 behaviour (all\n * resolved keys). Backward-compatible: `resolveKeys(keys)` is unaffected.\n */\nexport function resolveKeys(\n explicit?: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const base =\n explicit && explicit.length\n ? dedupe(explicit)\n : getRegistry()\n ? dedupe(getRegistry()!.snapshot())\n : [];\n return filterByNamespace(base, namespace);\n}\n\n/** Narrow keys to those whose namespace is in `namespace`. An undefined\n * filter, an empty string, or an empty list means \"no filter\" (the\n * additive default — identical to v5). Exported so non-panel callers\n * (e.g. a custom /core integration) can apply the same semantics. */\nexport function filterByNamespace(\n keys: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const list = (Array.isArray(namespace) ? namespace : namespace ? [namespace] : [])\n .map((n) => n.trim())\n .filter(Boolean);\n if (!list.length) return keys;\n const allow = new Set(list);\n return keys.filter((k) => allow.has(k.namespace));\n}\n\nfunction dedupe(keys: DeclaredKey[]): DeclaredKey[] {\n const seen = new Set<string>();\n const out: DeclaredKey[] = [];\n for (const k of keys) {\n const id = `${k.namespace}:${k.key}`;\n if (!seen.has(id)) {\n seen.add(id);\n out.push(k);\n }\n }\n return out;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../../src/native/index.ts","../../src/native/plugin.tsx","../../src/core/types.ts","../../src/core/tos.ts","../../src/core/client.ts","../../src/native/panel.tsx","../../src/core/keys.ts"],"sourcesContent":["export {\n feedbackPlugin,\n type FeedbackController,\n type FeedbackPluginOptions,\n type I18nPlugin,\n type I18nPluginContext,\n} from \"./plugin\";\nexport { FeedbackModal } from \"./panel\";\nexport {\n FeedbackClient,\n hasKeyRegistry,\n resolveKeys,\n type BatchResponse,\n type DeclaredKey,\n type FeedbackConfig,\n FeedbackError,\n type FeedbackString,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"../core\";\n","/**\n * `@verbumia/feedback` (React Native / Expo) as a PLUGIN of the\n * `@verbumia/*-i18n` provider — same architecture as the web plugin\n * (task 599): no second context; isolated sibling outlet; imperative\n * controller for the host CTA; sessionId is server-minted. Native uses\n * an RN <Modal> (its own overlay) toggled by a private store, so the\n * host app never re-renders when feedback opens.\n */\nimport { useSyncExternalStore, type ReactNode } from \"react\";\n\nimport { FeedbackClient } from \"../core/client\";\nimport type { DeclaredKey, FeedbackAddonState } from \"../core/types\";\nimport { FeedbackModal } from \"./panel\";\n\nexport interface I18nPluginContext {\n i18n?: {\n language?: string;\n /** 0.2.7 — direct i18next handle used by the `scope: \"current-view\"`\n * snapshot path (see /react plugin for full notes). Optional. */\n i18next?: {\n language?: string;\n changeLanguage?: (lng: string) => Promise<unknown>;\n };\n };\n config: {\n apiBase?: string;\n projectUuid: string;\n defaultLocale: string;\n };\n /** #806 — subscribe to runtime language changes (see\n * VerbumiaPluginContext in `@verbumia/react-i18next` ≥1.0.5). Returns\n * an unsubscribe fn the plugin MUST call from teardown. Optional so a\n * pre-1.0.5 react-i18next falls back gracefully. */\n onLanguageChange?: (cb: (lng: string) => void) => () => void;\n}\nexport interface I18nPlugin {\n name: string;\n setup?: (ctx: I18nPluginContext) => void | (() => void);\n render?: () => ReactNode;\n}\n\nexport interface FeedbackController {\n /** From 0.2.7 `open()` is async (Promise<void>) so the\n * `scope: \"current-view\"` snapshot sequence can run before the modal\n * mounts. Hosts that don't await still work; the modal mounts when\n * the promise resolves. */\n open: () => Promise<void>;\n close: () => void;\n client: FeedbackClient;\n /** 0.2.7 — current ToS version. */\n readonly tosVersion: string;\n /** 0.2.7 — whether this end-user has a live session-token bundle. */\n readonly hasAcceptedTos: boolean;\n /** 0.2.7 — programmatically POST `/v1/feedback/tos` (idempotent). Used\n * by hosts that build their own ToS page (`tos: \"skip\"`). */\n acceptTos: () => Promise<void>;\n /** 0.2.7 — addon-state-derived flag (see /react plugin docs).\n * `null` before the first state fetch resolves. */\n readonly isActive: boolean | null;\n /** 0.2.7 — `state.enabledLanguages` (string[] | \"all\" | null). */\n readonly enabledLanguages: string[] | \"all\" | null;\n /** 0.2.7 — raw SKU code. */\n readonly sku: \"feedback_starter\" | \"feedback_unlimited\" | null;\n}\n\nexport interface FeedbackPluginOptions {\n // No `tosVersion` — SDK build-time constant (SDK_TOS_VERSION, task 616).\n language?: string;\n apiBase?: string;\n projectId?: string;\n endUserId?: string;\n keys?: DeclaredKey[];\n /** Optional namespace filter (CONTRACT v6, additive). Single ns or\n * list; applied AFTER rendered-scoping (`rendered ∩ namespace`).\n * Unset ⇒ all resolved keys (v5 behaviour). */\n namespace?: string | string[];\n onReady?: (controller: FeedbackController) => void;\n controllerRef?: { current: FeedbackController | null };\n fetchImpl?: typeof fetch;\n /** 0.2.7 — `\"modal\"` (default, BC) keeps the built-in ToS step;\n * `\"skip\"` removes it (host owns acceptance via\n * `controller.acceptTos()`); /strings 401s render via the existing\n * error state until acceptance lands. */\n tos?: \"modal\" | \"skip\";\n /** 0.2.7 — host's project API key (`vrb_live_…`, scope project:read);\n * enables addon-state fetch at setup time. */\n apiKey?: string;\n /** 0.2.7 — controls `controller.isActive` (see /react plugin docs). */\n cta?: \"auto\" | \"show\" | \"hide\";\n /** 0.2.7 — on-screen-key set the panel reads (see /react plugin\n * docs). Default `\"current-view\"` (controller.open() resets the\n * registry, force-emits a same-locale languageChanged, waits 1\n * frame, snapshots). `\"all\"` preserves the pre-0.2.7 accumulator\n * view. */\n scope?: \"current-view\" | \"all\";\n}\n\n/** 0.2.7 panel store — see /react plugin docs. */\ntype PanelState = {\n isOpen: boolean;\n snapshotKeys: DeclaredKey[] | undefined;\n};\nfunction makeStore() {\n let state: PanelState = { isOpen: false, snapshotKeys: undefined };\n const listeners = new Set<() => void>();\n return {\n getState: (): PanelState => state,\n setOpen(open: boolean, snapshotKeys?: DeclaredKey[]): void {\n const next: PanelState = {\n isOpen: open,\n snapshotKeys: open ? snapshotKeys : undefined,\n };\n if (\n state.isOpen === next.isOpen &&\n state.snapshotKeys === next.snapshotKeys\n ) {\n return;\n }\n state = next;\n listeners.forEach((l) => l());\n },\n subscribe(l: () => void): () => void {\n listeners.add(l);\n return () => {\n listeners.delete(l);\n };\n },\n };\n}\n\n/** 0.2.7 — `scope: \"current-view\"` snapshot. See /react plugin for\n * the full rationale + known limitation. On native (Hermes) we yield\n * via `setTimeout(16)` since `requestAnimationFrame` is rarely\n * injected by Hermes runtimes. */\nasync function captureCurrentViewSnapshot(\n i18next:\n | {\n language?: string;\n changeLanguage?: (lng: string) => Promise<unknown>;\n }\n | undefined,\n): Promise<DeclaredKey[]> {\n type Reg = {\n snapshot: () => DeclaredKey[];\n reset?: () => void;\n };\n const reg = (globalThis as Record<string, unknown>)\n .__verbumia_key_registry__ as Reg | undefined;\n if (!reg) return [];\n const lng = i18next?.language;\n const change = i18next?.changeLanguage;\n if (typeof change === \"function\" && typeof lng === \"string\" && lng) {\n reg.reset?.();\n try {\n await change(lng);\n } catch {\n // best-effort\n }\n await new Promise<void>((r) => {\n if (typeof requestAnimationFrame === \"function\") {\n requestAnimationFrame(() => r());\n } else {\n setTimeout(() => r(), 16);\n }\n });\n return reg.snapshot();\n }\n return reg.snapshot();\n}\n\nexport function feedbackPlugin(options: FeedbackPluginOptions): I18nPlugin {\n const store = makeStore();\n let client: FeedbackClient | null = null;\n\n function Outlet() {\n const state = useSyncExternalStore(\n store.subscribe,\n store.getState,\n store.getState,\n );\n if (!client) return null;\n const c = client;\n // Precedence: options.keys > scope='current-view' snapshot > registry.\n const panelKeys = options.keys ?? state.snapshotKeys;\n return (\n <FeedbackModal\n client={c}\n visible={state.isOpen}\n keys={panelKeys}\n namespace={options.namespace}\n tos={options.tos ?? \"modal\"}\n onClose={() => {\n store.setOpen(false);\n void c.flush();\n }}\n />\n );\n }\n\n return {\n name: \"@verbumia/feedback\",\n setup(ctx) {\n // #806 SeedSower lang-change init source — same precedence as the\n // /react plugin: explicit option, then current i18n language,\n // then boot defaultLocale.\n const initialLanguage =\n options.language ?? ctx.i18n?.language ?? ctx.config.defaultLocale;\n // 0.2.7 ToS: `tos: \"skip\"` flips `autoAcceptTos: false` so the\n // client refuses to silently POST /v1/feedback/tos on the user's\n // behalf — the host owns acceptance via `controller.acceptTos()`.\n const tos: \"modal\" | \"skip\" = options.tos ?? \"modal\";\n client = new FeedbackClient({\n apiBase:\n options.apiBase ?? ctx.config.apiBase ?? \"https://api.verbumia.dev\",\n projectId: options.projectId ?? ctx.config.projectUuid,\n language: initialLanguage,\n endUserId: options.endUserId,\n fetchImpl: options.fetchImpl,\n apiKey: options.apiKey,\n autoAcceptTos: tos !== \"skip\",\n });\n const clientRef = client;\n // 0.2.7 addon-state cache — same semantics as the /react plugin.\n let addonState: FeedbackAddonState | null = null;\n const cta: \"auto\" | \"show\" | \"hide\" = options.cta ?? \"auto\";\n // 0.2.7 scope (default \"current-view\" — see plugin docs).\n const scope: \"current-view\" | \"all\" = options.scope ?? \"current-view\";\n const ctxI18next = ctx.i18n?.i18next;\n const refreshState = async (): Promise<void> => {\n try {\n const next = await clientRef.getAddonState();\n if (next !== null) addonState = next;\n } catch {\n // Pre-acceptTos + no apiKey path — defer until acceptTos().\n }\n };\n if (options.apiKey) void refreshState();\n // #806 — re-sync the client + refresh addon-state on lang change.\n let langUnsub: (() => void) | undefined;\n if (typeof ctx.onLanguageChange === \"function\") {\n langUnsub = ctx.onLanguageChange((lng) => {\n clientRef.setLanguage(lng);\n void refreshState();\n });\n }\n const controller: FeedbackController = {\n open: async (): Promise<void> => {\n if (scope === \"current-view\") {\n const snapshot = await captureCurrentViewSnapshot(ctxI18next);\n store.setOpen(true, snapshot);\n return;\n }\n store.setOpen(true);\n },\n close: () => {\n store.setOpen(false);\n void clientRef?.flush();\n },\n client: clientRef,\n get tosVersion(): string {\n return clientRef.tosVersion;\n },\n get hasAcceptedTos(): boolean {\n return clientRef.hasAcceptedTos;\n },\n acceptTos: async (): Promise<void> => {\n await clientRef.acceptTos();\n if (!options.apiKey) await refreshState();\n },\n get isActive(): boolean | null {\n if (cta === \"show\") return true;\n if (cta === \"hide\") return false;\n return addonState ? addonState.isActive : null;\n },\n get enabledLanguages(): string[] | \"all\" | null {\n return addonState ? addonState.enabledLanguages : null;\n },\n get sku(): \"feedback_starter\" | \"feedback_unlimited\" | null {\n return addonState ? addonState.sku : null;\n },\n };\n options.onReady?.(controller);\n if (options.controllerRef) options.controllerRef.current = controller;\n return () => {\n langUnsub?.();\n if (options.controllerRef) options.controllerRef.current = null;\n void client?.flush();\n };\n },\n render: () => <Outlet />,\n };\n}\n","/**\n * Wire types for the Verbumia End-User Translation Evaluation API.\n * Canonical reference: ./CONTRACT.md (frozen). Backend task 591.\n */\n\nexport interface FeedbackConfig {\n /** API base, no trailing slash needed. e.g. https://api.verbumia.dev */\n apiBase: string;\n /** Public project UUID the widget targets. */\n projectId: string;\n /**\n * sessionId / grouping_key is MINTED SERVER-SIDE by the Verbumia\n * backend at POST /v1/feedback/tos and returned in the token bundle\n * (`TokenBundle.grouping_key`). The widget receives + sends it; it\n * MUST NOT self-generate it. There is intentionally no client config\n * field for it.\n */\n /**\n * NOTE: there is intentionally NO `tosVersion` field. The ToS version\n * is a BUILD-TIME constant baked into @verbumia/feedback at release\n * (`SDK_TOS_VERSION`, task 616) — integrators do not set it; the SDK\n * sends it automatically.\n */\n /** Optional opaque end-user id; server generates one when absent. */\n endUserId?: string;\n /** BCP-47 language the widget rates strings in (e.g. \"fr\"). */\n language: string;\n /** Optional locale tag stored for analytics. */\n locale?: string;\n /** Debounce window (ms) before a queued batch is flushed. Default 1500. */\n flushDebounceMs?: number;\n /** Max queued items before an immediate flush. Default 20. */\n maxBatch?: number;\n /** Injected fetch (tests / RN polyfills). Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /**\n * 0.2.7 — host's project API key (`vrb_live_…`, scope `project:read`).\n * When set, the client can call addon-state at setup time (BEFORE\n * the end-user accepts ToS) using `Authorization: ApiKey …` — mirrors\n * the existing scheme used by the i18n SDK's dev-env loaders. When\n * unset, addon-state is deferred until the user's session bearer is\n * minted via `acceptTos()`.\n */\n apiKey?: string;\n /**\n * #806 / 0.2.7 — when `false`, the client must NOT silently call\n * `POST /v1/feedback/tos` to bootstrap a session on the user's behalf.\n * Set by the plugin when the host opts into `tos: \"skip\"` (the host\n * promises to handle consent externally via `controller.acceptTos()`).\n * `authed()` then throws `FeedbackError(\"not consented\")` instead of\n * auto-accepting — the panel surfaces the existing error state.\n * Default `true` preserves all pre-0.2.7 behaviour.\n */\n autoAcceptTos?: boolean;\n}\n\nexport interface TokenBundle {\n access_token: string;\n token_type: \"Bearer\";\n expires_in: number;\n refresh_token: string;\n refresh_expires_in: number;\n end_user_id: string;\n tos_version: string;\n grouping_key: string;\n}\n\nexport interface FeedbackString {\n namespace: string;\n key: string;\n key_uuid: string;\n language_uuid: string;\n value: string;\n translation_hash: string;\n avg_stars: number | null;\n ratings_count: number;\n my_rating: number | null;\n}\n\nexport interface StringsResponse {\n project_id: string;\n language: string;\n strings: FeedbackString[];\n}\n\n/**\n * 0.2.7 — addon state for a project, returned by\n * `GET /v1/projects/{projectId}/feedback-addon/state`. Drives the\n * controller surface (`isActive`, `enabledLanguages`, `sku`,\n * `languagesLimit`) so the host can hide its own CTA on Starter +\n * narrow the widget to enabled languages.\n *\n * Backend contract (camelCase aliases, populate_by_name=true on the\n * server schema, confirmed against backend task 836 SHA d78b939 + the\n * follow-up PR that extends the auth surface to accept project API\n * keys with `project:read` scope):\n * - `isActive`: org.feedback_addon.active\n * - `sku`: the SKU code, or `null` if no active SKU\n * - `enabledLanguages`: the project's allow-list, or literal `\"all\"`\n * when sku === `\"feedback_unlimited\"`; an empty array when no SKU\n * - `languagesLimit`: hard cap for Starter (3), null otherwise\n */\nexport interface FeedbackAddonState {\n isActive: boolean;\n sku: \"feedback_starter\" | \"feedback_unlimited\" | null;\n enabledLanguages: string[] | \"all\";\n languagesLimit: number | null;\n}\n\n/** A 5-star rating queued for a rendered string variant. */\nexport interface RatingInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n stars: number; // 1..5\n}\n\n/** A suggested alternative translation queued for moderation. */\nexport interface SuggestionInput {\n namespace: string;\n key: string;\n language: string;\n translation_hash: string;\n suggested_text: string;\n comment?: string;\n}\n\nexport interface BatchResponse {\n accepted: number;\n rejected: number;\n items: Array<Record<string, unknown>>;\n}\n\n/** A key the host app declares as on-screen. */\nexport interface DeclaredKey {\n namespace: string;\n key: string;\n}\n\nexport class FeedbackError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly code?: string,\n ) {\n super(message);\n this.name = \"FeedbackError\";\n }\n}\n","/**\n * BUILD-TIME ToS version constant (task 616, human decision option B).\n *\n * This is baked into the @verbumia/feedback PACKAGE at release. It is\n * NOT an integrator-set config field and NOT server-driven: a stale\n * (e.g. native) app must record consent to the ToS version IT shipped\n * and displayed, for legal correctness — never backend-latest.\n *\n * Releasing a ToS-TEXT change ⇒ bump this constant + cut a new\n * @verbumia/feedback release (per the human/real-CI publish handoff).\n * The backend accepts any version in its acceptable set and records it.\n */\nexport const SDK_TOS_VERSION = \"2026-05-18\";\n","/**\n * Framework-agnostic Verbumia feedback client.\n *\n * Responsibilities:\n * - versioned-ToS acceptance -> scoped end-user JWT bootstrap\n * - transparent access-token refresh (rotating refresh token)\n * - fetch on-screen strings for the widget\n * - debounced + size-capped batched POST of ratings / suggestions,\n * mirroring the missing-handler transport contract (ltm 280):\n * best-effort, never throws into the host app render path.\n *\n * The React and React Native entry points are thin UI shells over this.\n */\n\nimport {\n type BatchResponse,\n type FeedbackAddonState,\n type FeedbackConfig,\n FeedbackError,\n type RatingInput,\n type StringsResponse,\n type SuggestionInput,\n type TokenBundle,\n} from \"./types\";\nimport { SDK_TOS_VERSION } from \"./tos\";\n\nconst SDK_LIB = \"@verbumia/feedback\";\nconst SDK_VER = \"0.2.0\";\n\ntype QueueItem =\n | { kind: \"rating\"; payload: RatingInput }\n | { kind: \"suggestion\"; payload: SuggestionInput };\n\nexport class FeedbackClient {\n private cfg: Required<\n Pick<FeedbackConfig, \"flushDebounceMs\" | \"maxBatch\">\n > &\n FeedbackConfig;\n private fetchImpl: typeof fetch;\n private tokens: TokenBundle | null = null;\n private queue: QueueItem[] = [];\n private timer: ReturnType<typeof setTimeout> | null = null;\n private bootstrapping: Promise<TokenBundle> | null = null;\n\n constructor(config: FeedbackConfig) {\n this.cfg = {\n flushDebounceMs: 1500,\n maxBatch: 20,\n ...config,\n };\n const f = config.fetchImpl ?? globalThis.fetch;\n if (!f) {\n throw new FeedbackError(\n \"no fetch implementation available; pass config.fetchImpl\",\n );\n }\n this.fetchImpl = f.bind(globalThis);\n }\n\n private base(): string {\n return this.cfg.apiBase.replace(/\\/+$/, \"\");\n }\n\n get endUserId(): string | undefined {\n return this.tokens?.end_user_id ?? this.cfg.endUserId;\n }\n\n get hasConsented(): boolean {\n return this.tokens !== null;\n }\n\n /** Alias of `hasConsented` exposed under the 0.2.7 naming used by the\n * `controller.hasAcceptedTos` getter (the SDK's built-in modal and a\n * host's external ToS page share the same persisted token bundle, so\n * both flip this boolean once a session is bootstrapped). */\n get hasAcceptedTos(): boolean {\n return this.tokens !== null;\n }\n\n /** Server-minted sessionId / grouping_key (from the token bundle).\n * Available only after `acceptTos()`; never client-generated. */\n get sessionId(): string | undefined {\n return this.tokens?.grouping_key;\n }\n\n /**\n * 0.2.7 — `GET /v1/projects/{projectId}/feedback-addon/state`.\n *\n * Auth selection (matches backend's dual-acceptance after task 836's\n * follow-up PR — see CONTRACT note in the response type):\n * - If `cfg.apiKey` is set → `Authorization: ApiKey <key>` (so the\n * plugin can fetch state at `setup()` time, before the user has\n * accepted ToS).\n * - Else if a user-session bundle exists → `Authorization: Bearer\n * <access_token>` (the deferred-after-acceptTos path).\n * - Else → throw `FeedbackError(\"addon state requires apiKey or\n * acceptTos\")`. The plugin's setup catches and surfaces a\n * console.warn the first time, then re-attempts on the\n * bundle-mint that follows `acceptTos()`.\n *\n * Best-effort: a transport error returns `null` instead of throwing,\n * so the controller's `isActive` falls back to whatever was last\n * known (or its initial value) without flipping the host's CTA into\n * an inconsistent state on a transient blip.\n */\n async getAddonState(): Promise<FeedbackAddonState | null> {\n let auth: string | undefined;\n if (this.cfg.apiKey) {\n auth = `ApiKey ${this.cfg.apiKey}`;\n } else if (this.tokens) {\n auth = `Bearer ${this.tokens.access_token}`;\n } else {\n throw new FeedbackError(\n \"addon state requires apiKey or acceptTos\",\n );\n }\n const url = `${this.base()}/v1/projects/${this.cfg.projectId}/feedback-addon/state`;\n try {\n const res = await this.fetchImpl(url, {\n method: \"GET\",\n headers: { Authorization: auth },\n });\n if (!res.ok) return null;\n return (await res.json()) as FeedbackAddonState;\n } catch {\n return null;\n }\n }\n\n /** BCP-47 language the widget is rating strings in. */\n get language(): string {\n return this.cfg.language;\n }\n\n /**\n * Re-target the widget's working language at runtime (#806 SeedSower\n * lang-change propagation). Subsequent `getStrings` / `rate` /\n * `suggest` use the new language without rebuilding the client. The\n * server-minted ToS bundle has no `locale` field, so a language flip\n * does NOT invalidate consent — only the next `?language=` query\n * needs updating. The i18n provider plugin (feedback ≥0.2.6)\n * subscribes to `@verbumia/react-i18next` ≥1.0.5's\n * `VerbumiaPluginContext.onLanguageChange` and calls this on every\n * runtime language change so the host never has to wire it manually.\n */\n setLanguage(lang: string): void {\n if (typeof lang !== \"string\" || lang.length === 0) return;\n if (lang === this.cfg.language) return;\n this.cfg = { ...this.cfg, language: lang };\n }\n\n /** ToS version the end user is asked to accept — the SDK's\n * build-time constant (task 616). NOT integrator/server set. */\n get tosVersion(): string {\n return SDK_TOS_VERSION;\n }\n\n /**\n * Accept the ToS and bootstrap a scoped token. Idempotent: a second call\n * returns the in-flight / existing bundle rather than re-accepting.\n */\n async acceptTos(): Promise<TokenBundle> {\n if (this.tokens) return this.tokens;\n if (this.bootstrapping) return this.bootstrapping;\n this.bootstrapping = (async () => {\n const res = await this.fetchImpl(`${this.base()}/v1/feedback/tos`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n // NO grouping_key: server-minted (task 599). It comes back in\n // the token bundle and is bound into the JWT server-side.\n project_id: this.cfg.projectId,\n end_user_id: this.cfg.endUserId,\n tos_version: SDK_TOS_VERSION,\n locale: this.cfg.locale,\n }),\n });\n if (!res.ok) throw await this.problem(res, \"tos acceptance failed\");\n this.tokens = (await res.json()) as TokenBundle;\n return this.tokens;\n })();\n try {\n return await this.bootstrapping;\n } finally {\n this.bootstrapping = null;\n }\n }\n\n private async refresh(): Promise<void> {\n if (!this.tokens) throw new FeedbackError(\"not consented\");\n const res = await this.fetchImpl(\n `${this.base()}/v1/feedback/token/refresh`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refresh_token: this.tokens.refresh_token }),\n },\n );\n if (!res.ok) {\n this.tokens = null; // force a fresh ToS step\n throw await this.problem(res, \"token refresh failed\");\n }\n this.tokens = (await res.json()) as TokenBundle;\n }\n\n /** Authenticated fetch with a single transparent refresh-on-401 retry.\n * When `cfg.autoAcceptTos === false` (the plugin's `tos: \"skip\"` opt-in\n * / 0.2.7) and no token has been minted yet, throws a `not consented`\n * error instead of silently calling `acceptTos()` — the host promises\n * to handle consent externally via `controller.acceptTos()`. */\n private async authed(\n path: string,\n init: RequestInit,\n retry = true,\n ): Promise<Response> {\n if (!this.tokens) {\n if (this.cfg.autoAcceptTos === false) {\n throw new FeedbackError(\"not consented\");\n }\n await this.acceptTos();\n }\n const res = await this.fetchImpl(`${this.base()}${path}`, {\n ...init,\n headers: {\n ...(init.headers ?? {}),\n Authorization: `Bearer ${this.tokens!.access_token}`,\n },\n });\n if (res.status === 401 && retry) {\n await this.refresh();\n return this.authed(path, init, false);\n }\n return res;\n }\n\n /** Strings rendered on the current view, with this end user's prior rating. */\n async getStrings(opts?: {\n keys?: Array<{ namespace: string; key: string }>;\n namespace?: string;\n limit?: number;\n }): Promise<StringsResponse> {\n // #806 SeedSower P1 (Hermes URLSearchParams polyfill): React Native's\n // bundled URLSearchParams implements ONLY the constructor + toString().\n // `.set` / `.get` / `.append` / `.delete` / `.has` / `.entries` /\n // `.forEach` / `.keys` / `.values` / the iterator all throw on Hermes\n // (documented RN limitation since ~0.50, refused for bundle-size).\n // Build a plain Record<string, string> first, then pass to the\n // constructor — that variant is supported on Node + browser + Hermes\n // + JSC, and produces the same query string. NO mutation after\n // construction anywhere in the SDK.\n const params: Record<string, string> = { language: this.cfg.language };\n if (opts?.keys?.length) {\n params.keys = opts.keys.map((k) => `${k.namespace}:${k.key}`).join(\",\");\n }\n if (opts?.namespace) params.namespace = opts.namespace;\n if (opts?.limit) params.limit = String(opts.limit);\n const qs = new URLSearchParams(params);\n const res = await this.authed(`/v1/feedback/strings?${qs.toString()}`, {\n method: \"GET\",\n });\n if (!res.ok) throw await this.problem(res, \"failed to load strings\");\n return (await res.json()) as StringsResponse;\n }\n\n /** Queue a rating; flushed on debounce or when the batch fills. */\n rate(payload: RatingInput): void {\n this.enqueue({ kind: \"rating\", payload });\n }\n\n /** Queue a suggestion; flushed on debounce or when the batch fills. */\n suggest(payload: SuggestionInput): void {\n this.enqueue({ kind: \"suggestion\", payload });\n }\n\n private enqueue(item: QueueItem): void {\n this.queue.push(item);\n if (this.queue.length >= this.cfg.maxBatch) {\n void this.flush();\n return;\n }\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.flush(), this.cfg.flushDebounceMs);\n }\n\n /**\n * Flush queued items. Best-effort: a transport/auth failure re-queues the\n * batch once and swallows the error so the host app never sees a throw.\n */\n async flush(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n if (!this.queue.length) return;\n const batch = this.queue;\n this.queue = [];\n const ratings = batch\n .filter((b) => b.kind === \"rating\")\n .map((b) => b.payload as RatingInput);\n const suggestions = batch\n .filter((b) => b.kind === \"suggestion\")\n .map((b) => b.payload as SuggestionInput);\n try {\n if (ratings.length) {\n await this.postBatch(\"/v1/feedback/ratings\", { ratings });\n }\n if (suggestions.length) {\n await this.postBatch(\"/v1/feedback/suggestions\", { suggestions });\n }\n } catch {\n // Re-queue once; feedback must never break the host app.\n this.queue.unshift(...batch);\n }\n }\n\n private async postBatch(\n path: string,\n body: unknown,\n ): Promise<BatchResponse> {\n const res = await this.authed(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-SDK\": `${SDK_LIB}@${SDK_VER}` },\n body: JSON.stringify(body),\n });\n if (!res.ok) throw await this.problem(res, \"batch post failed\");\n return (await res.json()) as BatchResponse;\n }\n\n private async problem(res: Response, fallback: string): Promise<FeedbackError> {\n let code: string | undefined;\n let detail = fallback;\n try {\n const body = (await res.json()) as { code?: string; detail?: string };\n code = body.code;\n if (body.detail) detail = body.detail;\n } catch {\n /* non-JSON body */\n }\n return new FeedbackError(detail, res.status, code);\n }\n}\n","import { createElement, useCallback, useEffect, useState, type ComponentType, type ReactNode } from \"react\";\nimport {\n KeyboardAvoidingView,\n Modal,\n Platform,\n ScrollView,\n Text,\n TextInput,\n TouchableOpacity,\n View,\n} from \"react-native\";\n\nimport type { FeedbackClient } from \"../core/client\";\nimport { resolveKeys } from \"../core/keys\";\nimport type { DeclaredKey, FeedbackString } from \"../core/types\";\n\n/**\n * Soft-resolve `react-native-safe-area-context`'s `SafeAreaView` (#806\n * SeedSower Android panel-layout bug). When the host installed it (Expo\n * apps ship it by default; bare RN apps that follow the standard setup do\n * too), the panel's bottom edge respects the system gesture bar / home\n * indicator. When it isn't installed (web bundles, RN apps that opted\n * out), fall back to a plain `View` — same children, same style, no\n * insets. The dependency is listed as an OPTIONAL `peerDependency` so the\n * npm install never warns on web-only consumers.\n */\ntype SafeAreaProps = { edges?: ReadonlyArray<string>; style?: unknown; children?: ReactNode };\n// Local `require` declaration so we don't pull @types/node into this\n// package. Metro / Node both inject `require` at module scope; in the\n// ESM build path tsup polyfills it via createRequire(import.meta.url).\ndeclare const require: (id: string) => unknown;\nlet SafeAreaView: ComponentType<SafeAreaProps>;\ntry {\n const mod = require(\"react-native-safe-area-context\") as {\n SafeAreaView: ComponentType<SafeAreaProps>;\n };\n SafeAreaView = mod.SafeAreaView;\n} catch {\n SafeAreaView = ({ children, style }: SafeAreaProps) =>\n createElement(\n View as ComponentType<{ style?: unknown; children?: ReactNode }>,\n { style },\n children,\n );\n}\n\nconst C = {\n bg: \"#0b0f0e\",\n panel: \"#111714\",\n border: \"#1f2a25\",\n text: \"#e7f5ef\",\n dim: \"#8aa79b\",\n emerald: \"#10b981\",\n emeraldSoft: \"#34d399\",\n};\n\nexport function FeedbackModal(props: {\n client: FeedbackClient;\n visible: boolean;\n keys?: DeclaredKey[];\n namespace?: string | string[];\n /** 0.2.7 — `\"skip\"` removes the built-in ToS step; the host owns\n * acceptance via `controller.acceptTos()`. A missing token surfaces\n * as a 401-derived error in the existing error row. */\n tos?: \"modal\" | \"skip\";\n onClose: () => void;\n}) {\n const { client, visible, keys, namespace, tos = \"modal\", onClose } = props;\n const [consented, setConsented] = useState(\n tos === \"skip\" ? true : client.hasConsented,\n );\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [strings, setStrings] = useState<FeedbackString[]>([]);\n\n const loadStrings = useCallback(async () => {\n setBusy(true);\n setError(null);\n try {\n // On-screen scoping is NORMATIVE (spec ltm 373, CONTRACT v5): show\n // ONLY rendered keys. Empty -> \"no strings on this view\"; never\n // fall back to fetching the whole project.\n const resolved = resolveKeys(keys, namespace);\n if (!resolved.length) {\n // #806 SeedSower P1 — same diagnostic as the web panel: an empty\n // registry resolve with no explicit `keys` prop almost always\n // means the host's `useTranslation` imports went to\n // `react-i18next` instead of `@verbumia/react-i18next`.\n if (!keys || keys.length === 0) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[verbumia/feedback] registry empty — are your useTranslation imports coming from @verbumia/react-i18next?\",\n );\n }\n setStrings([]);\n return;\n }\n const res = await client.getStrings({ keys: resolved });\n setStrings(res.strings);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load strings\");\n } finally {\n setBusy(false);\n }\n }, [client, keys, namespace]);\n\n useEffect(() => {\n if (visible && consented) void loadStrings();\n }, [visible, consented, loadStrings]);\n\n const accept = useCallback(async () => {\n setBusy(true);\n try {\n await client.acceptTos();\n setConsented(true);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Could not accept the terms\");\n } finally {\n setBusy(false);\n }\n }, [client]);\n\n return (\n <Modal\n visible={visible}\n transparent\n animationType=\"slide\"\n onRequestClose={onClose}\n >\n {/*\n #806 SeedSower native panel layout — KeyboardAvoidingView wraps the\n whole overlay so the suggestion TextInput isn't covered by the soft\n keyboard. `padding` on iOS / `height` on Android matches the RN\n community convention; flex:1 keeps the overlay full-screen above\n the keyboard reservation.\n */}\n <KeyboardAvoidingView\n style={{ flex: 1 }}\n behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n >\n <View style={{ flex: 1, backgroundColor: \"rgba(0,0,0,.55)\", justifyContent: \"flex-end\" }}>\n {/*\n Panel sizing — `height:'85%'` gives a CONCRETE 85% of the\n modal's parent (the full-screen overlay), which is what the\n ToS step / strings ScrollView need to flex against. The prior\n `maxHeight:'85%'` was a ceiling only: without a height/flex\n child, the panel collapsed to its ToS intrinsic content (~15%\n of screen) on RN 0.77.3 Android. `maxHeight` is kept as a\n backstop in case a future child wants to render shorter (it\n never exceeds 85%; it can render shorter if the content does).\n\n SafeAreaView edges={['bottom']} keeps the CTA off the Android\n gesture bar / iOS home indicator. Falls back to plain View\n when react-native-safe-area-context isn't installed.\n */}\n <SafeAreaView\n edges={[\"bottom\"]}\n style={{\n height: \"85%\",\n maxHeight: \"85%\",\n backgroundColor: C.bg,\n borderTopWidth: 1,\n borderColor: C.border,\n }}\n >\n <View style={{ flex: 1, padding: 18 }}>\n <View\n style={{\n flexDirection: \"row\",\n justifyContent: \"space-between\",\n marginBottom: 12,\n }}\n >\n <Text style={{ color: C.emeraldSoft, fontWeight: \"700\", fontSize: 16 }}>\n Translation feedback\n </Text>\n <TouchableOpacity onPress={onClose} accessibilityLabel=\"Close\">\n <Text style={{ color: C.dim, fontSize: 20 }}>×</Text>\n </TouchableOpacity>\n </View>\n\n {error ? (\n <Text style={{ color: \"#f87171\", marginBottom: 8 }}>{error}</Text>\n ) : null}\n\n {!consented ? (\n <View>\n <Text style={{ color: C.text, lineHeight: 21 }}>\n Help improve the translations in this app. Your ratings and\n suggestions are sent to the app owner via Verbumia. Don’t\n submit personal or sensitive information.\n </Text>\n <TouchableOpacity\n disabled={busy}\n onPress={accept}\n style={{\n marginTop: 14,\n backgroundColor: C.emerald,\n borderRadius: 8,\n padding: 14,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n {busy ? \"…\" : `I accept the terms (v${client.tosVersion})`}\n </Text>\n </TouchableOpacity>\n </View>\n ) : (\n // flex:1 makes the ScrollView fill the remaining panel\n // height after the header; without it the list collapsed\n // to its intrinsic height and the panel left a large empty\n // area below the last row (the second half of the\n // SeedSower repro).\n <ScrollView style={{ flex: 1 }}>\n {!strings.length ? (\n <Text style={{ color: C.dim }}>\n {busy ? \"Loading…\" : \"No strings to review on this view.\"}\n </Text>\n ) : (\n strings.map((s) => (\n <StringRow\n key={`${s.namespace}:${s.key}`}\n s={s}\n client={client}\n />\n ))\n )}\n </ScrollView>\n )}\n </View>\n </SafeAreaView>\n </View>\n </KeyboardAvoidingView>\n </Modal>\n );\n}\n\nfunction StringRow(props: { s: FeedbackString; client: FeedbackClient }) {\n const { s, client } = props;\n const [mine, setMine] = useState<number | null>(s.my_rating);\n const [show, setShow] = useState(false);\n const [text, setText] = useState(\"\");\n const [sent, setSent] = useState(false);\n\n return (\n <View\n style={{\n backgroundColor: C.panel,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 10,\n padding: 12,\n marginBottom: 10,\n }}\n >\n <Text style={{ color: C.dim, fontSize: 12 }}>\n {s.namespace} · {s.key}\n </Text>\n <Text style={{ color: C.text, fontSize: 15, marginVertical: 6 }}>\n {s.value}\n </Text>\n <View style={{ flexDirection: \"row\" }}>\n {[1, 2, 3, 4, 5].map((n) => (\n <TouchableOpacity\n key={n}\n accessibilityLabel={`${n} stars`}\n onPress={() => {\n setMine(n);\n client.rate({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n stars: n,\n });\n }}\n >\n <Text\n style={{\n fontSize: 22,\n marginRight: 4,\n color: mine && n <= mine ? C.emeraldSoft : C.border,\n }}\n >\n ★\n </Text>\n </TouchableOpacity>\n ))}\n <TouchableOpacity\n onPress={() => setShow(!show)}\n style={{ marginLeft: \"auto\" }}\n >\n <Text style={{ color: C.emeraldSoft }}>\n {sent ? \"Suggested ✓\" : \"Suggest\"}\n </Text>\n </TouchableOpacity>\n </View>\n {show ? (\n <View style={{ marginTop: 10 }}>\n <TextInput\n value={text}\n onChangeText={setText}\n multiline\n numberOfLines={3}\n placeholder=\"Your suggested translation…\"\n placeholderTextColor={C.dim}\n style={{\n backgroundColor: C.bg,\n color: C.text,\n borderWidth: 1,\n borderColor: C.border,\n borderRadius: 6,\n padding: 8,\n }}\n />\n <TouchableOpacity\n onPress={() => {\n if (!text.trim()) return;\n client.suggest({\n namespace: s.namespace,\n key: s.key,\n language: client.language,\n translation_hash: s.translation_hash,\n suggested_text: text.trim(),\n });\n setSent(true);\n setShow(false);\n setText(\"\");\n }}\n style={{\n marginTop: 6,\n backgroundColor: C.emerald,\n borderRadius: 6,\n padding: 10,\n }}\n >\n <Text style={{ color: \"#03110c\", fontWeight: \"700\", textAlign: \"center\" }}>\n Send suggestion\n </Text>\n </TouchableOpacity>\n </View>\n ) : null}\n </View>\n );\n}\n","/**\n * On-screen key discovery.\n *\n * Preferred source: the `@verbumia/*-i18n` SDK exposes a lightweight key\n * registry of keys it has rendered. When that registry is present on the\n * global (the i18n SDK publishes `globalThis.__verbumia_key_registry__`),\n * we read the keys touched on the current view. Otherwise the host app\n * passes an explicit `keys` list — the always-available fallback.\n *\n * The registry contract is intentionally tiny so any framework port of the\n * i18n SDK can implement it without depending on this package.\n */\n\nimport type { DeclaredKey } from \"./types\";\n\nconst REGISTRY_GLOBAL = \"__verbumia_key_registry__\";\n\ninterface KeyRegistry {\n /** Returns the keys rendered since the last `reset()` (or ever). */\n snapshot(): DeclaredKey[];\n reset?(): void;\n}\n\nfunction getRegistry(): KeyRegistry | null {\n const g = globalThis as Record<string, unknown>;\n const reg = g[REGISTRY_GLOBAL];\n if (\n reg &&\n typeof (reg as KeyRegistry).snapshot === \"function\"\n ) {\n return reg as KeyRegistry;\n }\n return null;\n}\n\n/** True when a compatible `@verbumia/*-i18n` registry is detectable. */\nexport function hasKeyRegistry(): boolean {\n return getRegistry() !== null;\n}\n\n/**\n * Resolve the on-screen keys: explicit `keys` prop always wins (it is the\n * customer's authoritative declaration); otherwise fall back to the i18n\n * registry snapshot; otherwise an empty list (widget shows \"no strings\").\n *\n * Optional `namespace` filter (task 618, CONTRACT v6 — ADDITIVE): when\n * set (a single namespace or a list), the resolved set is narrowed to\n * keys whose `namespace` is in the filter. The filter is applied AFTER\n * resolution, so it composes with v5 rendered-scoping as\n * `rendered ∩ namespace`. UNSET / empty ⇒ unchanged v5 behaviour (all\n * resolved keys). Backward-compatible: `resolveKeys(keys)` is unaffected.\n */\nexport function resolveKeys(\n explicit?: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const base =\n explicit && explicit.length\n ? dedupe(explicit)\n : getRegistry()\n ? dedupe(getRegistry()!.snapshot())\n : [];\n return filterByNamespace(base, namespace);\n}\n\n/** Narrow keys to those whose namespace is in `namespace`. An undefined\n * filter, an empty string, or an empty list means \"no filter\" (the\n * additive default — identical to v5). Exported so non-panel callers\n * (e.g. a custom /core integration) can apply the same semantics. */\nexport function filterByNamespace(\n keys: DeclaredKey[],\n namespace?: string | string[],\n): DeclaredKey[] {\n const list = (Array.isArray(namespace) ? namespace : namespace ? [namespace] : [])\n .map((n) => n.trim())\n .filter(Boolean);\n if (!list.length) return keys;\n const allow = new Set(list);\n return keys.filter((k) => allow.has(k.namespace));\n}\n\nfunction dedupe(keys: DeclaredKey[]): DeclaredKey[] {\n const seen = new Set<string>();\n const out: DeclaredKey[] = [];\n for (const k of keys) {\n const id = `${k.namespace}:${k.key}`;\n if (!seen.has(id)) {\n seen.add(id);\n out.push(k);\n }\n }\n return out;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
|