@mhosaic/feedback 0.17.0 → 0.18.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-TG4YNEXE.mjs → chunk-4QQI5ZYD.mjs} +3 -3
- package/dist/{chunk-RP46APZ6.mjs → chunk-IP4Y5EZE.mjs} +9 -2
- package/dist/chunk-IP4Y5EZE.mjs.map +1 -0
- package/dist/{chunk-QVCHZCDS.mjs → chunk-QJ562NTG.mjs} +3 -1
- package/dist/chunk-QJ562NTG.mjs.map +1 -0
- package/dist/embed.min.js +1 -1
- package/dist/error-tracking.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/loader/react.d.ts +6 -5
- package/dist/loader/react.mjs +3 -9
- package/dist/loader/react.mjs.map +1 -1
- package/dist/loader.d.ts +13 -2
- package/dist/loader.min.js +1 -1
- package/dist/loader.min.js.map +1 -1
- package/dist/loader.mjs +1 -1
- package/dist/react.mjs +4 -4
- package/dist/widget.min.js +6 -4
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-QVCHZCDS.mjs.map +0 -1
- package/dist/chunk-RP46APZ6.mjs.map +0 -1
- /package/dist/{chunk-TG4YNEXE.mjs.map → chunk-4QQI5ZYD.mjs.map} +0 -0
|
@@ -4756,8 +4756,8 @@ function createFeedback(config) {
|
|
|
4756
4756
|
capture_method: captureMethod,
|
|
4757
4757
|
technical_context
|
|
4758
4758
|
};
|
|
4759
|
-
if ("0.
|
|
4760
|
-
payload.widget_version = "0.
|
|
4759
|
+
if ("0.18.0") {
|
|
4760
|
+
payload.widget_version = "0.18.0";
|
|
4761
4761
|
}
|
|
4762
4762
|
if (manualScreenshot) payload.screenshot = manualScreenshot;
|
|
4763
4763
|
if (values.synthetic) payload.synthetic = true;
|
|
@@ -4847,4 +4847,4 @@ function createFeedback(config) {
|
|
|
4847
4847
|
export {
|
|
4848
4848
|
createFeedback
|
|
4849
4849
|
};
|
|
4850
|
-
//# sourceMappingURL=chunk-
|
|
4850
|
+
//# sourceMappingURL=chunk-4QQI5ZYD.mjs.map
|
|
@@ -174,7 +174,14 @@ async function loadAndAttach(config, deferred) {
|
|
|
174
174
|
...config,
|
|
175
175
|
// Forward the project's per-end-user gating flag from the manifest unless
|
|
176
176
|
// the host set it explicitly. The widget then runs the visibility check.
|
|
177
|
-
requiresVisibilityCheck: config.requiresVisibilityCheck ?? manifest.config?.requires_visibility_check ?? false
|
|
177
|
+
requiresVisibilityCheck: config.requiresVisibilityCheck ?? manifest.config?.requires_visibility_check ?? false,
|
|
178
|
+
// Error capture is armed INSIDE the bundle (the only auto-updating part
|
|
179
|
+
// of a loader install), so already-deployed hosts pick it up without a
|
|
180
|
+
// redeploy. Precedence: explicit host override > per-project manifest
|
|
181
|
+
// flag > on. The loader provider forwards its `errorTracking` prop here
|
|
182
|
+
// rather than wrapping host-side, so there's exactly one arm point (the
|
|
183
|
+
// real instance) — no deferred/real double-registration.
|
|
184
|
+
errorTracking: config.errorTracking ?? manifest.config?.error_tracking ?? true
|
|
178
185
|
});
|
|
179
186
|
deferred._attach(real);
|
|
180
187
|
}
|
|
@@ -182,4 +189,4 @@ async function loadAndAttach(config, deferred) {
|
|
|
182
189
|
export {
|
|
183
190
|
createFeedback
|
|
184
191
|
};
|
|
185
|
-
//# sourceMappingURL=chunk-
|
|
192
|
+
//# sourceMappingURL=chunk-IP4Y5EZE.mjs.map
|
|
@@ -0,0 +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 }\n /** Human-readable detail when `enabled` is false. */\n detail?: string\n}\n\nexport async function fetchManifest(\n endpoint: string,\n apiKey: string,\n signal?: AbortSignal,\n): Promise<Manifest> {\n const base = endpoint.replace(/\\/$/, '')\n const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`\n // credentials: 'omit' — we authenticate via the public pk_proj_ query\n // param; sending cookies would just trigger CORS preflights for no\n // reason and is unnecessary since the manifest endpoint doesn't read\n // any session.\n const init: RequestInit = { credentials: 'omit' }\n if (signal) init.signal = signal\n const res = await fetch(url, init)\n if (!res.ok) {\n throw new Error(\n `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`,\n )\n }\n const body = (await res.json()) as Manifest\n return body\n}\n","/**\n * A FeedbackApi-shaped object that queues method calls until the real\n * widget bundle finishes loading, then drains the queue against the real\n * implementation.\n *\n * Why we need this: hosts that already have code like\n * const fb = createFeedback({...})\n * fb.identify({id, email, name})\n * useEffect(() => fb.open(...), [])\n * shouldn't have to refactor for async. The loader's createFeedback\n * returns sync — a deferred handle that records intent, then replays it\n * once the real bundle arrives. Idiomatic queue-then-flush pattern.\n *\n * If the bundle never loads (network failure, SRI mismatch, kill switch),\n * `_fail()` is called instead. Void methods (identify/setMetadata/open/...)\n * silently drop in that case — the widget just doesn't appear. The\n * `submit` Promise rejects so callers can surface real errors.\n */\n\nimport type {\n FeedbackApi,\n ReportPayload,\n SubmittedReport,\n UserIdentity,\n} from '../types'\n\ntype VoidMethod = 'show' | 'hide' | 'shutdown'\ntype ArgMethod = 'open' | 'identify' | 'setMetadata'\n\ntype Queued =\n | { kind: 'void'; method: VoidMethod }\n | { kind: 'arg'; method: 'open'; arg: Parameters<FeedbackApi['open']>[0] }\n | { kind: 'arg'; method: 'identify'; arg: UserIdentity }\n | { kind: 'arg'; method: 'setMetadata'; arg: Record<string, unknown> }\n | {\n kind: 'submit'\n payload: Partial<ReportPayload> & { description: string }\n resolve: (r: SubmittedReport) => void\n reject: (e: unknown) => void\n }\n\nexport interface DeferredHandle extends FeedbackApi {\n /** Called by the loader once the bundle is loaded and a real API exists. */\n _attach(real: FeedbackApi): void\n /** Called by the loader if the bundle fails to load. */\n _fail(err: Error): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n\n function flush(item: Queued): void {\n if (real) {\n if (item.kind === 'void') {\n ;(real[item.method] as () => void)()\n } else if (item.kind === 'arg') {\n ;(real[item.method] as (a: unknown) => void)(item.arg)\n } else {\n // submit\n Promise.resolve(real.submit(item.payload)).then(\n item.resolve,\n item.reject,\n )\n }\n return\n }\n if (failure) {\n if (item.kind === 'submit') item.reject(failure)\n // Void / arg methods silently dropped — the widget never appeared.\n return\n }\n queue.push(item)\n }\n\n return {\n show() {\n flush({ kind: 'void', method: 'show' })\n },\n hide() {\n flush({ kind: 'void', method: 'hide' })\n },\n shutdown() {\n flush({ kind: 'void', method: 'shutdown' })\n },\n open(arg) {\n flush({ kind: 'arg', method: 'open', arg })\n },\n identify(arg) {\n flush({ kind: 'arg', method: 'identify', arg })\n },\n setMetadata(arg) {\n flush({ kind: 'arg', method: 'setMetadata', arg })\n },\n submit(payload) {\n return new Promise<SubmittedReport>((resolve, reject) => {\n flush({ kind: 'submit', payload, resolve, reject })\n })\n },\n _attach(impl) {\n real = impl\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n // Drain any pending submits with the failure; drop void/arg.\n for (const item of queue) {\n if (item.kind === 'submit') item.reject(err)\n }\n queue.length = 0\n },\n }\n}\n","/**\n * `@mhosaic/feedback/loader` — public entry for the loader architecture.\n *\n * Same shape as the direct-import path (`@mhosaic/feedback`), but the\n * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend\n * dictates via the manifest endpoint. Letting hosts switch from the\n * direct-import path to the loader path gives them auto-updates without\n * any further code changes — Mhosaic ships a new Release row, every host\n * picks up the new bundle on next page load.\n *\n * Public surface MUST mirror the direct-import path exactly:\n * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.\n * - Method calls before the bundle finishes loading queue up and replay.\n * - `submit()` returns a Promise that resolves after the bundle is up\n * and the real submit completes.\n *\n * If the manifest endpoint reports the widget is disabled for this\n * project (`enabled: false` — kill switch or no stable release configured),\n * the deferred handle silently no-ops void calls and rejects `submit()`\n * calls with a clear error. The host page never crashes.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport 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 })\n deferred._attach(real)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -46,6 +46,8 @@ function withErrorTracking(fb, options = {}) {
|
|
|
46
46
|
if (!opts.enabled) return fb;
|
|
47
47
|
if (typeof window === "undefined") return fb;
|
|
48
48
|
const internal = fb;
|
|
49
|
+
if (internal._errorTrackingArmed) return fb;
|
|
50
|
+
internal._errorTrackingArmed = true;
|
|
49
51
|
const inFlight = /* @__PURE__ */ new Set();
|
|
50
52
|
const lastSent = /* @__PURE__ */ new Map();
|
|
51
53
|
const recentSendTimes = [];
|
|
@@ -140,4 +142,4 @@ function withErrorTracking(fb, options = {}) {
|
|
|
140
142
|
export {
|
|
141
143
|
withErrorTracking
|
|
142
144
|
};
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
145
|
+
//# sourceMappingURL=chunk-QJ562NTG.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 * Global ceiling on synthetic submissions per minute, regardless of\n * fingerprint. Defends against a hostile / buggy page that throws\n * errors with attacker-varying `message` strings (counter, nonce) —\n * each unique message creates a fresh fingerprint, bypassing the\n * per-fingerprint cooldown, and would otherwise burn the project's\n * backend write quota.\n * Default 30. Set to 0 to disable the global cap (not recommended).\n */\n globalRatePerMinute?: number\n /**\n * Max number of distinct fingerprints emitted per page lifetime. Once\n * exceeded, NEW fingerprints are dropped (the cooldown still applies\n * to already-seen ones, so legitimate repeating errors keep cycling).\n * Defense against a page that synthesizes an unbounded number of\n * unique error shapes.\n * Default 200. Set to 0 to disable.\n */\n maxDistinctFingerprints?: number\n}\n\ninterface InternalApi extends FeedbackApi {\n _registerTransformer?: (fn: unknown) => void\n /** Set once withErrorTracking has armed this instance (idempotency guard). */\n _errorTrackingArmed?: boolean\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 globalRatePerMinute: 30,\n maxDistinctFingerprints: 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 *\n * SECURITY: we include only a 30-char message prefix, not the full message.\n * A hostile or buggy page that throws errors with counter-suffixed messages\n * (e.g. `Error 1`, `Error 2`, …) would otherwise produce a fresh fingerprint\n * per throw and bypass the per-fingerprint cooldown. The stack frames are\n * the same across those counter variants, so collapsing the message into a\n * prefix preserves real-error distinction while killing the counter bypass.\n */\nfunction clientFingerprint(err: NormalizedError, pathname: string): string {\n const firstFrames = (err.stack ?? '').split('\\n').slice(0, 4).join('\\n')\n const messagePrefix = err.message.slice(0, 30)\n return `${err.name}:${messagePrefix}|${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 // Idempotency guard. The same instance can now be armed from two places:\n // the host-side <FeedbackProvider> (opt-out default) AND the runtime\n // widget bundle (which self-arms on the loader path). A client who both\n // upgrades the provider and receives the self-arming bundle would\n // otherwise double-register the window listeners and file every error\n // twice. First arm wins; later calls are no-ops.\n if (internal._errorTrackingArmed) return fb\n internal._errorTrackingArmed = true\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 // Sliding-window of recent send timestamps for the global rate cap.\n // Trimmed in shouldSend(); never grows beyond globalRatePerMinute entries.\n const recentSendTimes: number[] = []\n\n function shouldSend(fp: string, now: number): boolean {\n // (1) Global rate ceiling — runs first because it doesn't depend on\n // fingerprint state. Defends against the \"counter-suffixed error\n // messages\" attack that would otherwise produce one unique\n // fingerprint per throw.\n if (opts.globalRatePerMinute > 0) {\n const windowStart = now - 60_000\n // Drop expired entries from the front of the sliding window.\n while (recentSendTimes.length > 0 && recentSendTimes[0]! < windowStart) {\n recentSendTimes.shift()\n }\n if (recentSendTimes.length >= opts.globalRatePerMinute) {\n return false\n }\n }\n // (2) Per-page cap on the number of distinct fingerprints. A hostile\n // page that synthesizes an unbounded number of unique error\n // shapes runs out of new slots after this many — already-seen\n // fingerprints keep cycling under the per-fp cooldown.\n if (\n opts.maxDistinctFingerprints > 0 &&\n !lastSent.has(fp) &&\n lastSent.size >= opts.maxDistinctFingerprints\n ) {\n return false\n }\n if (opts.perFingerprintCooldownMs <= 0) {\n recentSendTimes.push(now)\n return true\n }\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. First pass: drop\n // entries past `cooldown * 2`. If we're still above a hard cap of\n // 256 — i.e. an SPA that's been live for hours and keeps emitting\n // *fresh* fingerprints faster than they go stale — drop the oldest\n // entries until we're back to a comfortable ceiling. Map iteration\n // order is insertion order so the first N keys are the oldest.\n const HARD_CAP = 256\n const TARGET_AFTER_TRIM = 192\n if (lastSent.size > HARD_CAP) {\n const cutoff = now - opts.perFingerprintCooldownMs * 2\n for (const [k, v] of lastSent) {\n if (v < cutoff) lastSent.delete(k)\n }\n if (lastSent.size > HARD_CAP) {\n const toDrop = lastSent.size - TARGET_AFTER_TRIM\n let dropped = 0\n for (const k of lastSent.keys()) {\n if (dropped >= toDrop) break\n lastSent.delete(k)\n dropped++\n }\n }\n }\n recentSendTimes.push(now)\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":[]}
|
package/dist/embed.min.js
CHANGED
|
@@ -1690,5 +1690,5 @@
|
|
|
1690
1690
|
@media (prefers-reduced-motion: reduce) {
|
|
1691
1691
|
.skeleton-line { animation: none; }
|
|
1692
1692
|
}
|
|
1693
|
-
`;function po(e,t,r){return!(!e.showFAB||e.getExternalId!==void 0&&!t||e.requiresVisibilityCheck&&!r)}function $t(e){let t=e.host.attachShadow({mode:"open"}),r=document.createElement("style");r.textContent=It,t.appendChild(r);let n=document.createElement("div");t.appendChild(n);let a={open:!1,status:"idle",tab:"send"},i=null;function s(c){a=c,Ae(Be(f,{state:c}),n)}function l(c){let{selectedReportId:b,...d}=c;return d}function f({state:c}){var v;let b=_t(async p=>{s({...a,status:"submitting"});try{await e.onSubmit(p),s({...a,status:"success"}),i!==null&&clearTimeout(i),i=setTimeout(()=>{i=null,s({...a,open:!1,status:"idle",tab:e.api?"mine":"send"})},1200)}catch(u){typeof console!="undefined"&&console.warn("[mhosaic] submit:",u),s({...a,status:"error",error:e.strings["form.error"]})}},[]),d=(v=e.getExternalId)==null?void 0:v.call(e),[y,g]=C(!e.requiresVisibilityCheck);D(()=>{if(!e.requiresVisibilityCheck){g(!0);return}if(!d||!e.checkVisibility){g(!1);return}let p=!1;return e.checkVisibility(d).then(u=>{p||g(u)}).catch(()=>{p||g(!1)}),()=>{p=!0}},[d]);let k=po(e,d,y),_=!!(e.api&&d);return o(U,{children:[k&&o(Et,{label:e.strings["fab.label"],onClick:()=>s({...a,open:!0})}),c.open&&o(At,{onDismiss:()=>s(l({...a,open:!1,status:"idle"})),closeLabel:e.strings["form.close"],expanded:c.tab==="board",children:[_&&o("div",{class:"tab-strip",role:"tablist",children:[o("button",{type:"button",role:"tab","aria-selected":c.tab==="send",class:`tab-button ${c.tab==="send"?"is-active":""}`,onClick:()=>s(l({...a,tab:"send"})),children:e.strings["tab.send"]}),o("button",{type:"button",role:"tab","aria-selected":c.tab==="mine",class:`tab-button ${c.tab==="mine"?"is-active":""}`,onClick:()=>s(l({...a,tab:"mine"})),children:e.strings["tab.mine"]}),o("button",{type:"button",role:"tab","aria-selected":c.tab==="changelog",class:`tab-button ${c.tab==="changelog"?"is-active":""}`,onClick:()=>s(l({...a,tab:"changelog"})),children:e.strings["tab.changelog"]}),o("button",{type:"button",role:"tab","aria-selected":c.tab==="board",class:`tab-button tab-button--board ${c.tab==="board"?"is-active":""}`,onClick:()=>s(l({...a,tab:"board"})),children:e.strings["tab.board"]})]}),c.tab==="send"&&o(zt,{strings:e.strings,onSubmit:b,onCancel:()=>s({...a,open:!1,status:"idle"}),status:c.status,...c.error!==void 0&&{errorMessage:c.error}}),c.tab==="mine"&&e.api&&d&&!c.selectedReportId&&o(Ft,{api:e.api,externalId:d,strings:e.strings,onSelect:p=>s({...a,selectedReportId:p.id})}),(c.tab==="mine"||c.tab==="changelog")&&e.api&&d&&c.selectedReportId&&o(ye,{api:e.api,externalId:d,reportId:c.selectedReportId,strings:e.strings,onBack:()=>s(l({...a}))}),c.tab==="changelog"&&e.api&&d&&!c.selectedReportId&&o(Rt,{api:e.api,externalId:d,strings:e.strings,onSelect:p=>s({...a,selectedReportId:p.id})}),c.tab==="board"&&e.api&&d&&o(Ct,{api:e.api,externalId:d,strings:e.strings})]})]})}return s(a),{open(){s({...a,open:!0})},close(){s({...a,open:!1,status:"idle"})},dispose(){i!==null&&(clearTimeout(i),i=null),Ae(null,n),e.host.innerHTML=""},notifyIdentityChanged(){s({...a})}}}function He(e){var g,k,_,v,p,u;let t=(g=e.env)!=null?g:"prod",r=(k=e.locale)!=null?k:typeof navigator!="undefined"?navigator.language:void 0,n=Ze((_=e.translations)!=null?_:{},r!==void 0?{locale:r}:{}),a=Xe({...e.sanitizeUrl!==void 0&&{sanitizeUrl:e.sanitizeUrl}}),i=e.user,s=(v=e.metadata)!=null?v:{},l=Ne({apiKey:e.apiKey,endpoint:e.endpoint,...e.fetchImpl!==void 0&&{fetch:e.fetchImpl},...e.beforeSend!==void 0&&{beforeSend:e.beforeSend},getSignedIdentity:()=>!i||!i.userHash||!i.exp||!i.email?null:{userHash:i.userHash,exp:i.exp,email:i.email}}),f=[],c=document.createElement("div");if(c.className="mhosaic-feedback",e.attachTo){let m=typeof e.attachTo=="string"?document.querySelector(e.attachTo):e.attachTo;m==null||m.appendChild(c)}else document.body.appendChild(c);async function b(m){var P,K,B,O,N;let x=m.synthetic?void 0:m.screenshot,R=a.snapshot();if(i){let{userHash:H,exp:Z,...ae}=i;R.user=ae}s&&Object.keys(s).length>0&&(R.metadata={...s});let L=x?(P=m.capture_method)!=null?P:"manual":"none",T={description:m.description,feedback_type:(K=m.feedback_type)!=null?K:"bug",severity:(B=m.severity)!=null?B:"medium",env:t,page_url:window.location.href,user_agent:navigator.userAgent,capture_method:L,technical_context:R};T.widget_version="0.
|
|
1693
|
+
`;function po(e,t,r){return!(!e.showFAB||e.getExternalId!==void 0&&!t||e.requiresVisibilityCheck&&!r)}function $t(e){let t=e.host.attachShadow({mode:"open"}),r=document.createElement("style");r.textContent=It,t.appendChild(r);let n=document.createElement("div");t.appendChild(n);let a={open:!1,status:"idle",tab:"send"},i=null;function s(c){a=c,Ae(Be(f,{state:c}),n)}function l(c){let{selectedReportId:b,...d}=c;return d}function f({state:c}){var v;let b=_t(async p=>{s({...a,status:"submitting"});try{await e.onSubmit(p),s({...a,status:"success"}),i!==null&&clearTimeout(i),i=setTimeout(()=>{i=null,s({...a,open:!1,status:"idle",tab:e.api?"mine":"send"})},1200)}catch(u){typeof console!="undefined"&&console.warn("[mhosaic] submit:",u),s({...a,status:"error",error:e.strings["form.error"]})}},[]),d=(v=e.getExternalId)==null?void 0:v.call(e),[y,g]=C(!e.requiresVisibilityCheck);D(()=>{if(!e.requiresVisibilityCheck){g(!0);return}if(!d||!e.checkVisibility){g(!1);return}let p=!1;return e.checkVisibility(d).then(u=>{p||g(u)}).catch(()=>{p||g(!1)}),()=>{p=!0}},[d]);let k=po(e,d,y),_=!!(e.api&&d);return o(U,{children:[k&&o(Et,{label:e.strings["fab.label"],onClick:()=>s({...a,open:!0})}),c.open&&o(At,{onDismiss:()=>s(l({...a,open:!1,status:"idle"})),closeLabel:e.strings["form.close"],expanded:c.tab==="board",children:[_&&o("div",{class:"tab-strip",role:"tablist",children:[o("button",{type:"button",role:"tab","aria-selected":c.tab==="send",class:`tab-button ${c.tab==="send"?"is-active":""}`,onClick:()=>s(l({...a,tab:"send"})),children:e.strings["tab.send"]}),o("button",{type:"button",role:"tab","aria-selected":c.tab==="mine",class:`tab-button ${c.tab==="mine"?"is-active":""}`,onClick:()=>s(l({...a,tab:"mine"})),children:e.strings["tab.mine"]}),o("button",{type:"button",role:"tab","aria-selected":c.tab==="changelog",class:`tab-button ${c.tab==="changelog"?"is-active":""}`,onClick:()=>s(l({...a,tab:"changelog"})),children:e.strings["tab.changelog"]}),o("button",{type:"button",role:"tab","aria-selected":c.tab==="board",class:`tab-button tab-button--board ${c.tab==="board"?"is-active":""}`,onClick:()=>s(l({...a,tab:"board"})),children:e.strings["tab.board"]})]}),c.tab==="send"&&o(zt,{strings:e.strings,onSubmit:b,onCancel:()=>s({...a,open:!1,status:"idle"}),status:c.status,...c.error!==void 0&&{errorMessage:c.error}}),c.tab==="mine"&&e.api&&d&&!c.selectedReportId&&o(Ft,{api:e.api,externalId:d,strings:e.strings,onSelect:p=>s({...a,selectedReportId:p.id})}),(c.tab==="mine"||c.tab==="changelog")&&e.api&&d&&c.selectedReportId&&o(ye,{api:e.api,externalId:d,reportId:c.selectedReportId,strings:e.strings,onBack:()=>s(l({...a}))}),c.tab==="changelog"&&e.api&&d&&!c.selectedReportId&&o(Rt,{api:e.api,externalId:d,strings:e.strings,onSelect:p=>s({...a,selectedReportId:p.id})}),c.tab==="board"&&e.api&&d&&o(Ct,{api:e.api,externalId:d,strings:e.strings})]})]})}return s(a),{open(){s({...a,open:!0})},close(){s({...a,open:!1,status:"idle"})},dispose(){i!==null&&(clearTimeout(i),i=null),Ae(null,n),e.host.innerHTML=""},notifyIdentityChanged(){s({...a})}}}function He(e){var g,k,_,v,p,u;let t=(g=e.env)!=null?g:"prod",r=(k=e.locale)!=null?k:typeof navigator!="undefined"?navigator.language:void 0,n=Ze((_=e.translations)!=null?_:{},r!==void 0?{locale:r}:{}),a=Xe({...e.sanitizeUrl!==void 0&&{sanitizeUrl:e.sanitizeUrl}}),i=e.user,s=(v=e.metadata)!=null?v:{},l=Ne({apiKey:e.apiKey,endpoint:e.endpoint,...e.fetchImpl!==void 0&&{fetch:e.fetchImpl},...e.beforeSend!==void 0&&{beforeSend:e.beforeSend},getSignedIdentity:()=>!i||!i.userHash||!i.exp||!i.email?null:{userHash:i.userHash,exp:i.exp,email:i.email}}),f=[],c=document.createElement("div");if(c.className="mhosaic-feedback",e.attachTo){let m=typeof e.attachTo=="string"?document.querySelector(e.attachTo):e.attachTo;m==null||m.appendChild(c)}else document.body.appendChild(c);async function b(m){var P,K,B,O,N;let x=m.synthetic?void 0:m.screenshot,R=a.snapshot();if(i){let{userHash:H,exp:Z,...ae}=i;R.user=ae}s&&Object.keys(s).length>0&&(R.metadata={...s});let L=x?(P=m.capture_method)!=null?P:"manual":"none",T={description:m.description,feedback_type:(K=m.feedback_type)!=null?K:"bug",severity:(B=m.severity)!=null?B:"medium",env:t,page_url:window.location.href,user_agent:navigator.userAgent,capture_method:L,technical_context:R};T.widget_version="0.18.0",x&&(T.screenshot=x),m.synthetic&&(T.synthetic=!0),(i==null?void 0:i.id)!==void 0&&i.id!==null&&i.id!==""&&(T.user={id:String(i.id),...i.email!==void 0&&{email:i.email},...i.name!==void 0&&{name:i.name}});let F=T;for(let H of f)F=await H(F);try{let H=await l.submitReport(F);return(O=e.onSubmitSuccess)==null||O.call(e,H),a.clear(),H}catch(H){let Z=H instanceof Error?H:new Error(String(H));throw(N=e.onError)==null||N.call(e,Z),Z}}let d=$t({host:c,strings:n,showFAB:(p=e.showFAB)!=null?p:!0,onSubmit:async m=>{await b(m)},api:l,getExternalId:()=>(i==null?void 0:i.id)!==void 0&&i.id!==null&&i.id!==""?String(i.id):void 0,requiresVisibilityCheck:(u=e.requiresVisibilityCheck)!=null?u:!1,checkVisibility:m=>l.checkVisibility(m)}),y={show(){d.open()},hide(){d.close()},open(m){d.open()},async submit(m){return b({description:m.description,...m.feedback_type!==void 0&&{feedback_type:m.feedback_type},...m.severity!==void 0&&{severity:m.severity},...m.synthetic!==void 0&&{synthetic:m.synthetic},...m.screenshot!==void 0&&{screenshot:m.screenshot}})},identify(m){i=m,d.notifyIdentityChanged()},setMetadata(m){s={...s,...m}},shutdown(){d.dispose(),a.dispose(),c.remove();let m=window;m.mhosaicFeedback===y&&delete m.mhosaicFeedback},_registerTransformer(m){f.push(m)}};return window.mhosaicFeedback=y,y}var Ht=window;function uo(){let e=document.currentScript;if(!e)return null;let t=e.dataset.key,r=e.dataset.endpoint;if(!t||!r)return null;let n=e.dataset.env;return{apiKey:t,endpoint:r,...n!==void 0&&{env:n}}}var Q=null,Kt,Wt,we=(Wt=(Kt=Ht.Feedback)==null?void 0:Kt.q)!=null?Wt:[];function Nt(e){let[t,r]=e;if(t==="init"){if(Q)return;Q=He(r);return}if(!Q){we.push(e);return}t==="identify"?Q.identify(r):t==="setMetadata"?Q.setMetadata(r):t==="open"&&Q.open(r)}var Dt=uo();Dt&&(Q=He(Dt));for(;we.length>0;)Nt(we.shift());var fo=Object.assign(function(t,r){Nt([t,r])},{q:we});Ht.Feedback=fo;})();
|
|
1694
1694
|
//# sourceMappingURL=embed.min.js.map
|
package/dist/error-tracking.mjs
CHANGED
package/dist/index.mjs
CHANGED
package/dist/loader/react.d.ts
CHANGED
|
@@ -7,11 +7,12 @@ interface FeedbackProviderProps extends FeedbackConfig {
|
|
|
7
7
|
children?: ReactNode;
|
|
8
8
|
/**
|
|
9
9
|
* Auto-capture uncaught errors + unhandled rejections as synthetic
|
|
10
|
-
* reports
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* on
|
|
10
|
+
* reports. On the loader path the runtime bundle arms this itself, so we
|
|
11
|
+
* DON'T wrap host-side (that would double-register, once on the deferred
|
|
12
|
+
* handle and once on the real instance). Instead we forward the host's
|
|
13
|
+
* preference to the bundle. Leave it unset to let the project's manifest
|
|
14
|
+
* flag decide (which itself defaults on); pass `false` to opt out, or an
|
|
15
|
+
* options object to tune the cooldown / sampling / rate caps.
|
|
15
16
|
*/
|
|
16
17
|
errorTracking?: boolean | ErrorTrackingOptions;
|
|
17
18
|
}
|
package/dist/loader/react.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
withErrorTracking
|
|
3
|
-
} from "../chunk-QVCHZCDS.mjs";
|
|
4
1
|
import {
|
|
5
2
|
createFeedback
|
|
6
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-IP4Y5EZE.mjs";
|
|
7
4
|
|
|
8
5
|
// src/loader/react/FeedbackProvider.tsx
|
|
9
6
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
@@ -11,15 +8,12 @@ import { jsx } from "react/jsx-runtime";
|
|
|
11
8
|
var FeedbackContext = createContext(null);
|
|
12
9
|
function FeedbackProvider({
|
|
13
10
|
children,
|
|
14
|
-
errorTracking
|
|
11
|
+
errorTracking,
|
|
15
12
|
...config
|
|
16
13
|
}) {
|
|
17
14
|
const [api, setApi] = useState(null);
|
|
18
15
|
useEffect(() => {
|
|
19
|
-
const instance = createFeedback(config);
|
|
20
|
-
if (errorTracking !== false) {
|
|
21
|
-
withErrorTracking(instance, errorTracking === true ? void 0 : errorTracking);
|
|
22
|
-
}
|
|
16
|
+
const instance = createFeedback({ ...config, errorTracking });
|
|
23
17
|
setApi(instance);
|
|
24
18
|
return () => {
|
|
25
19
|
instance.shutdown();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/loader/react/FeedbackProvider.tsx"],"sourcesContent":["/** @jsxImportSource react */\n/**\n * React provider for the loader path (@mhosaic/feedback/loader/react).\n *\n * Mirrors the direct-import path's <FeedbackProvider> exactly — same\n * props, same `useFeedback()` hook, same rendering semantics — but uses\n * the loader's createFeedback under the hood, so the host's bundle\n * doesn't include the widget code (it gets fetched at runtime from the\n * Mhosaic-controlled bundle URL).\n *\n * Subtle difference vs the direct-import provider:\n * The deferred handle is available SYNCHRONOUSLY (so we can render\n * children immediately instead of waiting for the bundle to load).\n * useFeedback() returns the deferred handle from first paint; method\n * calls queue until the bundle attaches. The direct-import provider\n * used to delay rendering children until the api instance was ready;\n * the loader doesn't need that delay since the deferred handle\n * already absorbs the lag.\n */\n\nimport { createContext, useContext, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { createFeedback } from '../index'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/loader/react/FeedbackProvider.tsx"],"sourcesContent":["/** @jsxImportSource react */\n/**\n * React provider for the loader path (@mhosaic/feedback/loader/react).\n *\n * Mirrors the direct-import path's <FeedbackProvider> exactly — same\n * props, same `useFeedback()` hook, same rendering semantics — but uses\n * the loader's createFeedback under the hood, so the host's bundle\n * doesn't include the widget code (it gets fetched at runtime from the\n * Mhosaic-controlled bundle URL).\n *\n * Subtle difference vs the direct-import provider:\n * The deferred handle is available SYNCHRONOUSLY (so we can render\n * children immediately instead of waiting for the bundle to load).\n * useFeedback() returns the deferred handle from first paint; method\n * calls queue until the bundle attaches. The direct-import provider\n * used to delay rendering children until the api instance was ready;\n * the loader doesn't need that delay since the deferred handle\n * already absorbs the lag.\n */\n\nimport { createContext, useContext, useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { createFeedback } from '../index'\nimport type { ErrorTrackingOptions } from '../../modules/error-tracking'\nimport type { FeedbackApi, FeedbackConfig } from '../../types'\n\nconst FeedbackContext = createContext<FeedbackApi | null>(null)\n\ninterface FeedbackProviderProps extends FeedbackConfig {\n children?: ReactNode\n /**\n * Auto-capture uncaught errors + unhandled rejections as synthetic\n * reports. On the loader path the runtime bundle arms this itself, so we\n * DON'T wrap host-side (that would double-register, once on the deferred\n * handle and once on the real instance). Instead we forward the host's\n * preference to the bundle. Leave it unset to let the project's manifest\n * flag decide (which itself defaults on); pass `false` to opt out, or an\n * options object to tune the cooldown / sampling / rate caps.\n */\n errorTracking?: boolean | ErrorTrackingOptions\n}\n\nexport function FeedbackProvider({\n children,\n errorTracking,\n ...config\n}: FeedbackProviderProps) {\n const [api, setApi] = useState<FeedbackApi | null>(null)\n\n useEffect(() => {\n // Forward errorTracking into the loader config — the bundle is the\n // single arm point. `undefined` (the default) defers to the manifest.\n const instance = createFeedback({ ...config, errorTracking })\n setApi(instance)\n return () => {\n instance.shutdown()\n setApi(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [config.apiKey])\n\n // Unlike the direct-import provider, render children immediately —\n // the deferred handle is sync-available, queued calls replay once\n // the bundle attaches.\n return <FeedbackContext.Provider value={api}>{children}</FeedbackContext.Provider>\n}\n\nexport { FeedbackContext }\n\nexport function useFeedback(): FeedbackApi {\n const api = useContext(FeedbackContext)\n if (!api) throw new Error('useFeedback must be used inside <FeedbackProvider>')\n return api\n}\n"],"mappings":";;;;;AAoBA,SAAS,eAAe,YAAY,WAAW,gBAAgB;AA6CtD;AAtCT,IAAM,kBAAkB,cAAkC,IAAI;AAgBvD,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA0B;AACxB,QAAM,CAAC,KAAK,MAAM,IAAI,SAA6B,IAAI;AAEvD,YAAU,MAAM;AAGd,UAAM,WAAW,eAAe,EAAE,GAAG,QAAQ,cAAc,CAAC;AAC5D,WAAO,QAAQ;AACf,WAAO,MAAM;AACX,eAAS,SAAS;AAClB,aAAO,IAAI;AAAA,IACb;AAAA,EAEF,GAAG,CAAC,OAAO,MAAM,CAAC;AAKlB,SAAO,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,KAAM,UAAS;AACzD;AAIO,SAAS,cAA2B;AACzC,QAAM,MAAM,WAAW,eAAe;AACtC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,oDAAoD;AAC9E,SAAO;AACT;","names":[]}
|
package/dist/loader.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { a as FeedbackConfig, F as FeedbackApi } from './types-2dZ9bHAn.js';
|
|
2
|
+
import { ErrorTrackingOptions } from './error-tracking.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Fetch the widget-manifest from the Mhosaic backend.
|
|
@@ -30,6 +31,9 @@ interface Manifest {
|
|
|
30
31
|
share_reports_with_widget: boolean;
|
|
31
32
|
/** Phase 4: project gates the widget per end-user (default false). */
|
|
32
33
|
requires_visibility_check?: boolean;
|
|
34
|
+
/** Per-project switch for the bundle's default error capture. When the
|
|
35
|
+
* backend omits it, the bundle falls back to its own default (on). */
|
|
36
|
+
error_tracking?: boolean;
|
|
33
37
|
};
|
|
34
38
|
/** Human-readable detail when `enabled` is false. */
|
|
35
39
|
detail?: string;
|
|
@@ -57,6 +61,13 @@ interface Manifest {
|
|
|
57
61
|
* calls with a clear error. The host page never crashes.
|
|
58
62
|
*/
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
/** Loader config = the public widget config plus the loader-only knobs the
|
|
65
|
+
* host can set to override what the manifest dictates. */
|
|
66
|
+
type LoaderConfig = FeedbackConfig & {
|
|
67
|
+
/** Override the bundle's default error capture. Omit (or pass `undefined`)
|
|
68
|
+
* to let the project's manifest flag decide (which itself defaults on). */
|
|
69
|
+
errorTracking?: boolean | ErrorTrackingOptions | undefined;
|
|
70
|
+
};
|
|
71
|
+
declare function createFeedback(config: LoaderConfig): FeedbackApi;
|
|
61
72
|
|
|
62
|
-
export { FeedbackApi, FeedbackConfig, type Manifest, createFeedback };
|
|
73
|
+
export { FeedbackApi, FeedbackConfig, type LoaderConfig, type Manifest, createFeedback };
|
package/dist/loader.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var MhosaicFeedbackLoader=(()=>{var
|
|
1
|
+
"use strict";var MhosaicFeedbackLoader=(()=>{var l="data-mhosaic-feedback-bundle";async function f(t){var n,r;return(n=window.MhosaicFeedback)!=null&&n.createFeedback?window.MhosaicFeedback:document.querySelector(`script[${l}]`)?w((r=t.timeoutMs)!=null?r:3e4):new Promise((e,o)=>{var s;let a=document.createElement("script");a.src=t.bundleUrl,a.integrity=t.sriHash,a.crossOrigin="anonymous",a.async=!0,a.setAttribute(l,"1"),a.onload=()=>{var c;(c=window.MhosaicFeedback)!=null&&c.createFeedback?e(window.MhosaicFeedback):o(new Error("mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing \u2014 bundle/loader version mismatch?"))},a.onerror=()=>{o(new Error(`mhosaic-feedback: failed to load bundle from ${t.bundleUrl} (network, CSP, or SRI mismatch)`))},document.head.appendChild(a);let d=(s=t.timeoutMs)!=null?s:3e4;setTimeout(()=>{o(new Error(`mhosaic-feedback: bundle load timeout after ${d}ms`))},d)})}function w(t){return new Promise((i,n)=>{let r=Date.now(),e=()=>{var o;if((o=window.MhosaicFeedback)!=null&&o.createFeedback){i(window.MhosaicFeedback);return}if(Date.now()-r>t){n(new Error("mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)"));return}setTimeout(e,50)};e()})}async function b(t,i,n){let e=`${t.replace(/\/$/,"")}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(i)}`,o={credentials:"omit"};n&&(o.signal=n);let a=await fetch(e,o);if(!a.ok)throw new Error(`mhosaic-feedback: manifest fetch failed (HTTP ${a.status})`);return await a.json()}function k(){let t=null,i=null,n=[];function r(e){if(t){e.kind==="void"?t[e.method]():e.kind==="arg"?t[e.method](e.arg):Promise.resolve(t.submit(e.payload)).then(e.resolve,e.reject);return}if(i){e.kind==="submit"&&e.reject(i);return}n.push(e)}return{show(){r({kind:"void",method:"show"})},hide(){r({kind:"void",method:"hide"})},shutdown(){r({kind:"void",method:"shutdown"})},open(e){r({kind:"arg",method:"open",arg:e})},identify(e){r({kind:"arg",method:"identify",arg:e})},setMetadata(e){r({kind:"arg",method:"setMetadata",arg:e})},submit(e){return new Promise((o,a)=>{r({kind:"submit",payload:e,resolve:o,reject:a})})},_attach(e){for(t=e;n.length>0;)r(n.shift())},_fail(e){i=e;for(let o of n)o.kind==="submit"&&o.reject(e);n.length=0}}}function p(t){let i=k();return y(t,i).catch(n=>{console.warn("[mhosaic-feedback] widget did not load:",n.message),i._fail(n)}),i}async function y(t,i){var o,a,d,s,c,u;let n=await b(t.endpoint,t.apiKey);if(!n.enabled||!n.bundle_url||!n.sri_hash){i._fail(new Error(`mhosaic-feedback: widget disabled for this project${n.detail?` \u2014 ${n.detail}`:""}`));return}let e=(await f({bundleUrl:n.bundle_url,sriHash:n.sri_hash})).createFeedback({...t,requiresVisibilityCheck:(d=(a=t.requiresVisibilityCheck)!=null?a:(o=n.config)==null?void 0:o.requires_visibility_check)!=null?d:!1,errorTracking:(u=(c=t.errorTracking)!=null?c:(s=n.config)==null?void 0:s.error_tracking)!=null?u:!0});i._attach(e)}function F(){let t=document.currentScript;if(!t)return null;let i=t.dataset.key,n=t.dataset.endpoint;if(!i||!n)return null;let r=t.dataset.env;return{apiKey:i,endpoint:n,...r!==void 0&&{env:r}}}var m=F(),h,g;if(m){let n=function(e){switch(e.name){case"identify":t.identify(e.args[0]);break;case"setMetadata":t.setMetadata(e.args[0]);break;case"open":t.open(e.args[0]);break;case"show":t.show();break;case"hide":t.hide();break;default:console.warn("[mhosaic-feedback] unknown queue call:",e.name)}};M=n;let t=p(m),i=(g=(h=window.Feedback)==null?void 0:h.q)!=null?g:[];for(let e of i)n(e);let r=Object.assign(function(o,...a){n({name:o,args:a})},{q:[]});window.Feedback=r}var M;})();
|
|
2
2
|
//# sourceMappingURL=loader.min.js.map
|
package/dist/loader.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.ts","../src/loader-embed.ts"],"sourcesContent":["/**\n * Inject the widget bundle as an SRI-protected <script> and wait for its\n * window global to appear.\n *\n * Why a <script> tag and not `import()`:\n * `import()` can fetch cross-origin ESM but doesn't honor Subresource\n * Integrity on the dynamic-import call itself. A <script> tag with\n * `integrity=…` is the only path where the browser actually refuses\n * to execute a bundle whose bytes don't match the manifest's hash.\n *\n * The bundle is built with `tsup --format iife --globalName MhosaicFeedback`,\n * so once it parses it sets `window.MhosaicFeedback = { createFeedback }`\n * (per `src/widget.ts`'s exports). We wait for that to appear.\n *\n * Idempotency: if multiple FeedbackProviders mount on the same page (e.g.\n * during a React fast-refresh cycle), we reuse an already-injected bundle\n * instead of fetching a second copy.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\n\ndeclare global {\n interface Window {\n MhosaicFeedback?: {\n createFeedback(config: FeedbackConfig): FeedbackApi\n }\n }\n}\n\nconst SCRIPT_DATA_ATTR = 'data-mhosaic-feedback-bundle'\n\nexport interface BundleLoadOptions {\n bundleUrl: string\n sriHash: string\n /** Default 30 s. Long enough for slow networks; short enough to surface\n * CDN outages quickly. */\n timeoutMs?: number\n}\n\nexport interface BundleHandle {\n createFeedback(config: FeedbackConfig): FeedbackApi\n}\n\nexport async function injectBundle(\n opts: BundleLoadOptions,\n): Promise<BundleHandle> {\n // Reuse a bundle already loaded this page.\n if (window.MhosaicFeedback?.createFeedback) {\n return window.MhosaicFeedback\n }\n\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_DATA_ATTR}]`,\n )\n if (existing) {\n // Another loader instance started the fetch; wait for the global.\n return waitForGlobal(opts.timeoutMs ?? 30_000)\n }\n\n return new Promise<BundleHandle>((resolve, reject) => {\n const script = document.createElement('script')\n script.src = opts.bundleUrl\n script.integrity = opts.sriHash\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute(SCRIPT_DATA_ATTR, '1')\n script.onload = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n } else {\n reject(\n new Error(\n 'mhosaic-feedback: bundle loaded but window.MhosaicFeedback.createFeedback is missing — bundle/loader version mismatch?',\n ),\n )\n }\n }\n script.onerror = () => {\n reject(\n new Error(\n `mhosaic-feedback: failed to load bundle from ${opts.bundleUrl} ` +\n '(network, CSP, or SRI mismatch)',\n ),\n )\n }\n document.head.appendChild(script)\n\n const timeoutMs = opts.timeoutMs ?? 30_000\n setTimeout(() => {\n reject(\n new Error(`mhosaic-feedback: bundle load timeout after ${timeoutMs}ms`),\n )\n }, timeoutMs)\n })\n}\n\nfunction waitForGlobal(timeoutMs: number): Promise<BundleHandle> {\n return new Promise((resolve, reject) => {\n const start = Date.now()\n const tick = () => {\n if (window.MhosaicFeedback?.createFeedback) {\n resolve(window.MhosaicFeedback)\n return\n }\n if (Date.now() - start > timeoutMs) {\n reject(\n new Error(\n 'mhosaic-feedback: timed out waiting for bundle global (another loader instance started the fetch but never finished)',\n ),\n )\n return\n }\n setTimeout(tick, 50)\n }\n tick()\n })\n}\n","/**\n * Fetch the widget-manifest from the Mhosaic backend.\n *\n * The loader calls this on every page load. The backend reads `Project.\n * pinned_version` (or current stable) + `Project.widget_enabled` and\n * returns the bundle URL + SRI hash for the version this tenant should\n * receive — or `{enabled: false}` if the widget is killed for the tenant.\n *\n * Network failures are surfaced to the caller. The loader degrades to\n * \"widget not present\" rather than crashing the host page.\n */\n\nexport interface Manifest {\n /** False when the widget is disabled for this project (kill switch) or\n * no stable release exists. Loader bails silently. */\n enabled: boolean\n /** Semver of the bundle being served. Present when enabled=true. */\n version?: string\n /** Fully-qualified URL to the version-pinned bundle (jsDelivr). */\n bundle_url?: string\n /** Subresource integrity hash for the bundle. The loader injects the\n * script tag with `integrity=…` so the browser refuses to run a\n * bundle whose bytes don't match. */\n sri_hash?: string\n /** Static config the manifest endpoint passes through to the widget. */\n config?: {\n endpoint: string\n project_slug: string\n share_reports_with_widget: boolean\n /** Phase 4: project gates the widget per end-user (default false). */\n requires_visibility_check?: boolean\n }\n /** Human-readable detail when `enabled` is false. */\n detail?: string\n}\n\nexport async function fetchManifest(\n endpoint: string,\n apiKey: string,\n signal?: AbortSignal,\n): Promise<Manifest> {\n const base = endpoint.replace(/\\/$/, '')\n const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`\n // credentials: 'omit' — we authenticate via the public pk_proj_ query\n // param; sending cookies would just trigger CORS preflights for no\n // reason and is unnecessary since the manifest endpoint doesn't read\n // any session.\n const init: RequestInit = { credentials: 'omit' }\n if (signal) init.signal = signal\n const res = await fetch(url, init)\n if (!res.ok) {\n throw new Error(\n `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`,\n )\n }\n const body = (await res.json()) as Manifest\n return body\n}\n","/**\n * A FeedbackApi-shaped object that queues method calls until the real\n * widget bundle finishes loading, then drains the queue against the real\n * implementation.\n *\n * Why we need this: hosts that already have code like\n * const fb = createFeedback({...})\n * fb.identify({id, email, name})\n * useEffect(() => fb.open(...), [])\n * shouldn't have to refactor for async. The loader's createFeedback\n * returns sync — a deferred handle that records intent, then replays it\n * once the real bundle arrives. Idiomatic queue-then-flush pattern.\n *\n * If the bundle never loads (network failure, SRI mismatch, kill switch),\n * `_fail()` is called instead. Void methods (identify/setMetadata/open/...)\n * silently drop in that case — the widget just doesn't appear. The\n * `submit` Promise rejects so callers can surface real errors.\n */\n\nimport type {\n FeedbackApi,\n ReportPayload,\n SubmittedReport,\n UserIdentity,\n} from '../types'\n\ntype VoidMethod = 'show' | 'hide' | 'shutdown'\ntype ArgMethod = 'open' | 'identify' | 'setMetadata'\n\ntype Queued =\n | { kind: 'void'; method: VoidMethod }\n | { kind: 'arg'; method: 'open'; arg: Parameters<FeedbackApi['open']>[0] }\n | { kind: 'arg'; method: 'identify'; arg: UserIdentity }\n | { kind: 'arg'; method: 'setMetadata'; arg: Record<string, unknown> }\n | {\n kind: 'submit'\n payload: Partial<ReportPayload> & { description: string }\n resolve: (r: SubmittedReport) => void\n reject: (e: unknown) => void\n }\n\nexport interface DeferredHandle extends FeedbackApi {\n /** Called by the loader once the bundle is loaded and a real API exists. */\n _attach(real: FeedbackApi): void\n /** Called by the loader if the bundle fails to load. */\n _fail(err: Error): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n\n function flush(item: Queued): void {\n if (real) {\n if (item.kind === 'void') {\n ;(real[item.method] as () => void)()\n } else if (item.kind === 'arg') {\n ;(real[item.method] as (a: unknown) => void)(item.arg)\n } else {\n // submit\n Promise.resolve(real.submit(item.payload)).then(\n item.resolve,\n item.reject,\n )\n }\n return\n }\n if (failure) {\n if (item.kind === 'submit') item.reject(failure)\n // Void / arg methods silently dropped — the widget never appeared.\n return\n }\n queue.push(item)\n }\n\n return {\n show() {\n flush({ kind: 'void', method: 'show' })\n },\n hide() {\n flush({ kind: 'void', method: 'hide' })\n },\n shutdown() {\n flush({ kind: 'void', method: 'shutdown' })\n },\n open(arg) {\n flush({ kind: 'arg', method: 'open', arg })\n },\n identify(arg) {\n flush({ kind: 'arg', method: 'identify', arg })\n },\n setMetadata(arg) {\n flush({ kind: 'arg', method: 'setMetadata', arg })\n },\n submit(payload) {\n return new Promise<SubmittedReport>((resolve, reject) => {\n flush({ kind: 'submit', payload, resolve, reject })\n })\n },\n _attach(impl) {\n real = impl\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n // Drain any pending submits with the failure; drop void/arg.\n for (const item of queue) {\n if (item.kind === 'submit') item.reject(err)\n }\n queue.length = 0\n },\n }\n}\n","/**\n * `@mhosaic/feedback/loader` — public entry for the loader architecture.\n *\n * Same shape as the direct-import path (`@mhosaic/feedback`), but the\n * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend\n * dictates via the manifest endpoint. Letting hosts switch from the\n * direct-import path to the loader path gives them auto-updates without\n * any further code changes — Mhosaic ships a new Release row, every host\n * picks up the new bundle on next page load.\n *\n * Public surface MUST mirror the direct-import path exactly:\n * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.\n * - Method calls before the bundle finishes loading queue up and replay.\n * - `submit()` returns a Promise that resolves after the bundle is up\n * and the real submit completes.\n *\n * If the manifest endpoint reports the widget is disabled for this\n * project (`enabled: false` — kill switch or no stable release configured),\n * the deferred handle silently no-ops void calls and rejects `submit()`\n * calls with a clear error. The host page never crashes.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport { injectBundle } from './bundleLoader'\nimport { fetchManifest } from './manifest'\nimport { createDeferredApi, type DeferredHandle } from './deferredApi'\n\nexport type { FeedbackApi, FeedbackConfig } from '../types'\nexport type { Manifest } from './manifest'\n\nexport function createFeedback(config: FeedbackConfig): FeedbackApi {\n const deferred = createDeferredApi()\n loadAndAttach(config, deferred).catch((err: Error) => {\n // Surface to console — host devtools is the canonical channel for\n // widget bootstrap errors. Production telemetry is the bundle's job\n // (it can't run if it never loaded), so console.warn is the floor.\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] widget did not load:', err.message)\n deferred._fail(err)\n })\n return deferred\n}\n\nasync function loadAndAttach(\n config: FeedbackConfig,\n deferred: DeferredHandle,\n): Promise<void> {\n const manifest = await fetchManifest(config.endpoint, config.apiKey)\n if (!manifest.enabled || !manifest.bundle_url || !manifest.sri_hash) {\n // Disabled by Mhosaic (kill switch) or no stable release. Surface a\n // clear failure so submit() callers see an error, but no host crash.\n deferred._fail(\n new Error(\n `mhosaic-feedback: widget disabled for this project${\n manifest.detail ? ` — ${manifest.detail}` : ''\n }`,\n ),\n )\n return\n }\n const bundle = await injectBundle({\n bundleUrl: manifest.bundle_url,\n sriHash: manifest.sri_hash,\n })\n const real: FeedbackApi = bundle.createFeedback({\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 })\n deferred._attach(real)\n}\n","/**\n * IIFE loader for the plain HTML / CDN install path.\n *\n * Hosts drop in a single tag, forever:\n *\n * <script\n * src=\"https://cdn.jsdelivr.net/npm/@mhosaic/feedback@1/dist/loader.min.js\"\n * data-key=\"pk_proj_…\"\n * data-endpoint=\"https://software-factory-…\"\n * defer\n * ></script>\n *\n * The loader reads its own script tag's `data-key`/`data-endpoint`/`data-env`,\n * calls `createFeedback(…)` from `loader/index.ts`, and the deferred handle\n * does the rest (fetch manifest, inject the actual widget bundle with SRI).\n *\n * Once installed, hosts never touch this file again. Mhosaic publishes a new\n * Release row → every host's next page load picks up the new widget bundle.\n */\n\nimport { createFeedback } from './loader/index'\nimport type { FeedbackConfig } from './types'\n\ninterface QueuedCall {\n name: string\n args: unknown[]\n}\n\ninterface WindowFeedback {\n (name: string, ...args: unknown[]): void\n q?: QueuedCall[]\n}\n\ndeclare global {\n interface Window {\n Feedback?: WindowFeedback\n }\n}\n\nfunction readConfigFromScriptTag(): FeedbackConfig | null {\n const script = document.currentScript as HTMLScriptElement | null\n if (!script) return null\n const apiKey = script.dataset.key\n const endpoint = script.dataset.endpoint\n if (!apiKey || !endpoint) return null\n const env = script.dataset.env as FeedbackConfig['env'] | undefined\n return {\n apiKey,\n endpoint,\n ...(env !== undefined && { env }),\n }\n}\n\nconst config = readConfigFromScriptTag()\nif (config) {\n const fb = createFeedback(config)\n // Drain any pre-queued calls a host may have set up before this script\n // loaded (Google-Analytics-style queue pattern: `window.Feedback = window.\n // Feedback || function(){(window.Feedback.q=window.Feedback.q||[]).push(\n // arguments)}; Feedback('identify', {...})`).\n const pending = window.Feedback?.q ?? []\n function dispatch(call: QueuedCall): void {\n switch (call.name) {\n case 'identify':\n fb.identify(call.args[0] as Parameters<typeof fb.identify>[0])\n break\n case 'setMetadata':\n fb.setMetadata(call.args[0] as Parameters<typeof fb.setMetadata>[0])\n break\n case 'open':\n fb.open(call.args[0] as Parameters<typeof fb.open>[0])\n break\n case 'show':\n fb.show()\n break\n case 'hide':\n fb.hide()\n break\n // submit and shutdown intentionally omitted from the queue API —\n // they have return values that the queue pattern can't surface\n // synchronously.\n default:\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] unknown queue call:', call.name)\n }\n }\n for (const c of pending) dispatch(c)\n\n const fn = Object.assign(\n function Feedback(name: string, ...args: unknown[]): void {\n dispatch({ name, args })\n },\n { q: [] as QueuedCall[] },\n ) as WindowFeedback\n window.Feedback = fn\n}\n"],"mappings":"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","names":["SCRIPT_DATA_ATTR","injectBundle","opts","_a","_b","waitForGlobal","resolve","reject","script","timeoutMs","start","tick","fetchManifest","endpoint","apiKey","signal","url","init","res","createDeferredApi","real","failure","queue","flush","item","arg","payload","resolve","reject","impl","err","createFeedback","config","deferred","createDeferredApi","loadAndAttach","err","_a","_b","_c","manifest","fetchManifest","real","injectBundle","readConfigFromScriptTag","script","apiKey","endpoint","env","config","_a","_b","dispatch","call","fb","createFeedback","pending","c","fn","name","args"]}
|
|
1
|
+
{"version":3,"sources":["../src/loader/bundleLoader.ts","../src/loader/manifest.ts","../src/loader/deferredApi.ts","../src/loader/index.ts","../src/loader-embed.ts"],"sourcesContent":["/**\n * Inject the widget bundle as an SRI-protected <script> and wait for its\n * window global to appear.\n *\n * Why a <script> tag and not `import()`:\n * `import()` can fetch cross-origin ESM but doesn't honor Subresource\n * Integrity on the dynamic-import call itself. A <script> tag with\n * `integrity=…` is the only path where the browser actually refuses\n * to execute a bundle whose bytes don't match the manifest's hash.\n *\n * The bundle is built with `tsup --format iife --globalName MhosaicFeedback`,\n * so once it parses it sets `window.MhosaicFeedback = { createFeedback }`\n * (per `src/widget.ts`'s exports). We wait for that to appear.\n *\n * Idempotency: if multiple FeedbackProviders mount on the same page (e.g.\n * during a React fast-refresh cycle), we reuse an already-injected bundle\n * instead of fetching a second copy.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\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 }\n /** Human-readable detail when `enabled` is false. */\n detail?: string\n}\n\nexport async function fetchManifest(\n endpoint: string,\n apiKey: string,\n signal?: AbortSignal,\n): Promise<Manifest> {\n const base = endpoint.replace(/\\/$/, '')\n const url = `${base}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(apiKey)}`\n // credentials: 'omit' — we authenticate via the public pk_proj_ query\n // param; sending cookies would just trigger CORS preflights for no\n // reason and is unnecessary since the manifest endpoint doesn't read\n // any session.\n const init: RequestInit = { credentials: 'omit' }\n if (signal) init.signal = signal\n const res = await fetch(url, init)\n if (!res.ok) {\n throw new Error(\n `mhosaic-feedback: manifest fetch failed (HTTP ${res.status})`,\n )\n }\n const body = (await res.json()) as Manifest\n return body\n}\n","/**\n * A FeedbackApi-shaped object that queues method calls until the real\n * widget bundle finishes loading, then drains the queue against the real\n * implementation.\n *\n * Why we need this: hosts that already have code like\n * const fb = createFeedback({...})\n * fb.identify({id, email, name})\n * useEffect(() => fb.open(...), [])\n * shouldn't have to refactor for async. The loader's createFeedback\n * returns sync — a deferred handle that records intent, then replays it\n * once the real bundle arrives. Idiomatic queue-then-flush pattern.\n *\n * If the bundle never loads (network failure, SRI mismatch, kill switch),\n * `_fail()` is called instead. Void methods (identify/setMetadata/open/...)\n * silently drop in that case — the widget just doesn't appear. The\n * `submit` Promise rejects so callers can surface real errors.\n */\n\nimport type {\n FeedbackApi,\n ReportPayload,\n SubmittedReport,\n UserIdentity,\n} from '../types'\n\ntype VoidMethod = 'show' | 'hide' | 'shutdown'\ntype ArgMethod = 'open' | 'identify' | 'setMetadata'\n\ntype Queued =\n | { kind: 'void'; method: VoidMethod }\n | { kind: 'arg'; method: 'open'; arg: Parameters<FeedbackApi['open']>[0] }\n | { kind: 'arg'; method: 'identify'; arg: UserIdentity }\n | { kind: 'arg'; method: 'setMetadata'; arg: Record<string, unknown> }\n | {\n kind: 'submit'\n payload: Partial<ReportPayload> & { description: string }\n resolve: (r: SubmittedReport) => void\n reject: (e: unknown) => void\n }\n\nexport interface DeferredHandle extends FeedbackApi {\n /** Called by the loader once the bundle is loaded and a real API exists. */\n _attach(real: FeedbackApi): void\n /** Called by the loader if the bundle fails to load. */\n _fail(err: Error): void\n}\n\nexport function createDeferredApi(): DeferredHandle {\n let real: FeedbackApi | null = null\n let failure: Error | null = null\n const queue: Queued[] = []\n\n function flush(item: Queued): void {\n if (real) {\n if (item.kind === 'void') {\n ;(real[item.method] as () => void)()\n } else if (item.kind === 'arg') {\n ;(real[item.method] as (a: unknown) => void)(item.arg)\n } else {\n // submit\n Promise.resolve(real.submit(item.payload)).then(\n item.resolve,\n item.reject,\n )\n }\n return\n }\n if (failure) {\n if (item.kind === 'submit') item.reject(failure)\n // Void / arg methods silently dropped — the widget never appeared.\n return\n }\n queue.push(item)\n }\n\n return {\n show() {\n flush({ kind: 'void', method: 'show' })\n },\n hide() {\n flush({ kind: 'void', method: 'hide' })\n },\n shutdown() {\n flush({ kind: 'void', method: 'shutdown' })\n },\n open(arg) {\n flush({ kind: 'arg', method: 'open', arg })\n },\n identify(arg) {\n flush({ kind: 'arg', method: 'identify', arg })\n },\n setMetadata(arg) {\n flush({ kind: 'arg', method: 'setMetadata', arg })\n },\n submit(payload) {\n return new Promise<SubmittedReport>((resolve, reject) => {\n flush({ kind: 'submit', payload, resolve, reject })\n })\n },\n _attach(impl) {\n real = impl\n while (queue.length > 0) flush(queue.shift()!)\n },\n _fail(err) {\n failure = err\n // Drain any pending submits with the failure; drop void/arg.\n for (const item of queue) {\n if (item.kind === 'submit') item.reject(err)\n }\n queue.length = 0\n },\n }\n}\n","/**\n * `@mhosaic/feedback/loader` — public entry for the loader architecture.\n *\n * Same shape as the direct-import path (`@mhosaic/feedback`), but the\n * widget bundle is fetched at runtime from a CDN URL the Mhosaic backend\n * dictates via the manifest endpoint. Letting hosts switch from the\n * direct-import path to the loader path gives them auto-updates without\n * any further code changes — Mhosaic ships a new Release row, every host\n * picks up the new bundle on next page load.\n *\n * Public surface MUST mirror the direct-import path exactly:\n * - `createFeedback(config)` returns a `FeedbackApi`-shaped handle.\n * - Method calls before the bundle finishes loading queue up and replay.\n * - `submit()` returns a Promise that resolves after the bundle is up\n * and the real submit completes.\n *\n * If the manifest endpoint reports the widget is disabled for this\n * project (`enabled: false` — kill switch or no stable release configured),\n * the deferred handle silently no-ops void calls and rejects `submit()`\n * calls with a clear error. The host page never crashes.\n */\n\nimport type { FeedbackApi, FeedbackConfig } from '../types'\nimport 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 })\n deferred._attach(real)\n}\n","/**\n * IIFE loader for the plain HTML / CDN install path.\n *\n * Hosts drop in a single tag, forever:\n *\n * <script\n * src=\"https://cdn.jsdelivr.net/npm/@mhosaic/feedback@1/dist/loader.min.js\"\n * data-key=\"pk_proj_…\"\n * data-endpoint=\"https://software-factory-…\"\n * defer\n * ></script>\n *\n * The loader reads its own script tag's `data-key`/`data-endpoint`/`data-env`,\n * calls `createFeedback(…)` from `loader/index.ts`, and the deferred handle\n * does the rest (fetch manifest, inject the actual widget bundle with SRI).\n *\n * Once installed, hosts never touch this file again. Mhosaic publishes a new\n * Release row → every host's next page load picks up the new widget bundle.\n */\n\nimport { createFeedback } from './loader/index'\nimport type { FeedbackConfig } from './types'\n\ninterface QueuedCall {\n name: string\n args: unknown[]\n}\n\ninterface WindowFeedback {\n (name: string, ...args: unknown[]): void\n q?: QueuedCall[]\n}\n\ndeclare global {\n interface Window {\n Feedback?: WindowFeedback\n }\n}\n\nfunction readConfigFromScriptTag(): FeedbackConfig | null {\n const script = document.currentScript as HTMLScriptElement | null\n if (!script) return null\n const apiKey = script.dataset.key\n const endpoint = script.dataset.endpoint\n if (!apiKey || !endpoint) return null\n const env = script.dataset.env as FeedbackConfig['env'] | undefined\n return {\n apiKey,\n endpoint,\n ...(env !== undefined && { env }),\n }\n}\n\nconst config = readConfigFromScriptTag()\nif (config) {\n const fb = createFeedback(config)\n // Drain any pre-queued calls a host may have set up before this script\n // loaded (Google-Analytics-style queue pattern: `window.Feedback = window.\n // Feedback || function(){(window.Feedback.q=window.Feedback.q||[]).push(\n // arguments)}; Feedback('identify', {...})`).\n const pending = window.Feedback?.q ?? []\n function dispatch(call: QueuedCall): void {\n switch (call.name) {\n case 'identify':\n fb.identify(call.args[0] as Parameters<typeof fb.identify>[0])\n break\n case 'setMetadata':\n fb.setMetadata(call.args[0] as Parameters<typeof fb.setMetadata>[0])\n break\n case 'open':\n fb.open(call.args[0] as Parameters<typeof fb.open>[0])\n break\n case 'show':\n fb.show()\n break\n case 'hide':\n fb.hide()\n break\n // submit and shutdown intentionally omitted from the queue API —\n // they have return values that the queue pattern can't surface\n // synchronously.\n default:\n // eslint-disable-next-line no-console\n console.warn('[mhosaic-feedback] unknown queue call:', call.name)\n }\n }\n for (const c of pending) dispatch(c)\n\n const fn = Object.assign(\n function Feedback(name: string, ...args: unknown[]): void {\n dispatch({ name, args })\n },\n { q: [] as QueuedCall[] },\n ) as WindowFeedback\n window.Feedback = fn\n}\n"],"mappings":"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","names":["SCRIPT_DATA_ATTR","injectBundle","opts","_a","_b","waitForGlobal","resolve","reject","script","timeoutMs","start","tick","fetchManifest","endpoint","apiKey","signal","url","init","res","createDeferredApi","real","failure","queue","flush","item","arg","payload","resolve","reject","impl","err","createFeedback","config","deferred","createDeferredApi","loadAndAttach","err","_a","_b","_c","_d","_e","_f","manifest","fetchManifest","real","injectBundle","readConfigFromScriptTag","script","apiKey","endpoint","env","config","_a","_b","dispatch","call","fb","createFeedback","pending","c","fn","name","args"]}
|
package/dist/loader.mjs
CHANGED
package/dist/react.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createFeedback
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-4QOIFIT5.mjs";
|
|
5
|
-
import "./chunk-FGA63IEZ.mjs";
|
|
3
|
+
} from "./chunk-4QQI5ZYD.mjs";
|
|
6
4
|
import {
|
|
7
5
|
withErrorTracking
|
|
8
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QJ562NTG.mjs";
|
|
7
|
+
import "./chunk-4QOIFIT5.mjs";
|
|
8
|
+
import "./chunk-FGA63IEZ.mjs";
|
|
9
9
|
|
|
10
10
|
// src/react/FeedbackProvider.tsx
|
|
11
11
|
import { createContext, useContext, useEffect, useRef, useState } from "react";
|