@mhosaic/feedback 0.4.0 → 0.5.1

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.
@@ -0,0 +1,45 @@
1
+ import { F as FeedbackApi } from './types-BCay5lo8.js';
2
+
3
+ /**
4
+ * Always-on debugger module. Hooks `window.error` and `unhandledrejection`,
5
+ * then ships each fresh runtime error as a synthetic FeedbackReport via
6
+ * the existing widget submit pipeline.
7
+ *
8
+ * Same opt-in pattern as `withReplay` / `withWebVitals` — host apps that
9
+ * want curated-feedback-only simply don't import it.
10
+ *
11
+ * Design choices (see PR description):
12
+ * - Per-fingerprint cooldown (default 5 min) absorbs render loops without
13
+ * flooding the operator dashboard or burning the per-key write throttle.
14
+ * - Server-side fingerprinting groups across sessions; the client-side
15
+ * fingerprint exists only to dedup within a single page lifetime, so
16
+ * it doesn't need to match the server's hash.
17
+ * - Recursion guard: an error thrown *inside* the submit pipeline must
18
+ * not re-enter the handler.
19
+ * - Description is the error message truncated at 200 chars — the full
20
+ * stack lives in technical_context.errors[].
21
+ * - No screenshot is taken (buildAndSubmit honors `synthetic: true`):
22
+ * html2canvas is slow + the DOM is unreliable mid-error.
23
+ */
24
+
25
+ interface ErrorTrackingOptions {
26
+ /** Opt-out kill switch without removing the import. Default `true`. */
27
+ enabled?: boolean;
28
+ /**
29
+ * Drop repeats of the same fingerprint within this window.
30
+ * Default 300_000 ms (5 minutes). Set to 0 to disable dedup
31
+ * (every fired error becomes a report — useful only for tests).
32
+ */
33
+ perFingerprintCooldownMs?: number;
34
+ /**
35
+ * Probabilistic head sampling: 1.0 captures everything, 0.5 drops half.
36
+ * Default 1.0. Sampled-out errors don't count toward the cooldown — if
37
+ * you sample at 0.1, you'll still see ~10% of the volume per fingerprint.
38
+ */
39
+ sampleRate?: number;
40
+ /** Max characters in the auto-generated description. Default 200. */
41
+ maxDescriptionLen?: number;
42
+ }
43
+ declare function withErrorTracking(fb: FeedbackApi, options?: ErrorTrackingOptions): FeedbackApi;
44
+
45
+ export { type ErrorTrackingOptions, withErrorTracking };
@@ -0,0 +1,111 @@
1
+ // src/modules/error-tracking.ts
2
+ var DEFAULTS = {
3
+ enabled: true,
4
+ perFingerprintCooldownMs: 5 * 60 * 1e3,
5
+ sampleRate: 1,
6
+ maxDescriptionLen: 200
7
+ };
8
+ function normalize(thrown) {
9
+ if (thrown instanceof Error) {
10
+ return {
11
+ name: thrown.name || "Error",
12
+ message: String(thrown.message ?? ""),
13
+ ...thrown.stack && { stack: String(thrown.stack) }
14
+ };
15
+ }
16
+ if (typeof thrown === "string") return { name: "Error", message: thrown };
17
+ if (thrown && typeof thrown === "object") {
18
+ const o = thrown;
19
+ return {
20
+ name: typeof o.name === "string" && o.name || "Error",
21
+ message: typeof o.message === "string" && o.message || (() => {
22
+ try {
23
+ return JSON.stringify(thrown);
24
+ } catch {
25
+ return String(thrown);
26
+ }
27
+ })(),
28
+ ...typeof o.stack === "string" && { stack: o.stack }
29
+ };
30
+ }
31
+ return { name: "Error", message: String(thrown) };
32
+ }
33
+ function clientFingerprint(err, pathname) {
34
+ const firstFrames = (err.stack ?? "").split("\n").slice(0, 4).join("\n");
35
+ return `${err.name}:${err.message}|${pathname}|${firstFrames}`;
36
+ }
37
+ function truncate(s, max) {
38
+ if (s.length <= max) return s;
39
+ return s.slice(0, Math.max(0, max - 1)) + "\u2026";
40
+ }
41
+ function withErrorTracking(fb, options = {}) {
42
+ const opts = { ...DEFAULTS, ...options };
43
+ if (!opts.enabled) return fb;
44
+ if (typeof window === "undefined") return fb;
45
+ const internal = fb;
46
+ const inFlight = /* @__PURE__ */ new Set();
47
+ const lastSent = /* @__PURE__ */ new Map();
48
+ function shouldSend(fp, now) {
49
+ if (opts.perFingerprintCooldownMs <= 0) return true;
50
+ const prev = lastSent.get(fp);
51
+ if (prev !== void 0 && now - prev < opts.perFingerprintCooldownMs) {
52
+ return false;
53
+ }
54
+ if (lastSent.size > 256) {
55
+ const cutoff = now - opts.perFingerprintCooldownMs * 2;
56
+ for (const [k, v] of lastSent) {
57
+ if (v < cutoff) lastSent.delete(k);
58
+ }
59
+ }
60
+ return true;
61
+ }
62
+ async function report(err) {
63
+ if (opts.sampleRate < 1 && Math.random() >= opts.sampleRate) return;
64
+ const fp = clientFingerprint(err, window.location.pathname);
65
+ if (inFlight.has(fp)) return;
66
+ const now = Date.now();
67
+ if (!shouldSend(fp, now)) return;
68
+ lastSent.set(fp, now);
69
+ const description = truncate(
70
+ err.message ? `${err.name}: ${err.message}` : err.name,
71
+ opts.maxDescriptionLen
72
+ );
73
+ inFlight.add(fp);
74
+ try {
75
+ await internal.submit({
76
+ description,
77
+ feedback_type: "bug",
78
+ severity: "high",
79
+ synthetic: true
80
+ });
81
+ } catch {
82
+ } finally {
83
+ inFlight.delete(fp);
84
+ }
85
+ }
86
+ function onError(event) {
87
+ const candidate = event.error ?? {
88
+ name: "Error",
89
+ message: event.message,
90
+ stack: `at ${event.filename}:${event.lineno}:${event.colno}`
91
+ };
92
+ void report(normalize(candidate));
93
+ }
94
+ function onUnhandledRejection(event) {
95
+ void report(normalize(event.reason));
96
+ }
97
+ window.addEventListener("error", onError);
98
+ window.addEventListener("unhandledrejection", onUnhandledRejection);
99
+ const originalShutdown = internal.shutdown.bind(internal);
100
+ internal.shutdown = () => {
101
+ window.removeEventListener("error", onError);
102
+ window.removeEventListener("unhandledrejection", onUnhandledRejection);
103
+ lastSent.clear();
104
+ originalShutdown();
105
+ };
106
+ return fb;
107
+ }
108
+ export {
109
+ withErrorTracking
110
+ };
111
+ //# sourceMappingURL=error-tracking.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/error-tracking.ts"],"sourcesContent":["/**\n * Always-on debugger module. Hooks `window.error` and `unhandledrejection`,\n * then ships each fresh runtime error as a synthetic FeedbackReport via\n * the existing widget submit pipeline.\n *\n * Same opt-in pattern as `withReplay` / `withWebVitals` — host apps that\n * want curated-feedback-only simply don't import it.\n *\n * Design choices (see PR description):\n * - Per-fingerprint cooldown (default 5 min) absorbs render loops without\n * flooding the operator dashboard or burning the per-key write throttle.\n * - Server-side fingerprinting groups across sessions; the client-side\n * fingerprint exists only to dedup within a single page lifetime, so\n * it doesn't need to match the server's hash.\n * - Recursion guard: an error thrown *inside* the submit pipeline must\n * not re-enter the handler.\n * - Description is the error message truncated at 200 chars — the full\n * stack lives in technical_context.errors[].\n * - No screenshot is taken (buildAndSubmit honors `synthetic: true`):\n * html2canvas is slow + the DOM is unreliable mid-error.\n */\n\nimport type { FeedbackApi } from '../types'\n\nexport interface ErrorTrackingOptions {\n /** Opt-out kill switch without removing the import. Default `true`. */\n enabled?: boolean\n /**\n * Drop repeats of the same fingerprint within this window.\n * Default 300_000 ms (5 minutes). Set to 0 to disable dedup\n * (every fired error becomes a report — useful only for tests).\n */\n perFingerprintCooldownMs?: number\n /**\n * Probabilistic head sampling: 1.0 captures everything, 0.5 drops half.\n * Default 1.0. Sampled-out errors don't count toward the cooldown — if\n * you sample at 0.1, you'll still see ~10% of the volume per fingerprint.\n */\n sampleRate?: number\n /** Max characters in the auto-generated description. Default 200. */\n maxDescriptionLen?: number\n}\n\ninterface InternalApi extends FeedbackApi {\n _registerTransformer?: (fn: unknown) => void\n}\n\ninterface NormalizedError {\n name: string\n message: string\n stack?: string\n}\n\nconst DEFAULTS = {\n enabled: true,\n perFingerprintCooldownMs: 5 * 60 * 1000,\n sampleRate: 1,\n maxDescriptionLen: 200,\n} as const satisfies Required<ErrorTrackingOptions>\n\n/**\n * Pull a normalized {name, message, stack} from whatever the runtime\n * handed us. Browsers throw genuine Error subclasses for uncaught\n * exceptions but `unhandledrejection.reason` is sometimes a string,\n * a plain object, or undefined.\n */\nfunction normalize(thrown: unknown): NormalizedError {\n if (thrown instanceof Error) {\n return {\n name: thrown.name || 'Error',\n message: String(thrown.message ?? ''),\n ...(thrown.stack && { stack: String(thrown.stack) }),\n }\n }\n if (typeof thrown === 'string') return { name: 'Error', message: thrown }\n if (thrown && typeof thrown === 'object') {\n const o = thrown as Record<string, unknown>\n return {\n name: (typeof o.name === 'string' && o.name) || 'Error',\n message:\n (typeof o.message === 'string' && o.message) ||\n (() => {\n try { return JSON.stringify(thrown) } catch { return String(thrown) }\n })(),\n ...(typeof o.stack === 'string' && { stack: o.stack }),\n }\n }\n return { name: 'Error', message: String(thrown) }\n}\n\n/**\n * Stable per-session signature. Server still computes its own fingerprint\n * across sessions — this only needs to dedup within one page lifetime.\n * Stack frames are the strongest discriminator; pathname keeps two distinct\n * routes from collapsing onto the same key.\n */\nfunction clientFingerprint(err: NormalizedError, pathname: string): string {\n const firstFrames = (err.stack ?? '').split('\\n').slice(0, 4).join('\\n')\n return `${err.name}:${err.message}|${pathname}|${firstFrames}`\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s\n return s.slice(0, Math.max(0, max - 1)) + '…'\n}\n\nexport function withErrorTracking(\n fb: FeedbackApi,\n options: ErrorTrackingOptions = {},\n): FeedbackApi {\n const opts = { ...DEFAULTS, ...options }\n if (!opts.enabled) return fb\n if (typeof window === 'undefined') return fb\n\n const internal = fb as InternalApi\n\n // Recursion guard, scoped per-fingerprint: while we're posting a synthetic\n // report for fingerprint X, a re-entry for the SAME X (e.g. an error\n // raised by the submit pipeline itself) is dropped. Distinct errors that\n // fire concurrently still both go through.\n const inFlight = new Set<string>()\n\n // fingerprint -> last-sent epoch ms. Periodically pruned in shouldSend().\n const lastSent = new Map<string, number>()\n\n function shouldSend(fp: string, now: number): boolean {\n if (opts.perFingerprintCooldownMs <= 0) return true\n const prev = lastSent.get(fp)\n if (prev !== undefined && now - prev < opts.perFingerprintCooldownMs) {\n return false\n }\n // Cheap stale-entry sweep so the Map doesn't grow unboundedly on a\n // long-lived session with many distinct errors.\n if (lastSent.size > 256) {\n const cutoff = now - opts.perFingerprintCooldownMs * 2\n for (const [k, v] of lastSent) {\n if (v < cutoff) lastSent.delete(k)\n }\n }\n return true\n }\n\n async function report(err: NormalizedError) {\n if (opts.sampleRate < 1 && Math.random() >= opts.sampleRate) return\n\n const fp = clientFingerprint(err, window.location.pathname)\n if (inFlight.has(fp)) return\n const now = Date.now()\n if (!shouldSend(fp, now)) return\n lastSent.set(fp, now)\n\n const description = truncate(\n err.message ? `${err.name}: ${err.message}` : err.name,\n opts.maxDescriptionLen,\n )\n\n inFlight.add(fp)\n try {\n await internal.submit({\n description,\n feedback_type: 'bug',\n severity: 'high',\n synthetic: true,\n } as Parameters<FeedbackApi['submit']>[0] & { synthetic: boolean })\n } catch {\n // Swallow submit errors — propagating them would just trigger another\n // unhandledrejection and feed the loop. The host's `onError` callback\n // (configured on createFeedback) still fires for visibility.\n } finally {\n inFlight.delete(fp)\n }\n }\n\n function onError(event: ErrorEvent) {\n const candidate: unknown = event.error ?? {\n name: 'Error',\n message: event.message,\n stack: `at ${event.filename}:${event.lineno}:${event.colno}`,\n }\n void report(normalize(candidate))\n }\n\n function onUnhandledRejection(event: PromiseRejectionEvent) {\n void report(normalize(event.reason))\n }\n\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onUnhandledRejection)\n\n // Wrap shutdown so listeners come down with the widget. Without this, a\n // host that re-mounts the widget under React StrictMode would double the\n // listeners and emit each error twice.\n const originalShutdown = internal.shutdown.bind(internal)\n internal.shutdown = () => {\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onUnhandledRejection)\n lastSent.clear()\n originalShutdown()\n }\n\n return fb\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { a as FeedbackConfig, F as FeedbackApi, R as ReportTransformer } from './types-DuPc4tyk.js';
1
+ import { a as FeedbackConfig, F as FeedbackApi, R as ReportTransformer } from './types-BCay5lo8.js';
2
2
 
