@mhosaic/feedback 0.36.0 → 0.37.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/{chunk-557ACRBU.mjs → chunk-BQDUJOWL.mjs} +3 -2
- package/dist/{chunk-557ACRBU.mjs.map → chunk-BQDUJOWL.mjs.map} +1 -1
- package/dist/{chunk-T36VC7TZ.mjs → chunk-C3ILJCVI.mjs} +127 -90
- package/dist/chunk-C3ILJCVI.mjs.map +1 -0
- package/dist/embed.min.js +11 -5
- package/dist/embed.min.js.map +1 -1
- package/dist/error-tracking.d.ts +1 -1
- package/dist/{index-Dp0nfVYj.d.ts → index-BRmQGB1a.d.ts} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/loader/react.d.ts +1 -1
- package/dist/loader/react.mjs +1 -1
- package/dist/loader.d.ts +1 -1
- package/dist/loader.min.js +1 -1
- package/dist/loader.min.js.map +1 -1
- package/dist/loader.mjs +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.mjs +1 -1
- package/dist/replay.d.ts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/{types-Cg1v8AbB.d.ts → types-C04JGBPo.d.ts} +10 -0
- package/dist/vue.d.ts +2 -2
- package/dist/vue.mjs +1 -1
- package/dist/webvitals.d.ts +1 -1
- package/dist/widget.min.js +12 -6
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-T36VC7TZ.mjs.map +0 -1
|
@@ -202,7 +202,8 @@ async function loadAndAttach(config, deferred) {
|
|
|
202
202
|
// an explicit host value still wins.
|
|
203
203
|
...config.openToCurrentPageFeedback === void 0 && typeof manifest.config?.behavior?.openToCurrentPageFeedback === "boolean" ? {
|
|
204
204
|
openToCurrentPageFeedback: manifest.config.behavior.openToCurrentPageFeedback
|
|
205
|
-
} : {}
|
|
205
|
+
} : {},
|
|
206
|
+
...config.metaFeedbackEnabled === void 0 && typeof manifest.config?.behavior?.metaFeedbackEnabled === "boolean" ? { metaFeedbackEnabled: manifest.config.behavior.metaFeedbackEnabled } : {}
|
|
206
207
|
});
|
|
207
208
|
deferred._attach(real);
|
|
208
209
|
}
|
|
@@ -210,4 +211,4 @@ async function loadAndAttach(config, deferred) {
|
|
|
210
211
|
export {
|
|
211
212
|
createFeedback
|
|
212
213
|
};
|
|
213
|
-
//# sourceMappingURL=chunk-
|
|
214
|
+
//# sourceMappingURL=chunk-BQDUJOWL.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.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'\nimport type { ErrorTrackingOptions } from '../modules/error-tracking'\n\n/** What the loader may pass to the bundle's `createFeedback`: the public\n * config plus the bundle-only `errorTracking` knob the loader forwards\n * from the manifest. */\nexport type BundleConfig = FeedbackConfig & {\n errorTracking?: boolean | ErrorTrackingOptions\n}\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: BundleConfig): 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: BundleConfig): 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 /** Phase 4: project gates the widget per end-user (default false). */\n requires_visibility_check?: boolean\n /** Per-project switch for the bundle's default error capture. When the\n * backend omits it, the bundle falls back to its own default (on). */\n error_tracking?: boolean\n behavior?: Record<string, 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 /** Buffered transformer registry — forwarded to the real instance on\n * attach, so wrapper modules (withReplay) work over loader installs. */\n _registerTransformer(fn: unknown): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n // Transformers (withReplay & friends) registered before the bundle\n // attaches. Forwarded to the real instance's registry on _attach so\n // host-side wrappers work over a loader install too (roadmap #7).\n const pendingTransformers: unknown[] = []\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 _registerTransformer(fn: unknown) {\n const target = real as { _registerTransformer?: (f: unknown) => void } | null\n if (target && typeof target._registerTransformer === 'function') {\n target._registerTransformer(fn)\n return\n }\n if (failure) return // widget never appeared — nothing to transform\n pendingTransformers.push(fn)\n },\n _attach(impl) {\n real = impl\n const target = impl as { _registerTransformer?: (f: unknown) => void }\n if (typeof target._registerTransformer === 'function') {\n for (const fn of pendingTransformers) target._registerTransformer(fn)\n }\n pendingTransformers.length = 0\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n pendingTransformers.length = 0\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 type { ErrorTrackingOptions } from '../modules/error-tracking'\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\n/** Loader config = the public widget config plus the loader-only knobs the\n * host can set to override what the manifest dictates. */\nexport type LoaderConfig = FeedbackConfig & {\n /** Override the bundle's default error capture. Omit (or pass `undefined`)\n * to let the project's manifest flag decide (which itself defaults on). */\n errorTracking?: boolean | ErrorTrackingOptions | undefined\n}\n\nexport function createFeedback(config: LoaderConfig): 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: LoaderConfig,\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({\n ...config,\n // Forward the project's per-end-user gating flag from the manifest unless\n // the host set it explicitly. The widget then runs the visibility check.\n requiresVisibilityCheck:\n config.requiresVisibilityCheck ??\n manifest.config?.requires_visibility_check ??\n false,\n // Error capture is armed INSIDE the bundle (the only auto-updating part\n // of a loader install), so already-deployed hosts pick it up without a\n // redeploy. Precedence: explicit host override > per-project manifest\n // flag > on. The loader provider forwards its `errorTracking` prop here\n // rather than wrapping host-side, so there's exactly one arm point (the\n // real instance) — no deferred/real double-registration.\n errorTracking:\n config.errorTracking ?? manifest.config?.error_tracking ?? true,\n // Roadmap #17: operator-flipped behavior toggles ride the manifest;\n // an explicit host value still wins.\n ...(config.openToCurrentPageFeedback === undefined &&\n typeof manifest.config?.behavior?.openToCurrentPageFeedback === 'boolean'\n ? {\n openToCurrentPageFeedback:\n manifest.config.behavior.openToCurrentPageFeedback,\n }\n : {}),\n })\n deferred._attach(real)\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.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'\nimport type { ErrorTrackingOptions } from '../modules/error-tracking'\n\n/** What the loader may pass to the bundle's `createFeedback`: the public\n * config plus the bundle-only `errorTracking` knob the loader forwards\n * from the manifest. */\nexport type BundleConfig = FeedbackConfig & {\n errorTracking?: boolean | ErrorTrackingOptions\n}\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: BundleConfig): 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: BundleConfig): 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 /** Phase 4: project gates the widget per end-user (default false). */\n requires_visibility_check?: boolean\n /** Per-project switch for the bundle's default error capture. When the\n * backend omits it, the bundle falls back to its own default (on). */\n error_tracking?: boolean\n behavior?: Record<string, 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 /** Buffered transformer registry — forwarded to the real instance on\n * attach, so wrapper modules (withReplay) work over loader installs. */\n _registerTransformer(fn: unknown): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n // Transformers (withReplay & friends) registered before the bundle\n // attaches. Forwarded to the real instance's registry on _attach so\n // host-side wrappers work over a loader install too (roadmap #7).\n const pendingTransformers: unknown[] = []\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 _registerTransformer(fn: unknown) {\n const target = real as { _registerTransformer?: (f: unknown) => void } | null\n if (target && typeof target._registerTransformer === 'function') {\n target._registerTransformer(fn)\n return\n }\n if (failure) return // widget never appeared — nothing to transform\n pendingTransformers.push(fn)\n },\n _attach(impl) {\n real = impl\n const target = impl as { _registerTransformer?: (f: unknown) => void }\n if (typeof target._registerTransformer === 'function') {\n for (const fn of pendingTransformers) target._registerTransformer(fn)\n }\n pendingTransformers.length = 0\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n pendingTransformers.length = 0\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 type { ErrorTrackingOptions } from '../modules/error-tracking'\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\n/** Loader config = the public widget config plus the loader-only knobs the\n * host can set to override what the manifest dictates. */\nexport type LoaderConfig = FeedbackConfig & {\n /** Override the bundle's default error capture. Omit (or pass `undefined`)\n * to let the project's manifest flag decide (which itself defaults on). */\n errorTracking?: boolean | ErrorTrackingOptions | undefined\n}\n\nexport function createFeedback(config: LoaderConfig): 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(config: LoaderConfig, deferred: DeferredHandle): 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({\n ...config,\n // Forward the project's per-end-user gating flag from the manifest unless\n // the host set it explicitly. The widget then runs the visibility check.\n requiresVisibilityCheck:\n config.requiresVisibilityCheck ?? manifest.config?.requires_visibility_check ?? false,\n // Error capture is armed INSIDE the bundle (the only auto-updating part\n // of a loader install), so already-deployed hosts pick it up without a\n // redeploy. Precedence: explicit host override > per-project manifest\n // flag > on. The loader provider forwards its `errorTracking` prop here\n // rather than wrapping host-side, so there's exactly one arm point (the\n // real instance) — no deferred/real double-registration.\n errorTracking: config.errorTracking ?? manifest.config?.error_tracking ?? true,\n // Roadmap #17: operator-flipped behavior toggles ride the manifest;\n // an explicit host value still wins.\n ...(config.openToCurrentPageFeedback === undefined &&\n typeof manifest.config?.behavior?.openToCurrentPageFeedback === 'boolean'\n ? {\n openToCurrentPageFeedback: manifest.config.behavior.openToCurrentPageFeedback,\n }\n : {}),\n ...(config.metaFeedbackEnabled === undefined &&\n typeof manifest.config?.behavior?.metaFeedbackEnabled === 'boolean'\n ? { metaFeedbackEnabled: manifest.config.behavior.metaFeedbackEnabled }\n : {}),\n })\n deferred._attach(real)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -20,9 +20,7 @@ function assertSafeEndpoint(endpoint) {
|
|
|
20
20
|
try {
|
|
21
21
|
parsed = new URL(endpoint);
|
|
22
22
|
} catch {
|
|
23
|
-
throw new Error(
|
|
24
|
-
`[mhosaic-feedback] \`endpoint\` is not a valid URL: ${endpoint}`
|
|
25
|
-
);
|
|
23
|
+
throw new Error(`[mhosaic-feedback] \`endpoint\` is not a valid URL: ${endpoint}`);
|
|
26
24
|
}
|
|
27
25
|
if (parsed.protocol === "https:") return;
|
|
28
26
|
if (parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]")) {
|
|
@@ -91,6 +89,7 @@ function createApiClient(options) {
|
|
|
91
89
|
form.append("screenshot", blob, i === 0 ? "screenshot.png" : `screenshot-${i + 1}.png`);
|
|
92
90
|
});
|
|
93
91
|
if (payload.synthetic) form.append("synthetic", "true");
|
|
92
|
+
if (payload.meta) form.append("meta", "true");
|
|
94
93
|
if (payload.user?.id) {
|
|
95
94
|
form.append("user", JSON.stringify(payload.user));
|
|
96
95
|
}
|
|
@@ -172,29 +171,26 @@ function createApiClient(options) {
|
|
|
172
171
|
return "healthy";
|
|
173
172
|
}
|
|
174
173
|
async function listChangelog(externalId) {
|
|
175
|
-
const response = await fetcher(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
174
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/changelog/`, {
|
|
175
|
+
method: "GET",
|
|
176
|
+
headers: widgetHeaders(externalId)
|
|
177
|
+
});
|
|
179
178
|
if (response.status === 404) return [];
|
|
180
179
|
return readJsonArray(response, "listChangelog");
|
|
181
180
|
}
|
|
182
181
|
async function getReport(reportId, externalId, opts) {
|
|
183
|
-
const response = await fetcher(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
189
|
-
}
|
|
190
|
-
);
|
|
182
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
|
|
183
|
+
method: "GET",
|
|
184
|
+
headers: widgetHeaders(externalId),
|
|
185
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
186
|
+
});
|
|
191
187
|
return readJsonObject(response, "getReport");
|
|
192
188
|
}
|
|
193
189
|
async function getReportBySeq(seq, externalId) {
|
|
194
|
-
const response = await fetcher(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
190
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/by-seq/${seq}/`, {
|
|
191
|
+
method: "GET",
|
|
192
|
+
headers: widgetHeaders(externalId)
|
|
193
|
+
});
|
|
198
194
|
return readJsonObject(response, "getReportBySeq");
|
|
199
195
|
}
|
|
200
196
|
async function addComment(reportId, externalId, body, clientNonce, attachments) {
|
|
@@ -203,9 +199,7 @@ function createApiClient(options) {
|
|
|
203
199
|
const form = new FormData();
|
|
204
200
|
form.append("body", body);
|
|
205
201
|
if (clientNonce !== void 0) form.append("client_nonce", clientNonce);
|
|
206
|
-
attachments.forEach(
|
|
207
|
-
(blob, i) => form.append("attachment", blob, `attachment-${i + 1}.png`)
|
|
208
|
-
);
|
|
202
|
+
attachments.forEach((blob, i) => form.append("attachment", blob, `attachment-${i + 1}.png`));
|
|
209
203
|
init = { method: "POST", headers: widgetHeaders(externalId), body: form };
|
|
210
204
|
} else {
|
|
211
205
|
init = {
|
|
@@ -231,17 +225,14 @@ function createApiClient(options) {
|
|
|
231
225
|
return response.json();
|
|
232
226
|
}
|
|
233
227
|
async function editDescription(reportId, externalId, description) {
|
|
234
|
-
const response = await fetcher(
|
|
235
|
-
|
|
236
|
-
{
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
body: JSON.stringify({ description })
|
|
243
|
-
}
|
|
244
|
-
);
|
|
228
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
|
|
229
|
+
method: "PATCH",
|
|
230
|
+
headers: {
|
|
231
|
+
...widgetHeaders(externalId),
|
|
232
|
+
"Content-Type": "application/json"
|
|
233
|
+
},
|
|
234
|
+
body: JSON.stringify({ description })
|
|
235
|
+
});
|
|
245
236
|
if (!response.ok) {
|
|
246
237
|
const text = await response.text().catch(() => "");
|
|
247
238
|
throw new Error(`editDescription failed: ${response.status} ${text}`);
|
|
@@ -249,17 +240,14 @@ function createApiClient(options) {
|
|
|
249
240
|
return response.json();
|
|
250
241
|
}
|
|
251
242
|
async function closeAsResolved(reportId, externalId) {
|
|
252
|
-
const response = await fetcher(
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
body: JSON.stringify({ status: "closed" })
|
|
261
|
-
}
|
|
262
|
-
);
|
|
243
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
|
|
244
|
+
method: "PATCH",
|
|
245
|
+
headers: {
|
|
246
|
+
...widgetHeaders(externalId),
|
|
247
|
+
"Content-Type": "application/json"
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({ status: "closed" })
|
|
250
|
+
});
|
|
263
251
|
if (!response.ok) {
|
|
264
252
|
const text = await response.text().catch(() => "");
|
|
265
253
|
throw new Error(`closeAsResolved failed: ${response.status} ${text}`);
|
|
@@ -267,17 +255,14 @@ function createApiClient(options) {
|
|
|
267
255
|
return response.json();
|
|
268
256
|
}
|
|
269
257
|
async function reopenUnresolved(reportId, externalId) {
|
|
270
|
-
const response = await fetcher(
|
|
271
|
-
|
|
272
|
-
{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
body: JSON.stringify({ status: "in_progress" })
|
|
279
|
-
}
|
|
280
|
-
);
|
|
258
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`, {
|
|
259
|
+
method: "PATCH",
|
|
260
|
+
headers: {
|
|
261
|
+
...widgetHeaders(externalId),
|
|
262
|
+
"Content-Type": "application/json"
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify({ status: "in_progress" })
|
|
265
|
+
});
|
|
281
266
|
if (!response.ok) {
|
|
282
267
|
const text = await response.text().catch(() => "");
|
|
283
268
|
throw new Error(`reopenUnresolved failed: ${response.status} ${text}`);
|
|
@@ -331,20 +316,17 @@ function createApiClient(options) {
|
|
|
331
316
|
return readJsonObject(response, "fetchBoardKpis");
|
|
332
317
|
}
|
|
333
318
|
async function checkVisibility(externalId, email) {
|
|
334
|
-
const response = await fetcher(
|
|
335
|
-
|
|
336
|
-
{
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
})
|
|
346
|
-
}
|
|
347
|
-
);
|
|
319
|
+
const response = await fetcher(`${endpoint}/api/feedback/v1/widget/visibility/`, {
|
|
320
|
+
method: "POST",
|
|
321
|
+
headers: {
|
|
322
|
+
...widgetHeaders(externalId),
|
|
323
|
+
"Content-Type": "application/json"
|
|
324
|
+
},
|
|
325
|
+
body: JSON.stringify({
|
|
326
|
+
external_id: externalId,
|
|
327
|
+
...email ? { email } : {}
|
|
328
|
+
})
|
|
329
|
+
});
|
|
348
330
|
if (!response.ok) {
|
|
349
331
|
throw new Error(`checkVisibility failed: ${response.status}`);
|
|
350
332
|
}
|
|
@@ -677,6 +659,9 @@ var DEFAULT_STRINGS = {
|
|
|
677
659
|
"form.cancel": "Cancel",
|
|
678
660
|
"form.close": "Close",
|
|
679
661
|
"form.submitting": "Sending\u2026",
|
|
662
|
+
"form.meta.action": "Report a problem with this feedback tool",
|
|
663
|
+
"form.meta.badge": "\u2192 goes to the feedback-tool team",
|
|
664
|
+
"form.meta.cancel": "Back to normal feedback",
|
|
680
665
|
"form.success": "Thanks \u2014 your feedback was sent.",
|
|
681
666
|
"form.success.seq": "Thanks \u2014 report #{seq} was sent. \u2713",
|
|
682
667
|
"form.discard.title": "Unsaved changes",
|
|
@@ -881,6 +866,9 @@ var FRENCH_STRINGS = {
|
|
|
881
866
|
"form.cancel": "Annuler",
|
|
882
867
|
"form.close": "Fermer",
|
|
883
868
|
"form.submitting": "Envoi\u2026",
|
|
869
|
+
"form.meta.action": "Signaler un probl\xE8me avec l'outil de feedback",
|
|
870
|
+
"form.meta.badge": "\u2192 envoy\xE9 \xE0 l'\xE9quipe de l'outil de feedback",
|
|
871
|
+
"form.meta.cancel": "Revenir au commentaire normal",
|
|
884
872
|
"form.success": "Merci \u2014 votre commentaire a \xE9t\xE9 envoy\xE9.",
|
|
885
873
|
"form.success.seq": "Merci \u2014 rapport n\xB0{seq} envoy\xE9. \u2713",
|
|
886
874
|
"form.discard.title": "Modifications non enregistr\xE9es",
|
|
@@ -1318,6 +1306,26 @@ var ALLOWED_IMAGE_TYPES = [
|
|
|
1318
1306
|
];
|
|
1319
1307
|
var MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
|
|
1320
1308
|
var MAX_SCREENSHOTS = 5;
|
|
1309
|
+
var ALLOWED_ATTACHMENT_TYPES = [
|
|
1310
|
+
...ALLOWED_IMAGE_TYPES,
|
|
1311
|
+
"application/pdf",
|
|
1312
|
+
"text/plain",
|
|
1313
|
+
"text/csv",
|
|
1314
|
+
"application/json",
|
|
1315
|
+
""
|
|
1316
|
+
];
|
|
1317
|
+
var ATTACHMENT_ACCEPT = "image/png,image/jpeg,image/webp,application/pdf,text/plain,.log,.txt,.csv,.json";
|
|
1318
|
+
function validateAttachmentFile(file) {
|
|
1319
|
+
if (!ALLOWED_ATTACHMENT_TYPES.includes(
|
|
1320
|
+
file.type
|
|
1321
|
+
)) {
|
|
1322
|
+
return { kind: "type" };
|
|
1323
|
+
}
|
|
1324
|
+
if (file.size > MAX_SCREENSHOT_BYTES) {
|
|
1325
|
+
return { kind: "size", maxMb: MAX_SCREENSHOT_BYTES / (1024 * 1024) };
|
|
1326
|
+
}
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1321
1329
|
function validateScreenshotFile(file) {
|
|
1322
1330
|
if (!ALLOWED_IMAGE_TYPES.includes(
|
|
1323
1331
|
file.type
|
|
@@ -1404,7 +1412,7 @@ function ReportDetailView({
|
|
|
1404
1412
|
const remaining = MAX_SCREENSHOTS - composeShots.length;
|
|
1405
1413
|
const batch = files.slice(0, Math.max(0, remaining));
|
|
1406
1414
|
for (const file of batch) {
|
|
1407
|
-
const err =
|
|
1415
|
+
const err = validateAttachmentFile(file);
|
|
1408
1416
|
if (err) {
|
|
1409
1417
|
setAttachError(
|
|
1410
1418
|
err.kind === "type" ? strings["form.screenshot.error_type"] : strings["form.screenshot.error_size"].replace("{max}", String(err.maxMb))
|
|
@@ -1776,7 +1784,7 @@ function ReportDetailView({
|
|
|
1776
1784
|
{
|
|
1777
1785
|
ref: fileInputRef,
|
|
1778
1786
|
type: "file",
|
|
1779
|
-
accept:
|
|
1787
|
+
accept: ATTACHMENT_ACCEPT,
|
|
1780
1788
|
multiple: true,
|
|
1781
1789
|
class: "compose-attach-input",
|
|
1782
1790
|
onChange: handleAttachChange,
|
|
@@ -3386,8 +3394,10 @@ function Form({
|
|
|
3386
3394
|
status,
|
|
3387
3395
|
errorMessage,
|
|
3388
3396
|
submittedSeq,
|
|
3389
|
-
onDirtyChange
|
|
3397
|
+
onDirtyChange,
|
|
3398
|
+
metaFeedbackEnabled
|
|
3390
3399
|
}) {
|
|
3400
|
+
const [metaMode, setMetaMode] = useState6(false);
|
|
3391
3401
|
const [description, setDescription] = useState6("");
|
|
3392
3402
|
const [feedbackType, setFeedbackType] = useState6("bug");
|
|
3393
3403
|
const [severity, setSeverity] = useState6("medium");
|
|
@@ -3507,10 +3517,14 @@ function Form({
|
|
|
3507
3517
|
values.screenshots = shots.map((s) => s.blob);
|
|
3508
3518
|
values.capture_method = "manual";
|
|
3509
3519
|
}
|
|
3520
|
+
if (metaMode) {
|
|
3521
|
+
values.meta = true;
|
|
3522
|
+
}
|
|
3510
3523
|
onSubmit(values);
|
|
3511
3524
|
};
|
|
3512
3525
|
return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, onPaste: handlePaste, children: [
|
|
3513
3526
|
/* @__PURE__ */ jsx9("h2", { children: strings["form.title"] }),
|
|
3527
|
+
metaMode && /* @__PURE__ */ jsx9("div", { class: "meta-badge", children: strings["form.meta.badge"] }),
|
|
3514
3528
|
/* @__PURE__ */ jsxs9("div", { class: "field", children: [
|
|
3515
3529
|
/* @__PURE__ */ jsx9("label", { for: "mfb-desc", children: strings["form.description.label"] }),
|
|
3516
3530
|
/* @__PURE__ */ jsx9(
|
|
@@ -3677,6 +3691,16 @@ function Form({
|
|
|
3677
3691
|
/* @__PURE__ */ jsx9("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
|
|
3678
3692
|
/* @__PURE__ */ jsx9("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
|
|
3679
3693
|
] }),
|
|
3694
|
+
metaFeedbackEnabled && /* @__PURE__ */ jsx9(
|
|
3695
|
+
"button",
|
|
3696
|
+
{
|
|
3697
|
+
type: "button",
|
|
3698
|
+
class: "meta-toggle",
|
|
3699
|
+
onClick: () => setMetaMode(!metaMode),
|
|
3700
|
+
disabled: submitting,
|
|
3701
|
+
children: metaMode ? strings["form.meta.cancel"] : strings["form.meta.action"]
|
|
3702
|
+
}
|
|
3703
|
+
),
|
|
3680
3704
|
annotatingIndex !== null && shots[annotatingIndex] && /* @__PURE__ */ jsx9(
|
|
3681
3705
|
Annotator,
|
|
3682
3706
|
{
|
|
@@ -4540,6 +4564,12 @@ var WIDGET_STYLES = `
|
|
|
4540
4564
|
/* REQ-A7: quiet transparency line under the form. */
|
|
4541
4565
|
.capture-notice { color: var(--mfb-muted, #6b7280); font-size: 11px; line-height: 1.4; margin-top: 6px; }
|
|
4542
4566
|
|
|
4567
|
+
/* Meta-feedback: footer escape hatch + active-mode badge. Deliberately as
|
|
4568
|
+
quiet as the capture notice \u2014 never competes with the host's own flow. */
|
|
4569
|
+
.meta-toggle { background: none; border: none; padding: 0; margin-top: 10px; color: var(--mfb-muted, #6b7280); font-size: 11px; text-decoration: underline; cursor: pointer; align-self: flex-start; }
|
|
4570
|
+
.meta-toggle:hover { color: var(--mfb-text, #111827); }
|
|
4571
|
+
.meta-badge { display: inline-block; margin: 2px 0 6px; padding: 2px 8px; border-radius: 999px; background: var(--mfb-accent-soft, #eef2ff); color: var(--mfb-accent, #4f46e5); font-size: 11px; font-weight: 600; }
|
|
4572
|
+
|
|
4543
4573
|
/* #85: inline "#NN" report reference rendered as a link (a button for a11y). */
|
|
4544
4574
|
.seq-link {
|
|
4545
4575
|
display: inline;
|
|
@@ -6323,8 +6353,7 @@ function mountWidget(options) {
|
|
|
6323
6353
|
});
|
|
6324
6354
|
}, 1200);
|
|
6325
6355
|
} catch (err) {
|
|
6326
|
-
if (typeof console !== "undefined")
|
|
6327
|
-
console.warn("[mhosaic] submit:", err);
|
|
6356
|
+
if (typeof console !== "undefined") console.warn("[mhosaic] submit:", err);
|
|
6328
6357
|
rerender({
|
|
6329
6358
|
...currentState,
|
|
6330
6359
|
status: "error",
|
|
@@ -6358,9 +6387,7 @@ function mountWidget(options) {
|
|
|
6358
6387
|
enabled: pageActivityEnabled
|
|
6359
6388
|
});
|
|
6360
6389
|
const fabLabel = pageSignal.open === 1 ? options.strings["page.badge.aria.one"] : pageSignal.open > 0 ? options.strings["page.badge.aria"].replace("{n}", String(pageSignal.open)) : options.strings["fab.label"];
|
|
6361
|
-
const [visibilityAllowed, setVisibilityAllowed] = useState10(
|
|
6362
|
-
!options.requiresVisibilityCheck
|
|
6363
|
-
);
|
|
6390
|
+
const [visibilityAllowed, setVisibilityAllowed] = useState10(!options.requiresVisibilityCheck);
|
|
6364
6391
|
useEffect10(() => {
|
|
6365
6392
|
if (!options.requiresVisibilityCheck) {
|
|
6366
6393
|
setVisibilityAllowed(true);
|
|
@@ -6564,6 +6591,9 @@ function mountWidget(options) {
|
|
|
6564
6591
|
},
|
|
6565
6592
|
onDirtyChange: (d) => {
|
|
6566
6593
|
formDirty = d;
|
|
6594
|
+
},
|
|
6595
|
+
...options.metaFeedbackEnabled !== void 0 && {
|
|
6596
|
+
metaFeedbackEnabled: options.metaFeedbackEnabled
|
|
6567
6597
|
}
|
|
6568
6598
|
}
|
|
6569
6599
|
)
|
|
@@ -6607,8 +6637,12 @@ function mountWidget(options) {
|
|
|
6607
6637
|
api: options.api,
|
|
6608
6638
|
externalId,
|
|
6609
6639
|
strings: options.strings,
|
|
6610
|
-
...options.getCurrentPage !== void 0 && {
|
|
6611
|
-
|
|
6640
|
+
...options.getCurrentPage !== void 0 && {
|
|
6641
|
+
getCurrentPage: options.getCurrentPage
|
|
6642
|
+
},
|
|
6643
|
+
...state.boardSelectId !== void 0 && {
|
|
6644
|
+
initialSelectedId: state.boardSelectId
|
|
6645
|
+
}
|
|
6612
6646
|
}
|
|
6613
6647
|
)
|
|
6614
6648
|
]
|
|
@@ -6623,9 +6657,7 @@ function mountWidget(options) {
|
|
|
6623
6657
|
if (!api || !externalId || !ref) return;
|
|
6624
6658
|
const seq = /^\d+$/.test(ref) ? parseInt(ref, 10) : null;
|
|
6625
6659
|
const resolve = seq !== null ? api.getReportBySeq(seq, externalId) : api.getReport(ref, externalId);
|
|
6626
|
-
void resolve.then(
|
|
6627
|
-
(r) => rerender({ ...currentState, open: true, tab: "mine", selectedReportId: r.id })
|
|
6628
|
-
).catch(() => {
|
|
6660
|
+
void resolve.then((r) => rerender({ ...currentState, open: true, tab: "mine", selectedReportId: r.id })).catch(() => {
|
|
6629
6661
|
});
|
|
6630
6662
|
}
|
|
6631
6663
|
if (options.deepLinkParam !== false) {
|
|
@@ -6664,10 +6696,7 @@ function defaultQaPosition() {
|
|
|
6664
6696
|
function createFeedback(config) {
|
|
6665
6697
|
const env = config.env ?? "prod";
|
|
6666
6698
|
const locale = config.locale ?? (typeof navigator !== "undefined" ? navigator.language : void 0);
|
|
6667
|
-
const strings = resolveStrings(
|
|
6668
|
-
config.translations ?? {},
|
|
6669
|
-
locale !== void 0 ? { locale } : {}
|
|
6670
|
-
);
|
|
6699
|
+
const strings = resolveStrings(config.translations ?? {}, locale !== void 0 ? { locale } : {});
|
|
6671
6700
|
const capture = installCapture({
|
|
6672
6701
|
...config.sanitizeUrl !== void 0 && { sanitizeUrl: config.sanitizeUrl }
|
|
6673
6702
|
});
|
|
@@ -6722,14 +6751,15 @@ function createFeedback(config) {
|
|
|
6722
6751
|
capture_method: captureMethod,
|
|
6723
6752
|
technical_context
|
|
6724
6753
|
};
|
|
6725
|
-
if ("0.
|
|
6726
|
-
payload.widget_version = "0.
|
|
6754
|
+
if ("0.37.0") {
|
|
6755
|
+
payload.widget_version = "0.37.0";
|
|
6727
6756
|
}
|
|
6728
6757
|
if (manualScreenshots?.length) {
|
|
6729
6758
|
payload.screenshots = manualScreenshots;
|
|
6730
6759
|
payload.screenshot = manualScreenshots[0];
|
|
6731
6760
|
}
|
|
6732
6761
|
if (values.synthetic) payload.synthetic = true;
|
|
6762
|
+
if (values.meta) payload.meta = true;
|
|
6733
6763
|
const pagePath = currentPagePath(config);
|
|
6734
6764
|
if (pagePath) payload.page_path = pagePath;
|
|
6735
6765
|
if (user?.id !== void 0 && user.id !== null && user.id !== "") {
|
|
@@ -6779,12 +6809,15 @@ function createFeedback(config) {
|
|
|
6779
6809
|
...config.openToCurrentPageFeedback !== void 0 && {
|
|
6780
6810
|
openToCurrentPageFeedback: config.openToCurrentPageFeedback
|
|
6781
6811
|
},
|
|
6812
|
+
...config.metaFeedbackEnabled !== void 0 && {
|
|
6813
|
+
metaFeedbackEnabled: config.metaFeedbackEnabled
|
|
6814
|
+
},
|
|
6782
6815
|
...config.showPageActivity !== void 0 && {
|
|
6783
6816
|
showPageActivity: config.showPageActivity
|
|
6784
6817
|
}
|
|
6785
6818
|
};
|
|
6786
6819
|
const handle = mountWidget(mountOptions);
|
|
6787
|
-
if (config.openToCurrentPageFeedback === void 0 && config.showFAB !== false) {
|
|
6820
|
+
if ((config.openToCurrentPageFeedback === void 0 || config.metaFeedbackEnabled === void 0) && config.showFAB !== false) {
|
|
6788
6821
|
const behaviorFetch = config.fetchImpl ?? globalThis.fetch;
|
|
6789
6822
|
void (async () => {
|
|
6790
6823
|
try {
|
|
@@ -6797,6 +6830,10 @@ function createFeedback(config) {
|
|
|
6797
6830
|
if (typeof v === "boolean" && mountOptions.openToCurrentPageFeedback === void 0) {
|
|
6798
6831
|
mountOptions.openToCurrentPageFeedback = v;
|
|
6799
6832
|
}
|
|
6833
|
+
const meta = m?.config?.behavior?.metaFeedbackEnabled;
|
|
6834
|
+
if (typeof meta === "boolean" && mountOptions.metaFeedbackEnabled === void 0) {
|
|
6835
|
+
mountOptions.metaFeedbackEnabled = meta;
|
|
6836
|
+
}
|
|
6800
6837
|
} catch {
|
|
6801
6838
|
}
|
|
6802
6839
|
})();
|
|
@@ -6867,4 +6904,4 @@ function createFeedback(config) {
|
|
|
6867
6904
|
export {
|
|
6868
6905
|
createFeedback
|
|
6869
6906
|
};
|
|
6870
|
-
//# sourceMappingURL=chunk-
|
|
6907
|
+
//# sourceMappingURL=chunk-C3ILJCVI.mjs.map
|