@mhosaic/feedback 0.15.6 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createFeedback
3
- } from "./chunk-AQW4WVZE.mjs";
3
+ } from "./chunk-CSQGHTXF.mjs";
4
4
  import "./chunk-FGA63IEZ.mjs";
5
5
  export {
6
6
  createFeedback
@@ -0,0 +1,12 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { a as FeedbackConfig, F as FeedbackApi } from '../types-BrsktaE1.js';
4
+
5
+ interface FeedbackProviderProps extends FeedbackConfig {
6
+ children?: ReactNode;
7
+ }
8
+ declare function FeedbackProvider({ children, ...config }: FeedbackProviderProps): react_jsx_runtime.JSX.Element;
9
+
10
+ declare function useFeedback(): FeedbackApi;
11
+
12
+ export { FeedbackProvider, useFeedback };
@@ -0,0 +1,30 @@
1
+ import {
2
+ createFeedback
3
+ } from "../chunk-6JDT2KWQ.mjs";
4
+
5
+ // src/loader/react/FeedbackProvider.tsx
6
+ import { createContext, useContext, useEffect, useState } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var FeedbackContext = createContext(null);
9
+ function FeedbackProvider({ children, ...config }) {
10
+ const [api, setApi] = useState(null);
11
+ useEffect(() => {
12
+ const instance = createFeedback(config);
13
+ setApi(instance);
14
+ return () => {
15
+ instance.shutdown();
16
+ setApi(null);
17
+ };
18
+ }, [config.apiKey]);
19
+ return /* @__PURE__ */ jsx(FeedbackContext.Provider, { value: api, children });
20
+ }
21
+ function useFeedback() {
22
+ const api = useContext(FeedbackContext);
23
+ if (!api) throw new Error("useFeedback must be used inside <FeedbackProvider>");
24
+ return api;
25
+ }
26
+ export {
27
+ FeedbackProvider,
28
+ useFeedback
29
+ };
30
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/loader/react/FeedbackProvider.tsx"],"sourcesContent":["/** @jsxImportSource react */\n/**\n * React provider for the loader path (@mhosaic/feedback/loader/react).\n *\n * Mirrors the direct-import path's <FeedbackProvider> exactly — same\n * props, same `useFeedback()` hook, same rendering semantics — but uses\n * the loader's createFeedback under the hood, so the host's bundle\n * doesn't include the widget code (it gets fetched at runtime from the\n * Mhosaic-controlled bundle URL).\n *\n * Subtle difference vs the direct-import provider:\n * The deferred handle is available SYNCHRONOUSLY (so we can render\n * children immediately instead of waiting for the bundle to load).\n * useFeedback() returns the deferred handle from first paint; method\n * calls queue until the bundle attaches. The direct-import provider\n * used to delay rendering children until the api instance was ready;\n * the loader doesn't need that delay since the deferred handle\n * already absorbs the lag.\n */\n\nimport { createContext, useContext, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { createFeedback } from '../index'\nimport type { FeedbackApi, FeedbackConfig } from '../../types'\n\nconst FeedbackContext = createContext<FeedbackApi | null>(null)\n\ninterface FeedbackProviderProps extends FeedbackConfig {\n children?: ReactNode\n}\n\nexport function FeedbackProvider({ children, ...config }: FeedbackProviderProps) {\n const [api, setApi] = useState<FeedbackApi | null>(null)\n\n useEffect(() => {\n const instance = createFeedback(config)\n setApi(instance)\n return () => {\n instance.shutdown()\n setApi(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [config.apiKey])\n\n // Unlike the direct-import provider, render children immediately —\n // the deferred handle is sync-available, queued calls replay once\n // the bundle attaches.\n return <FeedbackContext.Provider value={api}>{children}</FeedbackContext.Provider>\n}\n\nexport { FeedbackContext }\n\nexport function useFeedback(): FeedbackApi {\n const api = useContext(FeedbackContext)\n if (!api) throw new Error('useFeedback must be used inside <FeedbackProvider>')\n return api\n}\n"],"mappings":";;;;;AAoBA,SAAS,eAAe,YAAY,WAAW,gBAAgB;AA4BtD;AAtBT,IAAM,kBAAkB,cAAkC,IAAI;AAMvD,SAAS,iBAAiB,EAAE,UAAU,GAAG,OAAO,GAA0B;AAC/E,QAAM,CAAC,KAAK,MAAM,IAAI,SAA6B,IAAI;AAEvD,YAAU,MAAM;AACd,UAAM,WAAW,eAAe,MAAM;AACtC,WAAO,QAAQ;AACf,WAAO,MAAM;AACX,eAAS,SAAS;AAClB,aAAO,IAAI;AAAA,IACb;AAAA,EAEF,GAAG,CAAC,OAAO,MAAM,CAAC;AAKlB,SAAO,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,KAAM,UAAS;AACzD;AAIO,SAAS,cAA2B;AACzC,QAAM,MAAM,WAAW,eAAe;AACtC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,oDAAoD;AAC9E,SAAO;AACT;","names":[]}
@@ -0,0 +1,60 @@
1
+ import { a as FeedbackConfig, F as FeedbackApi } from './types-BrsktaE1.js';
2
+
3
+ /**
4
+ * Fetch the widget-manifest from the Mhosaic backend.
5
+ *
6
+ * The loader calls this on every page load. The backend reads `Project.
7
+ * pinned_version` (or current stable) + `Project.widget_enabled` and
8
+ * returns the bundle URL + SRI hash for the version this tenant should
9
+ * receive — or `{enabled: false}` if the widget is killed for the tenant.
10
+ *
11
+ * Network failures are surfaced to the caller. The loader degrades to
12
+ * "widget not present" rather than crashing the host page.
13
+ */
14
+ interface Manifest {
15
+ /** False when the widget is disabled for this project (kill switch) or
16
+ * no stable release exists. Loader bails silently. */
17
+ enabled: boolean;
18
+ /** Semver of the bundle being served. Present when enabled=true. */
19
+ version?: string;
20
+ /** Fully-qualified URL to the version-pinned bundle (jsDelivr). */
21
+ bundle_url?: string;
22
+ /** Subresource integrity hash for the bundle. The loader injects the
23
+ * script tag with `integrity=…` so the browser refuses to run a
24
+ * bundle whose bytes don't match. */
25
+ sri_hash?: string;
26
+ /** Static config the manifest endpoint passes through to the widget. */
27
+ config?: {
28
+ endpoint: string;
29
+ project_slug: string;
30
+ share_reports_with_widget: boolean;
31
+ };
32
+ /** Human-readable detail when `enabled` is false. */
33
+ detail?: string;
34
+ }
35
+
36
+ /**
37
+ * `@mhosaic/feedback/loader` — public entry for the loader architecture.
38
+ *
39
+ * Same shape as the direct-import path (`@mhosaic/feedback`), but the
40
+ * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend
41
+ * dictates via the manifest endpoint. Letting hosts switch from the
42
+ * direct-import path to the loader path gives them auto-updates without
43
+ * any further code changes — Mhosaic ships a new Release row, every host
44
+ * picks up the new bundle on next page load.
45
+ *
46
+ * Public surface MUST mirror the direct-import path exactly:
47
+ * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.
48
+ * - Method calls before the bundle finishes loading queue up and replay.
49
+ * - `submit()` returns a Promise that resolves after the bundle is up
50
+ * and the real submit completes.
51
+ *
52
+ * If the manifest endpoint reports the widget is disabled for this
53
+ * project (`enabled: false` — kill switch or no stable release configured),
54
+ * the deferred handle silently no-ops void calls and rejects `submit()`
55
+ * calls with a clear error. The host page never crashes.
56
+ */
57
+
58
+ declare function createFeedback(config: FeedbackConfig): FeedbackApi;
59
+
60
+ export { FeedbackApi, FeedbackConfig, type Manifest, createFeedback };
@@ -0,0 +1,2 @@
1
+ "use strict";var MhosaicFeedbackLoader=(()=>{var u="data-mhosaic-feedback-bundle";async function f(t){var n,i;return(n=window.MhosaicFeedback)!=null&&n.createFeedback?window.MhosaicFeedback:document.querySelector(`script[${u}]`)?g((i=t.timeoutMs)!=null?i:3e4):new Promise((e,o)=>{var s;let r=document.createElement("script");r.src=t.bundleUrl,r.integrity=t.sriHash,r.crossOrigin="anonymous",r.async=!0,r.setAttribute(u,"1"),r.onload=()=>{var c;(c=window.MhosaicFeedback)!=null&&c.createFeedback?e(window.MhosaicFeedback):o(new Error("mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing \u2014 bundle/loader version mismatch?"))},r.onerror=()=>{o(new Error(`mhosaic-feedback: failed to load bundle from ${t.bundleUrl} (network, CSP, or SRI mismatch)`))},document.head.appendChild(r);let d=(s=t.timeoutMs)!=null?s:3e4;setTimeout(()=>{o(new Error(`mhosaic-feedback: bundle load timeout after ${d}ms`))},d)})}function g(t){return new Promise((a,n)=>{let i=Date.now(),e=()=>{var o;if((o=window.MhosaicFeedback)!=null&&o.createFeedback){a(window.MhosaicFeedback);return}if(Date.now()-i>t){n(new Error("mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)"));return}setTimeout(e,50)};e()})}async function l(t,a,n){let e=`${t.replace(/\/$/,"")}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(a)}`,o={credentials:"omit"};n&&(o.signal=n);let r=await fetch(e,o);if(!r.ok)throw new Error(`mhosaic-feedback: manifest fetch failed (HTTP ${r.status})`);return await r.json()}function b(){let t=null,a=null,n=[];function i(e){if(t){e.kind==="void"?t[e.method]():e.kind==="arg"?t[e.method](e.arg):Promise.resolve(t.submit(e.payload)).then(e.resolve,e.reject);return}if(a){e.kind==="submit"&&e.reject(a);return}n.push(e)}return{show(){i({kind:"void",method:"show"})},hide(){i({kind:"void",method:"hide"})},shutdown(){i({kind:"void",method:"shutdown"})},open(e){i({kind:"arg",method:"open",arg:e})},identify(e){i({kind:"arg",method:"identify",arg:e})},setMetadata(e){i({kind:"arg",method:"setMetadata",arg:e})},submit(e){return new Promise((o,r)=>{i({kind:"submit",payload:e,resolve:o,reject:r})})},_attach(e){for(t=e;n.length>0;)i(n.shift())},_fail(e){a=e;for(let o of n)o.kind==="submit"&&o.reject(e);n.length=0}}}function k(t){let a=b();return w(t,a).catch(n=>{console.warn("[mhosaic-feedback] widget did not load:",n.message),a._fail(n)}),a}async function w(t,a){let n=await l(t.endpoint,t.apiKey);if(!n.enabled||!n.bundle_url||!n.sri_hash){a._fail(new Error(`mhosaic-feedback: widget disabled for this project${n.detail?` \u2014 ${n.detail}`:""}`));return}let e=(await f({bundleUrl:n.bundle_url,sriHash:n.sri_hash})).createFeedback(t);a._attach(e)}function F(){let t=document.currentScript;if(!t)return null;let a=t.dataset.key,n=t.dataset.endpoint;if(!a||!n)return null;let i=t.dataset.env;return{apiKey:a,endpoint:n,...i!==void 0&&{env:i}}}var m=F(),p,h;if(m){let n=function(e){switch(e.name){case"identify":t.identify(e.args[0]);break;case"setMetadata":t.setMetadata(e.args[0]);break;case"open":t.open(e.args[0]);break;case"show":t.show();break;case"hide":t.hide();break;default:console.warn("[mhosaic-feedback] unknown queue call:",e.name)}};y=n;let t=k(m),a=(h=(p=window.Feedback)==null?void 0:p.q)!=null?h:[];for(let e of a)n(e);let i=Object.assign(function(o,...r){n({name:o,args:r})},{q:[]});window.Feedback=i}var y;})();
2
+ //# sourceMappingURL=loader.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.ts","../src/loader-embed.ts"],"sourcesContent":["/**\n * Inject the widget bundle as an SRI-protected <script> and wait for its\n * window global to appear.\n *\n * Why a <script> tag and not `import()`:\n * `import()` can fetch cross-origin ESM but doesn't honor Subresource\n * Integrity on the dynamic-import call itself. A <script> tag with\n * `integrity=…` is the only path where the browser actually refuses\n * to execute a bundle whose bytes don't match the manifest's hash.\n *\n * The bundle is built with `tsup --format iife --globalName MhosaicFeedback`,\n * so once it parses it sets `window.MhosaicFeedback = { createFeedback }`\n * (per `src/widget.ts`'s exports). We wait for that to appear.\n *\n * Idempotency: if multiple FeedbackProviders mount on the same page (e.g.\n * during a React fast-refresh cycle), we reuse an already-injected bundle\n * instead of fetching a second copy.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: FeedbackConfig): FeedbackApi\n }\n }\n}\n\nconst SCRIPT_DATA_ATTR = 'data-mhosaic-feedback-bundle'\n\nexport interface BundleLoadOptions {\n bundleUrl: string\n sriHash: string\n /** Default 30 s. Long enough for slow networks; short enough to surface\n * CDN outages quickly. */\n timeoutMs?: number\n}\n\nexport interface BundleHandle {\n createFeedback(config: FeedbackConfig): FeedbackApi\n}\n\nexport async function injectBundle(\n opts: BundleLoadOptions,\n): Promise<BundleHandle> {\n // Reuse a bundle already loaded this page.\n if (window.MhosaicFeedback?.createFeedback) {\n return window.MhosaicFeedback\n }\n\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_DATA_ATTR}]`,\n )\n if (existing) {\n // Another loader instance started the fetch; wait for the global.\n return waitForGlobal(opts.timeoutMs ?? 30_000)\n }\n\n return new Promise<BundleHandle>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = opts.bundleUrl\n script.integrity = opts.sriHash\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute(SCRIPT_DATA_ATTR, '1')\n script.onload = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n } else {\n reject(\n new Error(\n 'mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing — bundle/loader version mismatch?',\n ),\n )\n }\n }\n script.onerror = () => {\n reject(\n new Error(\n `mhosaic-feedback: failed to load bundle from ${opts.bundleUrl} ` +\n '(network, CSP, or SRI mismatch)',\n ),\n )\n }\n document.head.appendChild(script)\n\n const timeoutMs = opts.timeoutMs ?? 30_000\n setTimeout(() => {\n reject(\n new Error(`mhosaic-feedback: bundle load timeout after ${timeoutMs}ms`),\n )\n }, timeoutMs)\n })\n}\n\nfunction waitForGlobal(timeoutMs: number): Promise<BundleHandle> {\n return new Promise((resolve, reject) => {\n const start = Date.now()\n const tick = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n return\n }\n if (Date.now() - start > timeoutMs) {\n reject(\n new Error(\n 'mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)',\n ),\n )\n return\n }\n setTimeout(tick, 50)\n }\n tick()\n })\n}\n","/**\n * Fetch the widget-manifest from the Mhosaic backend.\n *\n * The loader calls this on every page load. The backend reads `Project.\n * pinned_version` (or current stable) + `Project.widget_enabled` and\n * returns the bundle URL + SRI hash for the version this tenant should\n * receive — or `{enabled: false}` if the widget is killed for the tenant.\n *\n * Network failures are surfaced to the caller. The loader degrades to\n * \"widget not present\" rather than crashing the host page.\n */\n\nexport interface Manifest {\n /** False when the widget is disabled for this project (kill switch) or\n * no stable release exists. Loader bails silently. */\n enabled: boolean\n /** Semver of the bundle being served. Present when enabled=true. */\n version?: string\n /** Fully-qualified URL to the version-pinned bundle (jsDelivr). */\n bundle_url?: string\n /** Subresource integrity hash for the bundle. The loader injects the\n * script tag with `integrity=…` so the browser refuses to run a\n * bundle whose bytes don't match. */\n sri_hash?: string\n /** Static config the manifest endpoint passes through to the widget. */\n config?: {\n endpoint: string\n project_slug: string\n share_reports_with_widget: boolean\n }\n /** Human-readable detail when `enabled` is false. */\n detail?: string\n}\n\nexport async function fetchManifest(\n endpoint: string,\n apiKey: string,\n signal?: AbortSignal,\n): Promise<Manifest> {\n const base = endpoint.replace(/\\/$/, '')\n const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`\n // credentials: 'omit' — we authenticate via the public pk_proj_ query\n // param; sending cookies would just trigger CORS preflights for no\n // reason and is unnecessary since the manifest endpoint doesn't read\n // any session.\n const init: RequestInit = { credentials: 'omit' }\n if (signal) init.signal = signal\n const res = await fetch(url, init)\n if (!res.ok) {\n throw new Error(\n `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`,\n )\n }\n const body = (await res.json()) as Manifest\n return body\n}\n","/**\n * A FeedbackApi-shaped object that queues method calls until the real\n * widget bundle finishes loading, then drains the queue against the real\n * implementation.\n *\n * Why we need this: hosts that already have code like\n * const fb = createFeedback({...})\n * fb.identify({id, email, name})\n * useEffect(() => fb.open(...), [])\n * shouldn't have to refactor for async. The loader's createFeedback\n * returns sync — a deferred handle that records intent, then replays it\n * once the real bundle arrives. Idiomatic queue-then-flush pattern.\n *\n * If the bundle never loads (network failure, SRI mismatch, kill switch),\n * `_fail()` is called instead. Void methods (identify/setMetadata/open/...)\n * silently drop in that case — the widget just doesn't appear. The\n * `submit` Promise rejects so callers can surface real errors.\n */\n\nimport type {\n FeedbackApi,\n ReportPayload,\n SubmittedReport,\n UserIdentity,\n} from '../types'\n\ntype VoidMethod = 'show' | 'hide' | 'shutdown'\ntype ArgMethod = 'open' | 'identify' | 'setMetadata'\n\ntype Queued =\n | { kind: 'void'; method: VoidMethod }\n | { kind: 'arg'; method: 'open'; arg: Parameters<FeedbackApi['open']>[0] }\n | { kind: 'arg'; method: 'identify'; arg: UserIdentity }\n | { kind: 'arg'; method: 'setMetadata'; arg: Record<string, unknown> }\n | {\n kind: 'submit'\n payload: Partial<ReportPayload> & { description: string }\n resolve: (r: SubmittedReport) => void\n reject: (e: unknown) => void\n }\n\nexport interface DeferredHandle extends FeedbackApi {\n /** Called by the loader once the bundle is loaded and a real API exists. */\n _attach(real: FeedbackApi): void\n /** Called by the loader if the bundle fails to load. */\n _fail(err: Error): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n\n function flush(item: Queued): void {\n if (real) {\n if (item.kind === 'void') {\n ;(real[item.method] as () => void)()\n } else if (item.kind === 'arg') {\n ;(real[item.method] as (a: unknown) => void)(item.arg)\n } else {\n // submit\n Promise.resolve(real.submit(item.payload)).then(\n item.resolve,\n item.reject,\n )\n }\n return\n }\n if (failure) {\n if (item.kind === 'submit') item.reject(failure)\n // Void / arg methods silently dropped — the widget never appeared.\n return\n }\n queue.push(item)\n }\n\n return {\n show() {\n flush({ kind: 'void', method: 'show' })\n },\n hide() {\n flush({ kind: 'void', method: 'hide' })\n },\n shutdown() {\n flush({ kind: 'void', method: 'shutdown' })\n },\n open(arg) {\n flush({ kind: 'arg', method: 'open', arg })\n },\n identify(arg) {\n flush({ kind: 'arg', method: 'identify', arg })\n },\n setMetadata(arg) {\n flush({ kind: 'arg', method: 'setMetadata', arg })\n },\n submit(payload) {\n return new Promise<SubmittedReport>((resolve, reject) => {\n flush({ kind: 'submit', payload, resolve, reject })\n })\n },\n _attach(impl) {\n real = impl\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n // Drain any pending submits with the failure; drop void/arg.\n for (const item of queue) {\n if (item.kind === 'submit') item.reject(err)\n }\n queue.length = 0\n },\n }\n}\n","/**\n * `@mhosaic/feedback/loader` — public entry for the loader architecture.\n *\n * Same shape as the direct-import path (`@mhosaic/feedback`), but the\n * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend\n * dictates via the manifest endpoint. Letting hosts switch from the\n * direct-import path to the loader path gives them auto-updates without\n * any further code changes — Mhosaic ships a new Release row, every host\n * picks up the new bundle on next page load.\n *\n * Public surface MUST mirror the direct-import path exactly:\n * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.\n * - Method calls before the bundle finishes loading queue up and replay.\n * - `submit()` returns a Promise that resolves after the bundle is up\n * and the real submit completes.\n *\n * If the manifest endpoint reports the widget is disabled for this\n * project (`enabled: false` — kill switch or no stable release configured),\n * the deferred handle silently no-ops void calls and rejects `submit()`\n * calls with a clear error. The host page never crashes.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport { injectBundle } from './bundleLoader'\nimport { fetchManifest } from './manifest'\nimport { createDeferredApi, type DeferredHandle } from './deferredApi'\n\nexport type { FeedbackApi, FeedbackConfig } from '../types'\nexport type { Manifest } from './manifest'\n\nexport function createFeedback(config: FeedbackConfig): FeedbackApi {\n const deferred = createDeferredApi()\n loadAndAttach(config, deferred).catch((err: Error) => {\n // Surface to console — host devtools is the canonical channel for\n // widget bootstrap errors. Production telemetry is the bundle's job\n // (it can't run if it never loaded), so console.warn is the floor.\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] widget did not load:', err.message)\n deferred._fail(err)\n })\n return deferred\n}\n\nasync function loadAndAttach(\n config: FeedbackConfig,\n deferred: DeferredHandle,\n): Promise<void> {\n const manifest = await fetchManifest(config.endpoint, config.apiKey)\n if (!manifest.enabled || !manifest.bundle_url || !manifest.sri_hash) {\n // Disabled by Mhosaic (kill switch) or no stable release. Surface a\n // clear failure so submit() callers see an error, but no host crash.\n deferred._fail(\n new Error(\n `mhosaic-feedback: widget disabled for this project${\n manifest.detail ? ` — ${manifest.detail}` : ''\n }`,\n ),\n )\n return\n }\n const bundle = await injectBundle({\n bundleUrl: manifest.bundle_url,\n sriHash: manifest.sri_hash,\n })\n const real: FeedbackApi = bundle.createFeedback(config)\n deferred._attach(real)\n}\n","/**\n * IIFE loader for the plain HTML / CDN install path.\n *\n * Hosts drop in a single tag, forever:\n *\n * <script\n * src=\"https://cdn.jsdelivr.net/npm/@mhosaic/feedback@1/dist/loader.min.js\"\n * data-key=\"pk_proj_…\"\n * data-endpoint=\"https://software-factory-…\"\n * defer\n * ></script>\n *\n * The loader reads its own script tag's `data-key`/`data-endpoint`/`data-env`,\n * calls `createFeedback(…)` from `loader/index.ts`, and the deferred handle\n * does the rest (fetch manifest, inject the actual widget bundle with SRI).\n *\n * Once installed, hosts never touch this file again. Mhosaic publishes a new\n * Release row → every host's next page load picks up the new widget bundle.\n */\n\nimport { createFeedback } from './loader/index'\nimport type { FeedbackConfig } from './types'\n\ninterface QueuedCall {\n name: string\n args: unknown[]\n}\n\ninterface WindowFeedback {\n (name: string, ...args: unknown[]): void\n q?: QueuedCall[]\n}\n\ndeclare global {\n interface Window {\n Feedback?: WindowFeedback\n }\n}\n\nfunction readConfigFromScriptTag(): FeedbackConfig | null {\n const script = document.currentScript as HTMLScriptElement | null\n if (!script) return null\n const apiKey = script.dataset.key\n const endpoint = script.dataset.endpoint\n if (!apiKey || !endpoint) return null\n const env = script.dataset.env as FeedbackConfig['env'] | undefined\n return {\n apiKey,\n endpoint,\n ...(env !== undefined && { env }),\n }\n}\n\nconst config = readConfigFromScriptTag()\nif (config) {\n const fb = createFeedback(config)\n // Drain any pre-queued calls a host may have set up before this script\n // loaded (Google-Analytics-style queue pattern: `window.Feedback = window.\n // Feedback || function(){(window.Feedback.q=window.Feedback.q||[]).push(\n // arguments)}; Feedback('identify', {...})`).\n const pending = window.Feedback?.q ?? []\n function dispatch(call: QueuedCall): void {\n switch (call.name) {\n case 'identify':\n fb.identify(call.args[0] as Parameters<typeof fb.identify>[0])\n break\n case 'setMetadata':\n fb.setMetadata(call.args[0] as Parameters<typeof fb.setMetadata>[0])\n break\n case 'open':\n fb.open(call.args[0] as Parameters<typeof fb.open>[0])\n break\n case 'show':\n fb.show()\n break\n case 'hide':\n fb.hide()\n break\n // submit and shutdown intentionally omitted from the queue API —\n // they have return values that the queue pattern can't surface\n // synchronously.\n default:\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] unknown queue call:', call.name)\n }\n }\n for (const c of pending) dispatch(c)\n\n const fn = Object.assign(\n function Feedback(name: string, ...args: unknown[]): void {\n dispatch({ name, args })\n },\n { q: [] as QueuedCall[] },\n ) as WindowFeedback\n window.Feedback = fn\n}\n"],"mappings":"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","names":["SCRIPT_DATA_ATTR","injectBundle","opts","_a","_b","waitForGlobal","resolve","reject","script","timeoutMs","start","tick","fetchManifest","endpoint","apiKey","signal","url","init","res","createDeferredApi","real","failure","queue","flush","item","arg","payload","resolve","reject","impl","err","createFeedback","config","deferred","createDeferredApi","loadAndAttach","err","manifest","fetchManifest","real","injectBundle","readConfigFromScriptTag","script","apiKey","endpoint","env","config","_a","_b","dispatch","call","fb","createFeedback","pending","c","fn","name","args"]}
@@ -0,0 +1,7 @@
1
+ import {
2
+ createFeedback
3
+ } from "./chunk-6JDT2KWQ.mjs";
4
+ export {
5
+ createFeedback
6
+ };
7
+ //# sourceMappingURL=loader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createFeedback
3
- } from "./chunk-AQW4WVZE.mjs";
3
+ } from "./chunk-CSQGHTXF.mjs";
4
4
  import "./chunk-FGA63IEZ.mjs";
5
5
 
6
6
  // src/react/FeedbackProvider.tsx