3
3
  interface InternalConfig extends FeedbackConfig {
4
4
  fetchImpl?: typeof fetch;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { c as createFeedback } from './index-Cbxcx_S_.js';
2
- export { C as CapturedContext, F as FeedbackApi, a as FeedbackConfig, b as FeedbackEnv, c as FeedbackSeverity, d as FeedbackType, e as ReportPayload, S as SubmittedReport, U as UserIdentity } from './types-DuPc4tyk.js';
1
+ export { c as createFeedback } from './index-DRmZlKSW.js';
2
+ export { C as CapturedContext, F as FeedbackApi, a as FeedbackConfig, b as FeedbackEnv, c as FeedbackSeverity, d as FeedbackType, e as ReportPayload, S as SubmittedReport, U as UserIdentity } from './types-BCay5lo8.js';
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createFeedback
3
- } from "./chunk-PT4HC4CI.mjs";
3
+ } from "./chunk-JD3LK22A.mjs";
4
4
  export {
5
5
  createFeedback
6
6
  };
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { I as InternalConfig } from './index-Cbxcx_S_.js';
4
- import { F as FeedbackApi } from './types-DuPc4tyk.js';
3
+ import { I as InternalConfig } from './index-DRmZlKSW.js';
4
+ import { F as FeedbackApi } from './types-BCay5lo8.js';
5
5
 
6
6
  interface FeedbackProviderProps extends InternalConfig {
7
7
  children?: ReactNode;
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createFeedback
3
- } from "./chunk-PT4HC4CI.mjs";
3
+ } from "./chunk-JD3LK22A.mjs";
4
4
 
5
5
  // src/react/FeedbackProvider.tsx
6
6
  import { createContext, useContext, useEffect, useRef, useState } from "react";
package/dist/replay.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as FeedbackApi } from './types-DuPc4tyk.js';
1
+ import { F as FeedbackApi } from './types-BCay5lo8.js';
2
2
 
3
3
  interface ReplayOptions {
4
4
  durationMs?: number;
@@ -90,6 +90,14 @@ interface CapturedContext {
90
90
  capturedAt: number;
91
91
  webVitals?: WebVitalEntry[];
92
92
  replay?: ReplayBlob;
93
+ /**
94
+ * Identity supplied via `identify()` — surfaces on the operator side so a
95
+ * stack trace isn't anonymous. Stored inside technical_context so it
96
+ * inherits the existing 512 KiB cap and serializer-free contract.
97
+ */
98
+ user?: UserIdentity;
99
+ /** Free-form host-supplied metadata via `setMetadata()`. */
100
+ metadata?: Record<string, unknown>;
93
101
  }
94
102
  interface ReportPayload {
95
103
  description: string;
@@ -101,6 +109,13 @@ interface ReportPayload {
101
109
  capture_method: CaptureMethod;
102
110
  technical_context: CapturedContext;
103
111
  screenshot?: Blob;
112
+ /**
113
+ * Set to `true` by `withErrorTracking()` for auto-captured runtime
114
+ * errors. The backend hides synthetic reports from the default operator
115
+ * list view; the SPA toggle exposes them grouped by fingerprint. Manual
116
+ * submissions never set this.
117
+ */
118
+ synthetic?: boolean;
104
119
  }
105
120
  interface SubmittedReport {
106
121
  id: string;
@@ -1,4 +1,4 @@
1
- import { F as FeedbackApi } from './types-DuPc4tyk.js';
1
+ import { F as FeedbackApi } from './types-BCay5lo8.js';
2
2
 
3
3
  interface WebVitalsOptions {
4
4
  reportAllChanges?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhosaic/feedback",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -19,6 +19,10 @@
19
19
  "./replay": {
20
20
  "types": "./dist/replay.d.ts",
21
21
  "import": "./dist/replay.mjs"
22
+ },
23
+ "./error-tracking": {
24
+ "types": "./dist/error-tracking.d.ts",
25
+ "import": "./dist/error-tracking.mjs"
22
26
  }
23
27
  },
24
28
  "files": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/api/client.ts","../src/capture/urlSanitizer.ts","../src/capture/device.ts","../src/capture/console.ts","../src/capture/network.ts","../src/capture/errors.ts","../src/capture/performance.ts","../src/capture/ringBuffer.ts","../src/capture/index.ts","../src/screenshot/index.ts","../src/widget/i18n.ts","../src/widget/mount.tsx","../src/widget/Fab.tsx","../src/widget/Form.tsx","../src/widget/Modal.tsx","../src/widget/styles.ts","../src/core.ts"],"sourcesContent":["import type { ReportPayload, SubmittedReport } from '../types'\n\nexport interface ApiClientOptions {\n apiKey: string\n endpoint: string\n fetch?: typeof fetch\n beforeSend?: (payload: ReportPayload) => ReportPayload | false | Promise<ReportPayload | false>\n}\n\nexport interface ApiClient {\n submitReport(payload: ReportPayload): Promise<SubmittedReport>\n}\n\nconst SCALAR_FIELDS: Array<keyof ReportPayload> = [\n 'description', 'feedback_type', 'severity', 'env', 'page_url', 'user_agent', 'capture_method',\n]\n\nexport function createApiClient(options: ApiClientOptions): ApiClient {\n // No silent fallback to a hardcoded host — a misconfigured deploy used\n // to leak reports to https://core.mhosaic.com (which doesn't even\n // resolve). Failing fast at construction makes the misconfig obvious.\n const endpoint = (options.endpoint ?? '').replace(/\\/+$/, '')\n if (!endpoint) {\n throw new Error(\n '[mhosaic-feedback] `endpoint` is required (e.g. \"https://feedback.example.com\").',\n )\n }\n const fetcher = options.fetch ?? globalThis.fetch\n\n async function submitReport(input: ReportPayload): Promise<SubmittedReport> {\n let payload: ReportPayload | false = input\n if (options.beforeSend) payload = await options.beforeSend(input)\n if (payload === false) throw new Error('Submission cancelled by beforeSend')\n\n const form = new FormData()\n for (const field of SCALAR_FIELDS) {\n form.append(field, String(payload[field]))\n }\n form.append('technical_context', JSON.stringify(payload.technical_context))\n if (payload.screenshot) form.append('screenshot', payload.screenshot, 'screenshot.png')\n\n const response = await fetcher(`${endpoint}/api/feedback/v1/reports/`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${options.apiKey}` },\n body: form,\n })\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n throw new Error(`Feedback submit failed: ${response.status} ${text}`)\n }\n return response.json() as Promise<SubmittedReport>\n }\n\n return { submitReport }\n}\n","const SENSITIVE = /token|key|password|secret|auth|session|sig/i\n\nexport function sanitizeUrl(url: string): string {\n try {\n // Accept absolute URLs or root-relative paths; reject everything else\n const isAbsolute = /^https?:\\/\\//i.test(url)\n const isRootRelative = url.startsWith('/')\n if (!isAbsolute && !isRootRelative) return url\n\n const base = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n const u = new URL(url, base)\n const clean = new URLSearchParams()\n u.searchParams.forEach((value, name) => {\n clean.set(name, SENSITIVE.test(name) ? '[redacted]' : value)\n })\n u.search = clean.toString()\n return u.toString()\n } catch {\n return url\n }\n}\n","import type { DeviceContext } from '../types'\n\nexport function collectDevice(): DeviceContext {\n const nav = navigator as Navigator & { connection?: { effectiveType?: string }; deviceMemory?: number }\n const connection = nav.connection?.effectiveType\n const deviceMemory = nav.deviceMemory\n const referrer = document.referrer || undefined\n return {\n viewport: { w: window.innerWidth, h: window.innerHeight, dpr: window.devicePixelRatio || 1 },\n screen: { w: window.screen.width, h: window.screen.height },\n platform: nav.platform,\n language: nav.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n timezoneOffset: new Date().getTimezoneOffset(),\n ...(connection !== undefined && { connection }),\n online: nav.onLine,\n ...(deviceMemory !== undefined && { deviceMemory }),\n hardwareConcurrency: nav.hardwareConcurrency,\n ...(referrer !== undefined && { referrer }),\n title: document.title,\n pathname: window.location.pathname,\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { ConsoleEntry } from '../types'\n\ntype ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug'\n\nfunction safeStringify(arg: unknown): string {\n if (arg == null) return String(arg)\n if (typeof arg === 'string') return arg\n if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg)\n if (arg instanceof Error) return `${arg.name}: ${arg.message}`\n if (arg instanceof Element) return `<${arg.tagName.toLowerCase()}>`\n try {\n return JSON.stringify(arg, (_k, v) => (typeof v === 'bigint' ? v.toString() : v))\n } catch {\n try { return String(arg) } catch { return '[unserializable]' }\n }\n}\n\nexport function installConsolePatch(buffer: RingBuffer<ConsoleEntry>): () => void {\n const levels: ConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug']\n const originals: Partial<Record<ConsoleLevel, (...args: unknown[]) => void>> = {}\n for (const level of levels) {\n const original = console[level] as ((...args: unknown[]) => void) | undefined\n if (typeof original !== 'function') continue\n originals[level] = original\n console[level] = function patched(...args: unknown[]) {\n try {\n const message = args.map(safeStringify).join(' ').slice(0, 2000)\n const entry: ConsoleEntry = { level, message, ts: Date.now() }\n if (level === 'error') {\n const stack = new Error().stack\n if (stack) entry.stack = stack.split('\\n').slice(2, 8).join('\\n')\n }\n buffer.push(entry)\n } catch { /* never break console */ }\n original.apply(console, args)\n }\n }\n return () => {\n for (const [level, fn] of Object.entries(originals)) {\n (console as unknown as Record<string, (...args: unknown[]) => void>)[level] = fn\n }\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { NetworkEntry } from '../types'\n\nexport function installFetchPatch(buffer: RingBuffer<NetworkEntry>, sanitize: (url: string) => string): () => void {\n if (typeof window === 'undefined' || typeof window.fetch !== 'function') return () => {}\n const original = window.fetch.bind(window)\n window.fetch = async function patched(input: RequestInfo | URL, init?: RequestInit) {\n const start = performance.now()\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url\n const method = (init?.method || (input instanceof Request ? input.method : 'GET')).toUpperCase()\n try {\n const response = await original(input, init)\n buffer.push({ url: sanitize(url), method, status: response.status, durationMs: Math.round(performance.now() - start), ts: Date.now() })\n return response\n } catch (err) {\n buffer.push({\n url: sanitize(url), method, status: 0,\n durationMs: Math.round(performance.now() - start),\n ts: Date.now(),\n error: err instanceof Error ? err.message : String(err),\n })\n throw err\n }\n }\n return () => { window.fetch = original }\n}\n\nexport function installXhrPatch(buffer: RingBuffer<NetworkEntry>, sanitize: (url: string) => string): () => void {\n if (typeof window === 'undefined' || typeof window.XMLHttpRequest !== 'function') return () => {}\n const Original = window.XMLHttpRequest\n const originalOpen = Original.prototype.open\n const originalSend = Original.prototype.send\n\n Original.prototype.open = function patchedOpen(this: XMLHttpRequest & { __mfb?: { method: string; url: string; start: number } }, method: string, url: string | URL) {\n this.__mfb = { method: method.toUpperCase(), url: typeof url === 'string' ? url : url.toString(), start: performance.now() }\n return originalOpen.apply(this, arguments as unknown as Parameters<typeof originalOpen>)\n }\n\n Original.prototype.send = function patchedSend(this: XMLHttpRequest & { __mfb?: { method: string; url: string; start: number } }, body?: Document | XMLHttpRequestBodyInit | null) {\n this.addEventListener('loadend', () => {\n try {\n const ctx = this.__mfb\n if (!ctx) return\n buffer.push({\n url: sanitize(ctx.url),\n method: ctx.method,\n status: this.status,\n durationMs: Math.round(performance.now() - ctx.start),\n ts: Date.now(),\n })\n } catch { /* noop */ }\n })\n return originalSend.call(this, body ?? null)\n }\n\n return () => {\n Original.prototype.open = originalOpen\n Original.prototype.send = originalSend\n }\n}\n","import type { RingBuffer } from './ringBuffer'\nimport type { ErrorEntry } from '../types'\n\nexport function installErrorHandlers(buffer: RingBuffer<ErrorEntry>): () => void {\n if (typeof window === 'undefined') return () => {}\n const onError = (e: ErrorEvent) => {\n const stack = e.error instanceof Error ? e.error.stack : undefined\n buffer.push({\n message: e.message || 'Unknown error',\n ...(stack !== undefined && { stack }),\n ts: Date.now(),\n source: 'window.error',\n })\n }\n const onRejection = (e: PromiseRejectionEvent) => {\n const reason = e.reason\n const message =\n reason instanceof Error ? reason.message :\n typeof reason === 'string' ? reason :\n (() => { try { return JSON.stringify(reason) } catch { return String(reason) } })()\n const stack = reason instanceof Error ? reason.stack : undefined\n buffer.push({\n message,\n ...(stack !== undefined && { stack }),\n ts: Date.now(),\n source: 'unhandledrejection',\n })\n }\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n return () => {\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n }\n}\n","export interface PerformanceSnapshot {\n navigation?: { type: string; duration: number }\n longTasks: { duration: number; startTime: number }[]\n slowResources: { name: string; duration: number; initiatorType: string }[]\n}\n\nexport function createPerformanceCollector(slowResourceMs = 1000) {\n const longTasks: PerformanceSnapshot['longTasks'] = []\n const slowResources: PerformanceSnapshot['slowResources'] = []\n let observer: PerformanceObserver | null = null\n\n if (typeof PerformanceObserver !== 'undefined') {\n try {\n observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (entry.entryType === 'longtask') {\n longTasks.push({ duration: entry.duration, startTime: entry.startTime })\n while (longTasks.length > 20) longTasks.shift()\n } else if (entry.entryType === 'resource') {\n const e = entry as PerformanceResourceTiming\n if (e.duration > slowResourceMs) {\n slowResources.push({ name: e.name, duration: e.duration, initiatorType: e.initiatorType })\n while (slowResources.length > 20) slowResources.shift()\n }\n }\n }\n })\n observer.observe({ entryTypes: ['longtask', 'resource'] })\n } catch { /* unsupported */ }\n }\n\n return {\n snapshot(): PerformanceSnapshot {\n const nav = typeof performance !== 'undefined' ? performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined : undefined\n const navigation = nav ? { type: nav.type, duration: nav.duration } : undefined\n return {\n ...(navigation !== undefined && { navigation }),\n longTasks: longTasks.slice(),\n slowResources: slowResources.slice(),\n }\n },\n dispose() { observer?.disconnect() },\n }\n}\n","export class RingBuffer<T> {\n private items: T[] = []\n constructor(private readonly max: number) {}\n\n push(item: T): void {\n this.items.push(item)\n while (this.items.length > this.max) this.items.shift()\n }\n\n snapshot(): T[] {\n return this.items.slice()\n }\n\n clear(): void {\n this.items.length = 0\n }\n}\n","import { sanitizeUrl } from './urlSanitizer'\nimport { collectDevice } from './device'\nimport { installConsolePatch } from './console'\nimport { installFetchPatch, installXhrPatch } from './network'\nimport { installErrorHandlers } from './errors'\nimport { createPerformanceCollector } from './performance'\nimport { RingBuffer } from './ringBuffer'\nimport type { CapturedContext, ConsoleEntry, ErrorEntry, NetworkEntry } from '../types'\n\nexport interface CaptureOptions {\n sanitizeUrl?: (url: string) => string\n maxConsole?: number\n maxNetwork?: number\n maxErrors?: number\n}\n\nexport interface CaptureHandle {\n snapshot(): CapturedContext\n clear(): void\n dispose(): void\n}\n\nexport function installCapture(options: CaptureOptions = {}): CaptureHandle {\n const { maxConsole = 50, maxNetwork = 50, maxErrors = 20 } = options\n const sanitize = options.sanitizeUrl ?? sanitizeUrl\n\n const consoleBuf = new RingBuffer<ConsoleEntry>(maxConsole)\n const networkBuf = new RingBuffer<NetworkEntry>(maxNetwork)\n const errorBuf = new RingBuffer<ErrorEntry>(maxErrors)\n\n const uninstallConsole = installConsolePatch(consoleBuf)\n const uninstallFetch = installFetchPatch(networkBuf, sanitize)\n const uninstallXhr = installXhrPatch(networkBuf, sanitize)\n const uninstallErrors = installErrorHandlers(errorBuf)\n const perf = createPerformanceCollector()\n\n return {\n snapshot(): CapturedContext {\n return {\n consoleLogs: consoleBuf.snapshot(),\n networkRequests: networkBuf.snapshot(),\n errors: errorBuf.snapshot(),\n device: collectDevice(),\n capturedAt: Date.now(),\n }\n },\n clear() {\n consoleBuf.clear(); networkBuf.clear(); errorBuf.clear()\n },\n dispose() {\n uninstallConsole(); uninstallFetch(); uninstallXhr(); uninstallErrors()\n perf.dispose()\n },\n }\n}\n","export function isMaskedElement(el: Element): boolean {\n return el.hasAttribute('data-mfb-mask') || el.classList.contains('mfb-mask')\n}\n\nexport function prepareMaskMatcher(selectors: string[]): (el: Element) => boolean {\n const joined = selectors.join(',').trim()\n if (!joined) return isMaskedElement\n return (el: Element) => isMaskedElement(el) || el.matches(joined)\n}\n\nexport interface ScreenshotOptions {\n mask?: string[]\n maxBytes?: number\n}\n\nexport async function takeScreenshot(target: Element, options: ScreenshotOptions = {}): Promise<Blob | null> {\n if (typeof document === 'undefined') return null\n try {\n const html2canvas = (await import('html2canvas-pro')).default\n const matcher = prepareMaskMatcher(options.mask ?? [])\n const canvas = await html2canvas(target as HTMLElement, {\n ignoreElements: (el) => matcher(el),\n useCORS: true,\n logging: false,\n backgroundColor: null,\n })\n return await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/png'))\n } catch {\n return null\n }\n}\n","export const DEFAULT_STRINGS = {\n 'fab.label': 'Send feedback',\n 'form.title': 'Send feedback',\n 'form.description.label': 'What happened?',\n 'form.description.placeholder': 'Describe the issue or idea in one or two sentences.',\n 'form.type.label': 'Type',\n 'form.severity.label': 'Severity',\n 'form.submit': 'Send',\n 'form.cancel': 'Cancel',\n 'form.submitting': 'Sending…',\n 'form.success': 'Thanks — your feedback was sent.',\n 'form.error': 'Could not send. Please try again.',\n 'type.bug': 'Bug',\n 'type.feature': 'Feature request',\n 'type.question': 'Question',\n 'type.praise': 'Praise',\n 'type.typo': 'Typo',\n 'severity.blocker': 'Blocker',\n 'severity.high': 'High',\n 'severity.medium': 'Medium',\n 'severity.low': 'Low',\n}\n\nexport type StringKey = keyof typeof DEFAULT_STRINGS\n\nconst FRENCH_STRINGS: Record<StringKey, string> = {\n 'fab.label': 'Envoyer un commentaire',\n 'form.title': 'Envoyer un commentaire',\n 'form.description.label': 'Qu’est-il arrivé ?',\n 'form.description.placeholder': 'Décrivez le problème ou l’idée en une ou deux phrases.',\n 'form.type.label': 'Type',\n 'form.severity.label': 'Sévérité',\n 'form.submit': 'Envoyer',\n 'form.cancel': 'Annuler',\n 'form.submitting': 'Envoi…',\n 'form.success': 'Merci — votre commentaire a été envoyé.',\n 'form.error': 'Échec d’envoi. Veuillez réessayer.',\n 'type.bug': 'Bogue',\n 'type.feature': 'Suggestion',\n 'type.question': 'Question',\n 'type.praise': 'Compliment',\n 'type.typo': 'Coquille',\n 'severity.blocker': 'Bloquant',\n 'severity.high': 'Élevée',\n 'severity.medium': 'Moyenne',\n 'severity.low': 'Faible',\n}\n\nconst LOCALE_PACKS: Record<string, Record<StringKey, string>> = {\n fr: FRENCH_STRINGS,\n}\n\ninterface ResolveOptions {\n locale?: string\n}\n\nfunction packFor(locale: string | undefined): Record<StringKey, string> | null {\n if (!locale) return null\n // Match the language subtag only (fr-CA, fr-FR → fr).\n const tag = locale.toLowerCase().split(/[-_]/)[0]!\n return LOCALE_PACKS[tag] ?? null\n}\n\nexport function resolveStrings(\n overrides: Record<string, string>,\n options: ResolveOptions = {},\n): Record<StringKey, string> {\n const localePack = packFor(options.locale) ?? {}\n return {\n ...DEFAULT_STRINGS,\n ...localePack,\n ...overrides,\n } as Record<StringKey, string>\n}\n","import { h, render } from 'preact'\nimport { useCallback, useState } from 'preact/hooks'\n\nimport { Fab } from './Fab'\nimport { Form, type FormValues } from './Form'\nimport { Modal } from './Modal'\nimport { WIDGET_STYLES } from './styles'\nimport type { StringKey } from './i18n'\n\nexport interface MountOptions {\n host: HTMLElement\n strings: Record<StringKey, string>\n showFAB: boolean\n onSubmit: (values: FormValues) => Promise<void>\n}\n\nexport interface MountHandle {\n open(): void\n close(): void\n dispose(): void\n}\n\ntype State = { open: boolean; status: 'idle' | 'submitting' | 'error' | 'success'; error?: string }\n\nexport function mountWidget(options: MountOptions): MountHandle {\n const shadow = options.host.attachShadow({ mode: 'open' })\n const style = document.createElement('style')\n style.textContent = WIDGET_STYLES\n shadow.appendChild(style)\n const mountPoint = document.createElement('div')\n shadow.appendChild(mountPoint)\n\n let currentState: State = { open: false, status: 'idle' }\n\n function rerender(state: State) {\n currentState = state\n render(h(Root, { state }), mountPoint)\n }\n\n function Root({ state }: { state: State }) {\n const handleSubmit = useCallback(async (values: FormValues) => {\n rerender({ open: true, status: 'submitting' })\n try {\n await options.onSubmit(values)\n rerender({ open: true, status: 'success' })\n setTimeout(() => rerender({ open: false, status: 'idle' }), 1200)\n } catch (err) {\n rerender({ open: true, status: 'error', error: err instanceof Error ? err.message : String(err) })\n }\n }, [])\n\n return (\n <>\n {options.showFAB && (\n <Fab\n label={options.strings['fab.label']}\n onClick={() => rerender({ ...currentState, open: true })}\n />\n )}\n {state.open && (\n <Modal onDismiss={() => rerender({ open: false, status: 'idle' })}>\n <Form\n strings={options.strings}\n onSubmit={handleSubmit}\n onCancel={() => rerender({ open: false, status: 'idle' })}\n status={state.status}\n {...(state.error !== undefined && { errorMessage: state.error })}\n />\n </Modal>\n )}\n </>\n )\n }\n\n rerender(currentState)\n\n return {\n open() { rerender({ ...currentState, open: true }) },\n close() { rerender({ ...currentState, open: false, status: 'idle' }) },\n dispose() {\n render(null, mountPoint)\n options.host.innerHTML = ''\n },\n }\n}\n","import { h } from 'preact'\n\ninterface FabProps {\n label: string\n onClick: () => void\n}\n\nexport function Fab({ label, onClick }: FabProps) {\n return (\n <button type=\"button\" class=\"fab\" aria-label={label} onClick={onClick}>\n 💬\n </button>\n )\n}\n","import { h } from 'preact'\nimport { useState } from 'preact/hooks'\n\nimport type { FeedbackSeverity, FeedbackType } from '../types'\nimport type { StringKey } from './i18n'\n\nexport type FormStatus = 'idle' | 'submitting' | 'error' | 'success'\n\nexport interface FormValues {\n description: string\n feedback_type: FeedbackType\n severity: FeedbackSeverity\n}\n\ninterface FormProps {\n strings: Record<StringKey, string>\n onSubmit: (values: FormValues) => void\n onCancel: () => void\n status: FormStatus\n errorMessage?: string\n}\n\nconst TYPES: FeedbackType[] = ['bug', 'feature', 'question', 'praise', 'typo']\nconst SEVERITIES: FeedbackSeverity[] = ['blocker', 'high', 'medium', 'low']\n\nexport function Form({ strings, onSubmit, onCancel, status, errorMessage }: FormProps) {\n const [description, setDescription] = useState('')\n const [feedbackType, setFeedbackType] = useState<FeedbackType>('bug')\n const [severity, setSeverity] = useState<FeedbackSeverity>('medium')\n const [localError, setLocalError] = useState('')\n\n const submitting = status === 'submitting'\n const submitLabel = submitting ? strings['form.submitting'] : strings['form.submit']\n\n const handleSubmit = (e: Event) => {\n e.preventDefault()\n if (!description.trim()) {\n setLocalError(strings['form.description.placeholder'])\n return\n }\n setLocalError('')\n onSubmit({ description: description.trim(), feedback_type: feedbackType, severity })\n }\n\n return (\n <form onSubmit={handleSubmit}>\n <h2>{strings['form.title']}</h2>\n\n <div class=\"field\">\n <label for=\"mfb-desc\">{strings['form.description.label']}</label>\n <textarea\n id=\"mfb-desc\"\n value={description}\n placeholder={strings['form.description.placeholder']}\n onInput={(e) => setDescription((e.target as HTMLTextAreaElement).value)}\n />\n </div>\n\n <div class=\"row\">\n <div class=\"field\">\n <label for=\"mfb-type\">{strings['form.type.label']}</label>\n <select\n id=\"mfb-type\"\n value={feedbackType}\n onChange={(e) => setFeedbackType((e.target as HTMLSelectElement).value as FeedbackType)}\n >\n {TYPES.map((t) => <option value={t}>{strings[`type.${t}` as StringKey]}</option>)}\n </select>\n </div>\n <div class=\"field\">\n <label for=\"mfb-sev\">{strings['form.severity.label']}</label>\n <select\n id=\"mfb-sev\"\n value={severity}\n onChange={(e) => setSeverity((e.target as HTMLSelectElement).value as FeedbackSeverity)}\n >\n {SEVERITIES.map((s) => <option value={s}>{strings[`severity.${s}` as StringKey]}</option>)}\n </select>\n </div>\n </div>\n\n {localError && <div class=\"error\">{localError}</div>}\n {status === 'error' && errorMessage && <div class=\"error\">{errorMessage}</div>}\n {status === 'success' && <div class=\"success\">{strings['form.success']}</div>}\n\n <div class=\"actions\">\n <button type=\"button\" class=\"btn\" onClick={onCancel} disabled={submitting}>{strings['form.cancel']}</button>\n <button type=\"submit\" class=\"btn btn--primary\" disabled={submitting}>{submitLabel}</button>\n </div>\n </form>\n )\n}\n","import { h, type ComponentChildren } from 'preact'\n\ninterface ModalProps {\n onDismiss: () => void\n children: ComponentChildren\n}\n\nexport function Modal({ onDismiss, children }: ModalProps) {\n return (\n <div\n class=\"backdrop\"\n role=\"presentation\"\n onClick={(e) => { if (e.target === e.currentTarget) onDismiss() }}\n >\n <div class=\"modal\" role=\"dialog\" aria-modal=\"true\">{children}</div>\n </div>\n )\n}\n","export const WIDGET_STYLES = `\n:host {\n --mfb-accent: #3b82f6;\n --mfb-accent-contrast: #ffffff;\n --mfb-bg: #ffffff;\n --mfb-surface: #f9fafb;\n --mfb-text: #0a0a0a;\n --mfb-text-muted: #6b7280;\n --mfb-border: #e5e7eb;\n --mfb-radius: 8px;\n --mfb-font: system-ui, -apple-system, sans-serif;\n --mfb-z-index: 2147483640;\n\n all: initial;\n font-family: var(--mfb-font);\n color: var(--mfb-text);\n position: fixed;\n z-index: var(--mfb-z-index);\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --mfb-bg: #111827;\n --mfb-surface: #1f2937;\n --mfb-text: #f9fafb;\n --mfb-text-muted: #9ca3af;\n --mfb-border: #374151;\n }\n}\n\n.fab {\n position: fixed;\n bottom: 24px;\n right: 24px;\n width: 52px;\n height: 52px;\n border-radius: 999px;\n background: var(--mfb-accent);\n color: var(--mfb-accent-contrast);\n border: none;\n cursor: pointer;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);\n font-size: 22px;\n display: grid;\n place-items: center;\n}\n\n.fab:focus-visible { outline: 2px solid var(--mfb-accent); outline-offset: 2px; }\n\n.backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n display: grid;\n place-items: center;\n}\n\n.modal {\n background: var(--mfb-bg);\n border-radius: calc(var(--mfb-radius) * 1.5);\n box-shadow: 0 20px 48px rgba(0, 0, 0, 0.25);\n width: min(420px, 92vw);\n padding: 20px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.modal h2 { margin: 0 0 4px; font-size: 18px; font-weight: 600; }\n\n.field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }\n\n.field label { color: var(--mfb-text-muted); }\n\n.field input, .field select, .field textarea {\n font-family: inherit;\n font-size: 14px;\n color: inherit;\n padding: 8px 10px;\n border: 1px solid var(--mfb-border);\n border-radius: var(--mfb-radius);\n background: var(--mfb-surface);\n}\n\n.field textarea { min-height: 88px; resize: vertical; }\n\n.row { display: flex; gap: 8px; }\n.row > * { flex: 1; }\n\n.actions { display: flex; gap: 8px; justify-content: flex-end; padding-top: 8px; }\n\n.btn {\n padding: 8px 14px;\n border-radius: var(--mfb-radius);\n border: 1px solid var(--mfb-border);\n background: var(--mfb-bg);\n color: var(--mfb-text);\n font: inherit;\n cursor: pointer;\n}\n\n.btn--primary {\n background: var(--mfb-accent);\n color: var(--mfb-accent-contrast);\n border-color: var(--mfb-accent);\n}\n\n.btn[disabled] { opacity: 0.6; cursor: not-allowed; }\n\n.error { color: #dc2626; font-size: 13px; }\n.success { color: #059669; font-size: 13px; }\n`\n","import { createApiClient } from './api/client'\nimport { installCapture } from './capture'\nimport { takeScreenshot } from './screenshot'\nimport { resolveStrings } from './widget/i18n'\nimport { mountWidget } from './widget/mount'\nimport type { FeedbackApi, FeedbackConfig, ReportPayload, ReportTransformer, UserIdentity } from './types'\n\nexport interface InternalConfig extends FeedbackConfig {\n fetchImpl?: typeof fetch\n}\n\ninterface WindowWithGlobal extends Window {\n mhosaicFeedback?: FeedbackApi\n}\n\nexport function createFeedback(config: InternalConfig): FeedbackApi & { _registerTransformer(fn: ReportTransformer): void } {\n const env = config.env ?? 'prod'\n const locale =\n config.locale ?? (typeof navigator !== 'undefined' ? navigator.language : undefined)\n const strings = resolveStrings(\n config.translations ?? {},\n locale !== undefined ? { locale } : {},\n )\n const capture = installCapture({\n ...(config.sanitizeUrl !== undefined && { sanitizeUrl: config.sanitizeUrl }),\n })\n const api = createApiClient({\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n ...(config.fetchImpl !== undefined && { fetch: config.fetchImpl }),\n ...(config.beforeSend !== undefined && { beforeSend: config.beforeSend }),\n })\n\n let user: UserIdentity | undefined = config.user\n let metadata: Record<string, unknown> = config.metadata ?? {}\n const transformers: ReportTransformer[] = []\n\n const host = document.createElement('div')\n host.className = 'mhosaic-feedback'\n if (config.attachTo) {\n const attach = typeof config.attachTo === 'string' ? document.querySelector(config.attachTo) : config.attachTo\n attach?.appendChild(host)\n } else {\n document.body.appendChild(host)\n }\n\n async function buildAndSubmit(values: { description: string; feedback_type?: string; severity?: string }) {\n const screenshot = await takeScreenshot(document.body, { mask: ['.mhosaic-feedback', '[data-mfb-mask]'] })\n const payload: ReportPayload = {\n description: values.description,\n feedback_type: (values.feedback_type ?? 'bug') as ReportPayload['feedback_type'],\n severity: (values.severity ?? 'medium') as ReportPayload['severity'],\n env,\n page_url: window.location.href,\n user_agent: navigator.userAgent,\n capture_method: screenshot ? 'html2canvas' : 'none',\n technical_context: capture.snapshot(),\n }\n if (screenshot) payload.screenshot = screenshot\n let finalPayload: ReportPayload = payload\n for (const t of transformers) finalPayload = await t(finalPayload)\n try {\n const result = await api.submitReport(finalPayload)\n config.onSubmitSuccess?.(result)\n capture.clear()\n return result\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n config.onError?.(error)\n throw error\n }\n }\n\n const handle = mountWidget({\n host,\n strings,\n showFAB: config.showFAB ?? true,\n onSubmit: async (values) => { await buildAndSubmit(values) },\n })\n\n const instance: FeedbackApi & { _registerTransformer(fn: ReportTransformer): void } = {\n show() { handle.open() },\n hide() { handle.close() },\n open(opts) { handle.open(); void opts },\n async submit(partial) {\n return buildAndSubmit({\n description: partial.description,\n ...(partial.feedback_type !== undefined && { feedback_type: partial.feedback_type }),\n ...(partial.severity !== undefined && { severity: partial.severity }),\n })\n },\n identify(u) { user = u; void user },\n setMetadata(kv) { metadata = { ...metadata, ...kv }; void metadata },\n shutdown() {\n handle.dispose()\n capture.dispose()\n host.remove()\n // Only release the global if it still points at *us* — otherwise a\n // newer instance has taken ownership and we mustn't blow it away.\n const w = window as unknown as WindowWithGlobal\n if (w.mhosaicFeedback === instance) {\n delete w.mhosaicFeedback\n }\n },\n _registerTransformer(fn: ReportTransformer) { transformers.push(fn) },\n }\n\n // Expose the instance globally for ad-hoc callers (DevTools, docs pages,\n // help-widget integrations). The most-recently-created instance wins.\n ;(window as unknown as WindowWithGlobal).mhosaicFeedback = instance\n\n return instance\n}\n"],"mappings":"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","names":["jsx","jsx","jsx","jsxs"]}