@fixprompt/browser 0.0.1 → 0.0.2
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/breadcrumbs.d.ts +24 -0
- package/dist/console.d.ts +11 -0
- package/dist/index.cjs +76 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +76 -3
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +76 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +9 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +9 -2
- package/dist/react.js.map +1 -1
- package/dist/state.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory ring buffer of the most recent console log lines.
|
|
3
|
+
*
|
|
4
|
+
* These are NEVER streamed to Loki on their own (would balloon volume + cost
|
|
5
|
+
* without much value). Instead, every outgoing event from the SDK attaches
|
|
6
|
+
* the current buffer as `attrs.breadcrumbs`, giving the broker + dashboard
|
|
7
|
+
* the 50 lines that ran in the seconds before an error fired.
|
|
8
|
+
*
|
|
9
|
+
* That context is what the Fix Prompt template uses to give Cursor/Claude
|
|
10
|
+
* meaningful prelude — "this error happened, and HERE'S WHAT THE APP WAS
|
|
11
|
+
* DOING right before it." Without it, the AI just sees a stack with no
|
|
12
|
+
* surrounding state.
|
|
13
|
+
*/
|
|
14
|
+
export interface Breadcrumb {
|
|
15
|
+
/** Wall-clock ms timestamp. */
|
|
16
|
+
ts: number;
|
|
17
|
+
/** One of the four console levels (log / info / warn / error). */
|
|
18
|
+
level: 'log' | 'info' | 'warn' | 'error';
|
|
19
|
+
/** Stringified console args, capped at 500 chars. */
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function pushBreadcrumb(level: Breadcrumb['level'], args: any[]): void;
|
|
23
|
+
export declare function getBreadcrumbs(): Breadcrumb[];
|
|
24
|
+
export declare function clearBreadcrumbs(): void;
|
package/dist/console.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import type { ResolvedConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Patches all four console levels (log / info / warn / error) to push entries
|
|
4
|
+
* into the breadcrumb ring buffer. Does NOT forward anything to the broker —
|
|
5
|
+
* forwarding is handled by patchConsoleError below.
|
|
6
|
+
*
|
|
7
|
+
* Always installed before the forwarding patches so the breadcrumb push runs
|
|
8
|
+
* AFTER the forwarding sendEvent (which snapshots the buffer before the
|
|
9
|
+
* current call lands in it — the forwarded event then sees N-1 lines and the
|
|
10
|
+
* current console.error is itself the Nth, attached to the NEXT event).
|
|
11
|
+
*/
|
|
12
|
+
export declare function patchConsoleForBreadcrumbs(): () => void;
|
|
2
13
|
export declare function patchConsoleError(config: ResolvedConfig): () => void;
|
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
30
30
|
|
|
31
31
|
// src/version.ts
|
|
32
32
|
var SDK_NAME = "@fixprompt/browser";
|
|
33
|
-
var SDK_VERSION = "0.0.
|
|
33
|
+
var SDK_VERSION = "0.0.2";
|
|
34
34
|
|
|
35
35
|
// src/session.ts
|
|
36
36
|
var STORAGE_KEY = "fixprompt_sid";
|
|
@@ -58,6 +58,45 @@ function getSessionId() {
|
|
|
58
58
|
return uuid();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/breadcrumbs.ts
|
|
62
|
+
var MAX_BREADCRUMBS = 50;
|
|
63
|
+
var MAX_MESSAGE_CHARS = 500;
|
|
64
|
+
var buffer = [];
|
|
65
|
+
function pushBreadcrumb(level, args) {
|
|
66
|
+
try {
|
|
67
|
+
const message = stringifyArgs(args);
|
|
68
|
+
buffer.push({ ts: Date.now(), level, message });
|
|
69
|
+
if (buffer.length > MAX_BREADCRUMBS) buffer.shift();
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getBreadcrumbs() {
|
|
74
|
+
return [...buffer];
|
|
75
|
+
}
|
|
76
|
+
function clearBreadcrumbs() {
|
|
77
|
+
buffer.length = 0;
|
|
78
|
+
}
|
|
79
|
+
function stringifyArgs(args) {
|
|
80
|
+
const parts = [];
|
|
81
|
+
for (const a of args) {
|
|
82
|
+
if (a instanceof Error) {
|
|
83
|
+
parts.push(`${a.name}: ${a.message}`);
|
|
84
|
+
} else if (typeof a === "string") {
|
|
85
|
+
parts.push(a);
|
|
86
|
+
} else if (a == null) {
|
|
87
|
+
parts.push(String(a));
|
|
88
|
+
} else {
|
|
89
|
+
try {
|
|
90
|
+
parts.push(JSON.stringify(a));
|
|
91
|
+
} catch {
|
|
92
|
+
parts.push(String(a));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const joined = parts.join(" ");
|
|
97
|
+
return joined.length > MAX_MESSAGE_CHARS ? joined.slice(0, MAX_MESSAGE_CHARS) + "\u2026" : joined;
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
// src/transport.ts
|
|
62
101
|
function sendEvent(config, payload) {
|
|
63
102
|
try {
|
|
@@ -75,7 +114,8 @@ function sendEvent(config, payload) {
|
|
|
75
114
|
session_id: getSessionId(),
|
|
76
115
|
release: config.release,
|
|
77
116
|
page_url: typeof location !== "undefined" ? location.href : void 0,
|
|
78
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
|
|
117
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
|
|
118
|
+
breadcrumbs: getBreadcrumbs()
|
|
79
119
|
},
|
|
80
120
|
synthetic: payload.synthetic
|
|
81
121
|
};
|
|
@@ -159,6 +199,36 @@ function safeJson(v) {
|
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
// src/console.ts
|
|
202
|
+
function patchConsoleForBreadcrumbs() {
|
|
203
|
+
const originals = {
|
|
204
|
+
log: console.log,
|
|
205
|
+
info: console.info,
|
|
206
|
+
warn: console.warn,
|
|
207
|
+
error: console.error
|
|
208
|
+
};
|
|
209
|
+
console.log = function patchedLog(...args) {
|
|
210
|
+
pushBreadcrumb("log", args);
|
|
211
|
+
return originals.log.apply(console, args);
|
|
212
|
+
};
|
|
213
|
+
console.info = function patchedInfo(...args) {
|
|
214
|
+
pushBreadcrumb("info", args);
|
|
215
|
+
return originals.info.apply(console, args);
|
|
216
|
+
};
|
|
217
|
+
console.warn = function patchedWarn(...args) {
|
|
218
|
+
pushBreadcrumb("warn", args);
|
|
219
|
+
return originals.warn.apply(console, args);
|
|
220
|
+
};
|
|
221
|
+
console.error = function patchedError(...args) {
|
|
222
|
+
pushBreadcrumb("error", args);
|
|
223
|
+
return originals.error.apply(console, args);
|
|
224
|
+
};
|
|
225
|
+
return () => {
|
|
226
|
+
console.log = originals.log;
|
|
227
|
+
console.info = originals.info;
|
|
228
|
+
console.warn = originals.warn;
|
|
229
|
+
console.error = originals.error;
|
|
230
|
+
};
|
|
231
|
+
}
|
|
162
232
|
function patchConsoleError(config) {
|
|
163
233
|
const original = console.error;
|
|
164
234
|
console.error = function patchedError(...args) {
|
|
@@ -365,6 +435,7 @@ function initFixPrompt(opts) {
|
|
|
365
435
|
const config = resolveConfig(opts);
|
|
366
436
|
state.config = config;
|
|
367
437
|
state.sessionId = getSessionId();
|
|
438
|
+
state.cleanup.restoreBreadcrumbConsole = patchConsoleForBreadcrumbs();
|
|
368
439
|
const { removeErrorListener, removeRejectionListener } = installGlobalHandlers(config);
|
|
369
440
|
state.cleanup.removeErrorListener = removeErrorListener;
|
|
370
441
|
state.cleanup.removeRejectionListener = removeRejectionListener;
|
|
@@ -402,14 +473,16 @@ function captureException(err, options = {}) {
|
|
|
402
473
|
}
|
|
403
474
|
function _resetForTests() {
|
|
404
475
|
const state = getState();
|
|
476
|
+
state.cleanup.restoreConsoleError?.();
|
|
477
|
+
state.cleanup.restoreBreadcrumbConsole?.();
|
|
405
478
|
state.cleanup.removeErrorListener?.();
|
|
406
479
|
state.cleanup.removeRejectionListener?.();
|
|
407
|
-
state.cleanup.restoreConsoleError?.();
|
|
408
480
|
state.cleanup.restoreFetch?.();
|
|
409
481
|
state.initialized = false;
|
|
410
482
|
state.config = null;
|
|
411
483
|
state.sessionId = null;
|
|
412
484
|
state.cleanup = {};
|
|
485
|
+
clearBreadcrumbs();
|
|
413
486
|
}
|
|
414
487
|
// Annotate the CommonJS export names for ESM import in node:
|
|
415
488
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/session.ts","../src/transport.ts","../src/handlers.ts","../src/console.ts","../src/fetch.ts","../src/release.ts","../src/state.ts","../src/init.ts"],"sourcesContent":["export { initFixPrompt, captureException, _resetForTests } from './init';\nexport type {\n InitOptions,\n ResolvedConfig,\n EventPayload,\n Severity,\n} from './types';\nexport { SDK_NAME, SDK_VERSION } from './version';\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.1';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function installGlobalHandlers(config: ResolvedConfig): {\n removeErrorListener: () => void;\n removeRejectionListener: () => void;\n} {\n const onError = (ev: ErrorEvent) => {\n const err = ev.error;\n sendEvent(config, {\n level: 'error',\n message: ev.message ?? (err && err.message) ?? 'Unknown error',\n stack: err && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'window.error',\n severity: 'error',\n filename: ev.filename,\n line: ev.lineno,\n column: ev.colno,\n error_name: err && err.name,\n },\n });\n };\n\n const onRejection = (ev: PromiseRejectionEvent) => {\n const reason = ev.reason;\n const isErr = reason instanceof Error;\n sendEvent(config, {\n level: 'error',\n message: isErr\n ? reason.message\n : typeof reason === 'string'\n ? reason\n : safeJson(reason),\n stack: isErr && typeof reason.stack === 'string' ? reason.stack : undefined,\n attrs: {\n kind: 'unhandledrejection',\n severity: 'error',\n error_name: isErr ? reason.name : typeof reason,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return {\n removeErrorListener: () => window.removeEventListener('error', onError),\n removeRejectionListener: () =>\n window.removeEventListener('unhandledrejection', onRejection),\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function patchConsoleError(config: ResolvedConfig): () => void {\n const original = console.error;\n\n console.error = function patchedError(...args: any[]) {\n try {\n const stackFrom = args.find((a) => a instanceof Error) as\n | Error\n | undefined;\n sendEvent(config, {\n level: 'warn',\n message: args\n .map((a) =>\n a instanceof Error\n ? a.message\n : typeof a === 'string'\n ? a\n : safeJson(a),\n )\n .join(' '),\n stack: stackFrom && typeof stackFrom.stack === 'string' ? stackFrom.stack : undefined,\n attrs: {\n kind: 'console.error',\n severity: 'warning',\n },\n });\n } catch {\n // never block user code\n }\n return original.apply(console, args as any);\n };\n\n return () => {\n console.error = original;\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Wraps `window.fetch` so failed requests (4xx → warning, 5xx → critical)\n * forward to the broker. Network failures (thrown errors) also forward.\n *\n * URL query string is stripped before logging to reduce PII leak risk.\n */\nexport function wrapFetch(config: ResolvedConfig): () => void {\n if (typeof fetch !== 'function') return () => undefined;\n const original = fetch.bind(globalThis);\n const brokerHost = safeHost(config.endpoint);\n\n const patched: typeof fetch = async (...args) => {\n const t0 = nowMs();\n const [input, init] = args;\n const method =\n (init && (init as RequestInit).method) ||\n (input instanceof Request ? input.method : 'GET');\n const url = urlString(input);\n\n // Never trip on requests to our own broker — would infinite-loop on outage.\n if (url.startsWith(config.endpoint) || safeHost(url) === brokerHost) {\n return original(...args);\n }\n\n try {\n const response = await original(...args);\n const status = response.status;\n const latency = Math.round(nowMs() - t0);\n\n if (status >= 400) {\n sendEvent(config, {\n level: status >= 500 ? 'error' : 'warn',\n message: `fetch ${method} ${stripQuery(url)} → ${status}`,\n attrs: {\n kind: 'fetch',\n severity: status >= 500 ? 'critical' : 'warning',\n method,\n url: stripQuery(url),\n status,\n latency_ms: latency,\n },\n });\n }\n\n return response;\n } catch (err: any) {\n const latency = Math.round(nowMs() - t0);\n sendEvent(config, {\n level: 'error',\n message: `fetch ${method} ${stripQuery(url)} failed: ${err?.message ?? String(err)}`,\n stack: err?.stack,\n attrs: {\n kind: 'fetch',\n severity: 'critical',\n method,\n url: stripQuery(url),\n latency_ms: latency,\n error_name: err?.name,\n },\n });\n throw err;\n }\n };\n\n (globalThis as any).fetch = patched;\n\n return () => {\n (globalThis as any).fetch = original;\n };\n}\n\nfunction urlString(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n if (input instanceof Request) return input.url;\n return String(input);\n}\n\nfunction stripQuery(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : url.slice(0, q);\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","/**\n * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.\n *\n * Priority:\n * 1. opts.release (handled in init.ts)\n * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\n * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA\n * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA\n * 5. process.env.GITHUB_SHA\n * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__\n * 7. <meta name=\"fixprompt-release\" content=\"...\">\n * 8. null\n */\n\nfunction fromImportMeta(): string | null {\n try {\n // Vite/Next/etc. — import.meta.env may exist at build time\n const meta = (import.meta as any).env;\n if (!meta) return null;\n return (\n meta.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||\n meta.VERCEL_GIT_COMMIT_SHA ||\n meta.NEXT_PUBLIC_COMMIT_SHA ||\n null\n );\n } catch {\n return null;\n }\n}\n\nfunction fromProcessEnv(): string | null {\n try {\n if (typeof process === 'undefined' || !process || !process.env) return null;\n return process.env.GITHUB_SHA || process.env.RELEASE || null;\n } catch {\n return null;\n }\n}\n\nfunction fromGlobal(): string | null {\n try {\n const g = globalThis as any;\n return typeof g.__FIXPROMPT_RELEASE__ === 'string'\n ? g.__FIXPROMPT_RELEASE__\n : null;\n } catch {\n return null;\n }\n}\n\nfunction fromMetaTag(): string | null {\n try {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector('meta[name=\"fixprompt-release\"]');\n const content = el?.getAttribute('content');\n return content && content.length > 0 ? content : null;\n } catch {\n return null;\n }\n}\n\nexport function detectRelease(explicit?: string): string | null {\n if (typeof explicit === 'string' && explicit.length > 0) return explicit;\n return (\n fromImportMeta() ||\n fromProcessEnv() ||\n fromGlobal() ||\n fromMetaTag() ||\n null\n );\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n","import { installGlobalHandlers } from './handlers';\nimport { patchConsoleError } from './console';\nimport { wrapFetch as installFetchWrapper } from './fetch';\nimport { detectRelease } from './release';\nimport { getSessionId } from './session';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\nimport type { InitOptions, ResolvedConfig } from './types';\n\nconst DEFAULT_ENDPOINT = 'https://geosloghub-production.up.railway.app';\n\nfunction resolveConfig(opts: InitOptions): ResolvedConfig {\n if (!opts || typeof opts.projectKey !== 'string' || opts.projectKey.length === 0) {\n throw new Error('initFixPrompt: projectKey is required');\n }\n return {\n projectKey: opts.projectKey,\n endpoint: (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, ''),\n source: opts.source ?? 'browser',\n release: detectRelease(opts.release),\n service: opts.service ?? 'web',\n app: opts.app ?? 'browser',\n env: opts.env ?? 'prod',\n wrapFetch: opts.wrapFetch ?? false,\n patchConsoleError: opts.patchConsoleError ?? true,\n debug: opts.debug ?? false,\n };\n}\n\nexport function initFixPrompt(opts: InitOptions): void {\n const state = getState();\n if (state.initialized) {\n if (opts.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] already initialized — call ignored');\n }\n return;\n }\n\n if (typeof window === 'undefined') {\n // Not in a browser environment — degrade gracefully (e.g. SSR import).\n return;\n }\n\n const config = resolveConfig(opts);\n state.config = config;\n state.sessionId = getSessionId();\n\n const { removeErrorListener, removeRejectionListener } =\n installGlobalHandlers(config);\n state.cleanup.removeErrorListener = removeErrorListener;\n state.cleanup.removeRejectionListener = removeRejectionListener;\n\n if (config.patchConsoleError) {\n state.cleanup.restoreConsoleError = patchConsoleError(config);\n }\n if (config.wrapFetch) {\n state.cleanup.restoreFetch = installFetchWrapper(config);\n }\n\n state.initialized = true;\n\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.log('[fixprompt] initialized', {\n endpoint: config.endpoint,\n release: config.release,\n session_id: state.sessionId,\n });\n }\n}\n\n/**\n * Manually report an error or message. Useful for synthetic events\n * (\"Send Test Exception\") or domain-specific catch blocks.\n */\nexport function captureException(\n err: unknown,\n options: { synthetic?: boolean; attrs?: Record<string, any> } = {},\n): void {\n const state = getState();\n if (!state.initialized || !state.config) return;\n\n const isErr = err instanceof Error;\n sendEvent(state.config, {\n level: 'error',\n message: isErr ? err.message : String(err),\n stack: isErr && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'captureException',\n severity: 'error',\n error_name: isErr ? err.name : typeof err,\n ...(options.attrs ?? {}),\n },\n synthetic: options.synthetic === true,\n });\n}\n\n/**\n * Internal use only. Reverses every patch installed by initFixPrompt —\n * intended for tests and React HMR. Not part of the supported API.\n * @internal\n */\nexport function _resetForTests(): void {\n const state = getState();\n state.cleanup.removeErrorListener?.();\n state.cleanup.removeRejectionListener?.();\n state.cleanup.restoreConsoleError?.();\n state.cleanup.restoreFetch?.();\n state.initialized = false;\n state.config = null;\n state.sessionId = null;\n state.cleanup = {};\n}\n"],"mappings":"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","names":["safeJson"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/session.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/handlers.ts","../src/console.ts","../src/fetch.ts","../src/release.ts","../src/state.ts","../src/init.ts"],"sourcesContent":["export { initFixPrompt, captureException, _resetForTests } from './init';\nexport type {\n InitOptions,\n ResolvedConfig,\n EventPayload,\n Severity,\n} from './types';\nexport { SDK_NAME, SDK_VERSION } from './version';\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.2';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","/**\n * In-memory ring buffer of the most recent console log lines.\n *\n * These are NEVER streamed to Loki on their own (would balloon volume + cost\n * without much value). Instead, every outgoing event from the SDK attaches\n * the current buffer as `attrs.breadcrumbs`, giving the broker + dashboard\n * the 50 lines that ran in the seconds before an error fired.\n *\n * That context is what the Fix Prompt template uses to give Cursor/Claude\n * meaningful prelude — \"this error happened, and HERE'S WHAT THE APP WAS\n * DOING right before it.\" Without it, the AI just sees a stack with no\n * surrounding state.\n */\n\nconst MAX_BREADCRUMBS = 50;\nconst MAX_MESSAGE_CHARS = 500;\n\nexport interface Breadcrumb {\n /** Wall-clock ms timestamp. */\n ts: number;\n /** One of the four console levels (log / info / warn / error). */\n level: 'log' | 'info' | 'warn' | 'error';\n /** Stringified console args, capped at 500 chars. */\n message: string;\n}\n\nconst buffer: Breadcrumb[] = [];\n\nexport function pushBreadcrumb(level: Breadcrumb['level'], args: any[]): void {\n try {\n const message = stringifyArgs(args);\n buffer.push({ ts: Date.now(), level, message });\n if (buffer.length > MAX_BREADCRUMBS) buffer.shift();\n } catch {\n // never let breadcrumb collection crash user code\n }\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function clearBreadcrumbs(): void {\n buffer.length = 0;\n}\n\nfunction stringifyArgs(args: any[]): string {\n const parts: string[] = [];\n for (const a of args) {\n if (a instanceof Error) {\n parts.push(`${a.name}: ${a.message}`);\n } else if (typeof a === 'string') {\n parts.push(a);\n } else if (a == null) {\n parts.push(String(a));\n } else {\n try {\n parts.push(JSON.stringify(a));\n } catch {\n parts.push(String(a));\n }\n }\n }\n const joined = parts.join(' ');\n return joined.length > MAX_MESSAGE_CHARS\n ? joined.slice(0, MAX_MESSAGE_CHARS) + '…'\n : joined;\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport { getBreadcrumbs } from './breadcrumbs';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n breadcrumbs: getBreadcrumbs(),\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function installGlobalHandlers(config: ResolvedConfig): {\n removeErrorListener: () => void;\n removeRejectionListener: () => void;\n} {\n const onError = (ev: ErrorEvent) => {\n const err = ev.error;\n sendEvent(config, {\n level: 'error',\n message: ev.message ?? (err && err.message) ?? 'Unknown error',\n stack: err && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'window.error',\n severity: 'error',\n filename: ev.filename,\n line: ev.lineno,\n column: ev.colno,\n error_name: err && err.name,\n },\n });\n };\n\n const onRejection = (ev: PromiseRejectionEvent) => {\n const reason = ev.reason;\n const isErr = reason instanceof Error;\n sendEvent(config, {\n level: 'error',\n message: isErr\n ? reason.message\n : typeof reason === 'string'\n ? reason\n : safeJson(reason),\n stack: isErr && typeof reason.stack === 'string' ? reason.stack : undefined,\n attrs: {\n kind: 'unhandledrejection',\n severity: 'error',\n error_name: isErr ? reason.name : typeof reason,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return {\n removeErrorListener: () => window.removeEventListener('error', onError),\n removeRejectionListener: () =>\n window.removeEventListener('unhandledrejection', onRejection),\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\nimport { pushBreadcrumb } from './breadcrumbs';\n\n/**\n * Patches all four console levels (log / info / warn / error) to push entries\n * into the breadcrumb ring buffer. Does NOT forward anything to the broker —\n * forwarding is handled by patchConsoleError below.\n *\n * Always installed before the forwarding patches so the breadcrumb push runs\n * AFTER the forwarding sendEvent (which snapshots the buffer before the\n * current call lands in it — the forwarded event then sees N-1 lines and the\n * current console.error is itself the Nth, attached to the NEXT event).\n */\nexport function patchConsoleForBreadcrumbs(): () => void {\n const originals = {\n log: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n\n console.log = function patchedLog(...args: any[]) {\n pushBreadcrumb('log', args);\n return originals.log.apply(console, args as any);\n };\n console.info = function patchedInfo(...args: any[]) {\n pushBreadcrumb('info', args);\n return originals.info.apply(console, args as any);\n };\n console.warn = function patchedWarn(...args: any[]) {\n pushBreadcrumb('warn', args);\n return originals.warn.apply(console, args as any);\n };\n console.error = function patchedError(...args: any[]) {\n pushBreadcrumb('error', args);\n return originals.error.apply(console, args as any);\n };\n\n return () => {\n console.log = originals.log;\n console.info = originals.info;\n console.warn = originals.warn;\n console.error = originals.error;\n };\n}\n\nexport function patchConsoleError(config: ResolvedConfig): () => void {\n const original = console.error;\n\n console.error = function patchedError(...args: any[]) {\n try {\n const stackFrom = args.find((a) => a instanceof Error) as\n | Error\n | undefined;\n sendEvent(config, {\n level: 'warn',\n message: args\n .map((a) =>\n a instanceof Error\n ? a.message\n : typeof a === 'string'\n ? a\n : safeJson(a),\n )\n .join(' '),\n stack: stackFrom && typeof stackFrom.stack === 'string' ? stackFrom.stack : undefined,\n attrs: {\n kind: 'console.error',\n severity: 'warning',\n },\n });\n } catch {\n // never block user code\n }\n return original.apply(console, args as any);\n };\n\n return () => {\n console.error = original;\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Wraps `window.fetch` so failed requests (4xx → warning, 5xx → critical)\n * forward to the broker. Network failures (thrown errors) also forward.\n *\n * URL query string is stripped before logging to reduce PII leak risk.\n */\nexport function wrapFetch(config: ResolvedConfig): () => void {\n if (typeof fetch !== 'function') return () => undefined;\n const original = fetch.bind(globalThis);\n const brokerHost = safeHost(config.endpoint);\n\n const patched: typeof fetch = async (...args) => {\n const t0 = nowMs();\n const [input, init] = args;\n const method =\n (init && (init as RequestInit).method) ||\n (input instanceof Request ? input.method : 'GET');\n const url = urlString(input);\n\n // Never trip on requests to our own broker — would infinite-loop on outage.\n if (url.startsWith(config.endpoint) || safeHost(url) === brokerHost) {\n return original(...args);\n }\n\n try {\n const response = await original(...args);\n const status = response.status;\n const latency = Math.round(nowMs() - t0);\n\n if (status >= 400) {\n sendEvent(config, {\n level: status >= 500 ? 'error' : 'warn',\n message: `fetch ${method} ${stripQuery(url)} → ${status}`,\n attrs: {\n kind: 'fetch',\n severity: status >= 500 ? 'critical' : 'warning',\n method,\n url: stripQuery(url),\n status,\n latency_ms: latency,\n },\n });\n }\n\n return response;\n } catch (err: any) {\n const latency = Math.round(nowMs() - t0);\n sendEvent(config, {\n level: 'error',\n message: `fetch ${method} ${stripQuery(url)} failed: ${err?.message ?? String(err)}`,\n stack: err?.stack,\n attrs: {\n kind: 'fetch',\n severity: 'critical',\n method,\n url: stripQuery(url),\n latency_ms: latency,\n error_name: err?.name,\n },\n });\n throw err;\n }\n };\n\n (globalThis as any).fetch = patched;\n\n return () => {\n (globalThis as any).fetch = original;\n };\n}\n\nfunction urlString(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n if (input instanceof Request) return input.url;\n return String(input);\n}\n\nfunction stripQuery(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : url.slice(0, q);\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","/**\n * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.\n *\n * Priority:\n * 1. opts.release (handled in init.ts)\n * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\n * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA\n * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA\n * 5. process.env.GITHUB_SHA\n * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__\n * 7. <meta name=\"fixprompt-release\" content=\"...\">\n * 8. null\n */\n\nfunction fromImportMeta(): string | null {\n try {\n // Vite/Next/etc. — import.meta.env may exist at build time\n const meta = (import.meta as any).env;\n if (!meta) return null;\n return (\n meta.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||\n meta.VERCEL_GIT_COMMIT_SHA ||\n meta.NEXT_PUBLIC_COMMIT_SHA ||\n null\n );\n } catch {\n return null;\n }\n}\n\nfunction fromProcessEnv(): string | null {\n try {\n if (typeof process === 'undefined' || !process || !process.env) return null;\n return process.env.GITHUB_SHA || process.env.RELEASE || null;\n } catch {\n return null;\n }\n}\n\nfunction fromGlobal(): string | null {\n try {\n const g = globalThis as any;\n return typeof g.__FIXPROMPT_RELEASE__ === 'string'\n ? g.__FIXPROMPT_RELEASE__\n : null;\n } catch {\n return null;\n }\n}\n\nfunction fromMetaTag(): string | null {\n try {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector('meta[name=\"fixprompt-release\"]');\n const content = el?.getAttribute('content');\n return content && content.length > 0 ? content : null;\n } catch {\n return null;\n }\n}\n\nexport function detectRelease(explicit?: string): string | null {\n if (typeof explicit === 'string' && explicit.length > 0) return explicit;\n return (\n fromImportMeta() ||\n fromProcessEnv() ||\n fromGlobal() ||\n fromMetaTag() ||\n null\n );\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreBreadcrumbConsole?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n","import { installGlobalHandlers } from './handlers';\nimport { patchConsoleError, patchConsoleForBreadcrumbs } from './console';\nimport { wrapFetch as installFetchWrapper } from './fetch';\nimport { detectRelease } from './release';\nimport { getSessionId } from './session';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\nimport { clearBreadcrumbs } from './breadcrumbs';\nimport type { InitOptions, ResolvedConfig } from './types';\n\nconst DEFAULT_ENDPOINT = 'https://geosloghub-production.up.railway.app';\n\nfunction resolveConfig(opts: InitOptions): ResolvedConfig {\n if (!opts || typeof opts.projectKey !== 'string' || opts.projectKey.length === 0) {\n throw new Error('initFixPrompt: projectKey is required');\n }\n return {\n projectKey: opts.projectKey,\n endpoint: (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, ''),\n source: opts.source ?? 'browser',\n release: detectRelease(opts.release),\n service: opts.service ?? 'web',\n app: opts.app ?? 'browser',\n env: opts.env ?? 'prod',\n wrapFetch: opts.wrapFetch ?? false,\n patchConsoleError: opts.patchConsoleError ?? true,\n debug: opts.debug ?? false,\n };\n}\n\nexport function initFixPrompt(opts: InitOptions): void {\n const state = getState();\n if (state.initialized) {\n if (opts.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] already initialized — call ignored');\n }\n return;\n }\n\n if (typeof window === 'undefined') {\n // Not in a browser environment — degrade gracefully (e.g. SSR import).\n return;\n }\n\n const config = resolveConfig(opts);\n state.config = config;\n state.sessionId = getSessionId();\n\n // Install breadcrumb capture FIRST so any console call during init lands\n // in the ring buffer. Forwarding patches wrap these later — order is\n // important: forwarders snapshot the buffer BEFORE pushing the current\n // call, so the forwarded event sees the lines that ran before it.\n state.cleanup.restoreBreadcrumbConsole = patchConsoleForBreadcrumbs();\n\n const { removeErrorListener, removeRejectionListener } =\n installGlobalHandlers(config);\n state.cleanup.removeErrorListener = removeErrorListener;\n state.cleanup.removeRejectionListener = removeRejectionListener;\n\n if (config.patchConsoleError) {\n state.cleanup.restoreConsoleError = patchConsoleError(config);\n }\n if (config.wrapFetch) {\n state.cleanup.restoreFetch = installFetchWrapper(config);\n }\n\n state.initialized = true;\n\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.log('[fixprompt] initialized', {\n endpoint: config.endpoint,\n release: config.release,\n session_id: state.sessionId,\n });\n }\n}\n\n/**\n * Manually report an error or message. Useful for synthetic events\n * (\"Send Test Exception\") or domain-specific catch blocks.\n */\nexport function captureException(\n err: unknown,\n options: { synthetic?: boolean; attrs?: Record<string, any> } = {},\n): void {\n const state = getState();\n if (!state.initialized || !state.config) return;\n\n const isErr = err instanceof Error;\n sendEvent(state.config, {\n level: 'error',\n message: isErr ? err.message : String(err),\n stack: isErr && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'captureException',\n severity: 'error',\n error_name: isErr ? err.name : typeof err,\n ...(options.attrs ?? {}),\n },\n synthetic: options.synthetic === true,\n });\n}\n\n/**\n * Internal use only. Reverses every patch installed by initFixPrompt —\n * intended for tests and React HMR. Not part of the supported API.\n * @internal\n */\nexport function _resetForTests(): void {\n const state = getState();\n state.cleanup.restoreConsoleError?.();\n state.cleanup.restoreBreadcrumbConsole?.();\n state.cleanup.removeErrorListener?.();\n state.cleanup.removeRejectionListener?.();\n state.cleanup.restoreFetch?.();\n state.initialized = false;\n state.config = null;\n state.sessionId = null;\n state.cleanup = {};\n clearBreadcrumbs();\n}\n"],"mappings":"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","names":["safeJson"]}
|
package/dist/index.global.js
CHANGED
|
@@ -30,7 +30,7 @@ var FixPrompt = (() => {
|
|
|
30
30
|
|
|
31
31
|
// src/version.ts
|
|
32
32
|
var SDK_NAME = "@fixprompt/browser";
|
|
33
|
-
var SDK_VERSION = "0.0.
|
|
33
|
+
var SDK_VERSION = "0.0.2";
|
|
34
34
|
|
|
35
35
|
// src/session.ts
|
|
36
36
|
var STORAGE_KEY = "fixprompt_sid";
|
|
@@ -58,6 +58,45 @@ var FixPrompt = (() => {
|
|
|
58
58
|
return uuid();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/breadcrumbs.ts
|
|
62
|
+
var MAX_BREADCRUMBS = 50;
|
|
63
|
+
var MAX_MESSAGE_CHARS = 500;
|
|
64
|
+
var buffer = [];
|
|
65
|
+
function pushBreadcrumb(level, args) {
|
|
66
|
+
try {
|
|
67
|
+
const message = stringifyArgs(args);
|
|
68
|
+
buffer.push({ ts: Date.now(), level, message });
|
|
69
|
+
if (buffer.length > MAX_BREADCRUMBS) buffer.shift();
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function getBreadcrumbs() {
|
|
74
|
+
return [...buffer];
|
|
75
|
+
}
|
|
76
|
+
function clearBreadcrumbs() {
|
|
77
|
+
buffer.length = 0;
|
|
78
|
+
}
|
|
79
|
+
function stringifyArgs(args) {
|
|
80
|
+
const parts = [];
|
|
81
|
+
for (const a of args) {
|
|
82
|
+
if (a instanceof Error) {
|
|
83
|
+
parts.push(`${a.name}: ${a.message}`);
|
|
84
|
+
} else if (typeof a === "string") {
|
|
85
|
+
parts.push(a);
|
|
86
|
+
} else if (a == null) {
|
|
87
|
+
parts.push(String(a));
|
|
88
|
+
} else {
|
|
89
|
+
try {
|
|
90
|
+
parts.push(JSON.stringify(a));
|
|
91
|
+
} catch {
|
|
92
|
+
parts.push(String(a));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const joined = parts.join(" ");
|
|
97
|
+
return joined.length > MAX_MESSAGE_CHARS ? joined.slice(0, MAX_MESSAGE_CHARS) + "\u2026" : joined;
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
// src/transport.ts
|
|
62
101
|
function sendEvent(config, payload) {
|
|
63
102
|
try {
|
|
@@ -75,7 +114,8 @@ var FixPrompt = (() => {
|
|
|
75
114
|
session_id: getSessionId(),
|
|
76
115
|
release: config.release,
|
|
77
116
|
page_url: typeof location !== "undefined" ? location.href : void 0,
|
|
78
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
|
|
117
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
|
|
118
|
+
breadcrumbs: getBreadcrumbs()
|
|
79
119
|
},
|
|
80
120
|
synthetic: payload.synthetic
|
|
81
121
|
};
|
|
@@ -159,6 +199,36 @@ var FixPrompt = (() => {
|
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
// src/console.ts
|
|
202
|
+
function patchConsoleForBreadcrumbs() {
|
|
203
|
+
const originals = {
|
|
204
|
+
log: console.log,
|
|
205
|
+
info: console.info,
|
|
206
|
+
warn: console.warn,
|
|
207
|
+
error: console.error
|
|
208
|
+
};
|
|
209
|
+
console.log = function patchedLog(...args) {
|
|
210
|
+
pushBreadcrumb("log", args);
|
|
211
|
+
return originals.log.apply(console, args);
|
|
212
|
+
};
|
|
213
|
+
console.info = function patchedInfo(...args) {
|
|
214
|
+
pushBreadcrumb("info", args);
|
|
215
|
+
return originals.info.apply(console, args);
|
|
216
|
+
};
|
|
217
|
+
console.warn = function patchedWarn(...args) {
|
|
218
|
+
pushBreadcrumb("warn", args);
|
|
219
|
+
return originals.warn.apply(console, args);
|
|
220
|
+
};
|
|
221
|
+
console.error = function patchedError(...args) {
|
|
222
|
+
pushBreadcrumb("error", args);
|
|
223
|
+
return originals.error.apply(console, args);
|
|
224
|
+
};
|
|
225
|
+
return () => {
|
|
226
|
+
console.log = originals.log;
|
|
227
|
+
console.info = originals.info;
|
|
228
|
+
console.warn = originals.warn;
|
|
229
|
+
console.error = originals.error;
|
|
230
|
+
};
|
|
231
|
+
}
|
|
162
232
|
function patchConsoleError(config) {
|
|
163
233
|
const original = console.error;
|
|
164
234
|
console.error = function patchedError(...args) {
|
|
@@ -365,6 +435,7 @@ var FixPrompt = (() => {
|
|
|
365
435
|
const config = resolveConfig(opts);
|
|
366
436
|
state.config = config;
|
|
367
437
|
state.sessionId = getSessionId();
|
|
438
|
+
state.cleanup.restoreBreadcrumbConsole = patchConsoleForBreadcrumbs();
|
|
368
439
|
const { removeErrorListener, removeRejectionListener } = installGlobalHandlers(config);
|
|
369
440
|
state.cleanup.removeErrorListener = removeErrorListener;
|
|
370
441
|
state.cleanup.removeRejectionListener = removeRejectionListener;
|
|
@@ -402,14 +473,16 @@ var FixPrompt = (() => {
|
|
|
402
473
|
}
|
|
403
474
|
function _resetForTests() {
|
|
404
475
|
const state = getState();
|
|
476
|
+
state.cleanup.restoreConsoleError?.();
|
|
477
|
+
state.cleanup.restoreBreadcrumbConsole?.();
|
|
405
478
|
state.cleanup.removeErrorListener?.();
|
|
406
479
|
state.cleanup.removeRejectionListener?.();
|
|
407
|
-
state.cleanup.restoreConsoleError?.();
|
|
408
480
|
state.cleanup.restoreFetch?.();
|
|
409
481
|
state.initialized = false;
|
|
410
482
|
state.config = null;
|
|
411
483
|
state.sessionId = null;
|
|
412
484
|
state.cleanup = {};
|
|
485
|
+
clearBreadcrumbs();
|
|
413
486
|
}
|
|
414
487
|
return __toCommonJS(src_exports);
|
|
415
488
|
})();
|
package/dist/index.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/session.ts","../src/transport.ts","../src/handlers.ts","../src/console.ts","../src/fetch.ts","../src/release.ts","../src/state.ts","../src/init.ts"],"sourcesContent":["export { initFixPrompt, captureException, _resetForTests } from './init';\nexport type {\n InitOptions,\n ResolvedConfig,\n EventPayload,\n Severity,\n} from './types';\nexport { SDK_NAME, SDK_VERSION } from './version';\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.1';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function installGlobalHandlers(config: ResolvedConfig): {\n removeErrorListener: () => void;\n removeRejectionListener: () => void;\n} {\n const onError = (ev: ErrorEvent) => {\n const err = ev.error;\n sendEvent(config, {\n level: 'error',\n message: ev.message ?? (err && err.message) ?? 'Unknown error',\n stack: err && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'window.error',\n severity: 'error',\n filename: ev.filename,\n line: ev.lineno,\n column: ev.colno,\n error_name: err && err.name,\n },\n });\n };\n\n const onRejection = (ev: PromiseRejectionEvent) => {\n const reason = ev.reason;\n const isErr = reason instanceof Error;\n sendEvent(config, {\n level: 'error',\n message: isErr\n ? reason.message\n : typeof reason === 'string'\n ? reason\n : safeJson(reason),\n stack: isErr && typeof reason.stack === 'string' ? reason.stack : undefined,\n attrs: {\n kind: 'unhandledrejection',\n severity: 'error',\n error_name: isErr ? reason.name : typeof reason,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return {\n removeErrorListener: () => window.removeEventListener('error', onError),\n removeRejectionListener: () =>\n window.removeEventListener('unhandledrejection', onRejection),\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function patchConsoleError(config: ResolvedConfig): () => void {\n const original = console.error;\n\n console.error = function patchedError(...args: any[]) {\n try {\n const stackFrom = args.find((a) => a instanceof Error) as\n | Error\n | undefined;\n sendEvent(config, {\n level: 'warn',\n message: args\n .map((a) =>\n a instanceof Error\n ? a.message\n : typeof a === 'string'\n ? a\n : safeJson(a),\n )\n .join(' '),\n stack: stackFrom && typeof stackFrom.stack === 'string' ? stackFrom.stack : undefined,\n attrs: {\n kind: 'console.error',\n severity: 'warning',\n },\n });\n } catch {\n // never block user code\n }\n return original.apply(console, args as any);\n };\n\n return () => {\n console.error = original;\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Wraps `window.fetch` so failed requests (4xx → warning, 5xx → critical)\n * forward to the broker. Network failures (thrown errors) also forward.\n *\n * URL query string is stripped before logging to reduce PII leak risk.\n */\nexport function wrapFetch(config: ResolvedConfig): () => void {\n if (typeof fetch !== 'function') return () => undefined;\n const original = fetch.bind(globalThis);\n const brokerHost = safeHost(config.endpoint);\n\n const patched: typeof fetch = async (...args) => {\n const t0 = nowMs();\n const [input, init] = args;\n const method =\n (init && (init as RequestInit).method) ||\n (input instanceof Request ? input.method : 'GET');\n const url = urlString(input);\n\n // Never trip on requests to our own broker — would infinite-loop on outage.\n if (url.startsWith(config.endpoint) || safeHost(url) === brokerHost) {\n return original(...args);\n }\n\n try {\n const response = await original(...args);\n const status = response.status;\n const latency = Math.round(nowMs() - t0);\n\n if (status >= 400) {\n sendEvent(config, {\n level: status >= 500 ? 'error' : 'warn',\n message: `fetch ${method} ${stripQuery(url)} → ${status}`,\n attrs: {\n kind: 'fetch',\n severity: status >= 500 ? 'critical' : 'warning',\n method,\n url: stripQuery(url),\n status,\n latency_ms: latency,\n },\n });\n }\n\n return response;\n } catch (err: any) {\n const latency = Math.round(nowMs() - t0);\n sendEvent(config, {\n level: 'error',\n message: `fetch ${method} ${stripQuery(url)} failed: ${err?.message ?? String(err)}`,\n stack: err?.stack,\n attrs: {\n kind: 'fetch',\n severity: 'critical',\n method,\n url: stripQuery(url),\n latency_ms: latency,\n error_name: err?.name,\n },\n });\n throw err;\n }\n };\n\n (globalThis as any).fetch = patched;\n\n return () => {\n (globalThis as any).fetch = original;\n };\n}\n\nfunction urlString(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n if (input instanceof Request) return input.url;\n return String(input);\n}\n\nfunction stripQuery(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : url.slice(0, q);\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","/**\n * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.\n *\n * Priority:\n * 1. opts.release (handled in init.ts)\n * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\n * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA\n * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA\n * 5. process.env.GITHUB_SHA\n * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__\n * 7. <meta name=\"fixprompt-release\" content=\"...\">\n * 8. null\n */\n\nfunction fromImportMeta(): string | null {\n try {\n // Vite/Next/etc. — import.meta.env may exist at build time\n const meta = (import.meta as any).env;\n if (!meta) return null;\n return (\n meta.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||\n meta.VERCEL_GIT_COMMIT_SHA ||\n meta.NEXT_PUBLIC_COMMIT_SHA ||\n null\n );\n } catch {\n return null;\n }\n}\n\nfunction fromProcessEnv(): string | null {\n try {\n if (typeof process === 'undefined' || !process || !process.env) return null;\n return process.env.GITHUB_SHA || process.env.RELEASE || null;\n } catch {\n return null;\n }\n}\n\nfunction fromGlobal(): string | null {\n try {\n const g = globalThis as any;\n return typeof g.__FIXPROMPT_RELEASE__ === 'string'\n ? g.__FIXPROMPT_RELEASE__\n : null;\n } catch {\n return null;\n }\n}\n\nfunction fromMetaTag(): string | null {\n try {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector('meta[name=\"fixprompt-release\"]');\n const content = el?.getAttribute('content');\n return content && content.length > 0 ? content : null;\n } catch {\n return null;\n }\n}\n\nexport function detectRelease(explicit?: string): string | null {\n if (typeof explicit === 'string' && explicit.length > 0) return explicit;\n return (\n fromImportMeta() ||\n fromProcessEnv() ||\n fromGlobal() ||\n fromMetaTag() ||\n null\n );\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n","import { installGlobalHandlers } from './handlers';\nimport { patchConsoleError } from './console';\nimport { wrapFetch as installFetchWrapper } from './fetch';\nimport { detectRelease } from './release';\nimport { getSessionId } from './session';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\nimport type { InitOptions, ResolvedConfig } from './types';\n\nconst DEFAULT_ENDPOINT = 'https://geosloghub-production.up.railway.app';\n\nfunction resolveConfig(opts: InitOptions): ResolvedConfig {\n if (!opts || typeof opts.projectKey !== 'string' || opts.projectKey.length === 0) {\n throw new Error('initFixPrompt: projectKey is required');\n }\n return {\n projectKey: opts.projectKey,\n endpoint: (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, ''),\n source: opts.source ?? 'browser',\n release: detectRelease(opts.release),\n service: opts.service ?? 'web',\n app: opts.app ?? 'browser',\n env: opts.env ?? 'prod',\n wrapFetch: opts.wrapFetch ?? false,\n patchConsoleError: opts.patchConsoleError ?? true,\n debug: opts.debug ?? false,\n };\n}\n\nexport function initFixPrompt(opts: InitOptions): void {\n const state = getState();\n if (state.initialized) {\n if (opts.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] already initialized — call ignored');\n }\n return;\n }\n\n if (typeof window === 'undefined') {\n // Not in a browser environment — degrade gracefully (e.g. SSR import).\n return;\n }\n\n const config = resolveConfig(opts);\n state.config = config;\n state.sessionId = getSessionId();\n\n const { removeErrorListener, removeRejectionListener } =\n installGlobalHandlers(config);\n state.cleanup.removeErrorListener = removeErrorListener;\n state.cleanup.removeRejectionListener = removeRejectionListener;\n\n if (config.patchConsoleError) {\n state.cleanup.restoreConsoleError = patchConsoleError(config);\n }\n if (config.wrapFetch) {\n state.cleanup.restoreFetch = installFetchWrapper(config);\n }\n\n state.initialized = true;\n\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.log('[fixprompt] initialized', {\n endpoint: config.endpoint,\n release: config.release,\n session_id: state.sessionId,\n });\n }\n}\n\n/**\n * Manually report an error or message. Useful for synthetic events\n * (\"Send Test Exception\") or domain-specific catch blocks.\n */\nexport function captureException(\n err: unknown,\n options: { synthetic?: boolean; attrs?: Record<string, any> } = {},\n): void {\n const state = getState();\n if (!state.initialized || !state.config) return;\n\n const isErr = err instanceof Error;\n sendEvent(state.config, {\n level: 'error',\n message: isErr ? err.message : String(err),\n stack: isErr && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'captureException',\n severity: 'error',\n error_name: isErr ? err.name : typeof err,\n ...(options.attrs ?? {}),\n },\n synthetic: options.synthetic === true,\n });\n}\n\n/**\n * Internal use only. Reverses every patch installed by initFixPrompt —\n * intended for tests and React HMR. Not part of the supported API.\n * @internal\n */\nexport function _resetForTests(): void {\n const state = getState();\n state.cleanup.removeErrorListener?.();\n state.cleanup.removeRejectionListener?.();\n state.cleanup.restoreConsoleError?.();\n state.cleanup.restoreFetch?.();\n state.initialized = false;\n state.config = null;\n state.sessionId = null;\n state.cleanup = {};\n}\n"],"mappings":"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","names":["safeJson"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/session.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/handlers.ts","../src/console.ts","../src/fetch.ts","../src/release.ts","../src/state.ts","../src/init.ts"],"sourcesContent":["export { initFixPrompt, captureException, _resetForTests } from './init';\nexport type {\n InitOptions,\n ResolvedConfig,\n EventPayload,\n Severity,\n} from './types';\nexport { SDK_NAME, SDK_VERSION } from './version';\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.2';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","/**\n * In-memory ring buffer of the most recent console log lines.\n *\n * These are NEVER streamed to Loki on their own (would balloon volume + cost\n * without much value). Instead, every outgoing event from the SDK attaches\n * the current buffer as `attrs.breadcrumbs`, giving the broker + dashboard\n * the 50 lines that ran in the seconds before an error fired.\n *\n * That context is what the Fix Prompt template uses to give Cursor/Claude\n * meaningful prelude — \"this error happened, and HERE'S WHAT THE APP WAS\n * DOING right before it.\" Without it, the AI just sees a stack with no\n * surrounding state.\n */\n\nconst MAX_BREADCRUMBS = 50;\nconst MAX_MESSAGE_CHARS = 500;\n\nexport interface Breadcrumb {\n /** Wall-clock ms timestamp. */\n ts: number;\n /** One of the four console levels (log / info / warn / error). */\n level: 'log' | 'info' | 'warn' | 'error';\n /** Stringified console args, capped at 500 chars. */\n message: string;\n}\n\nconst buffer: Breadcrumb[] = [];\n\nexport function pushBreadcrumb(level: Breadcrumb['level'], args: any[]): void {\n try {\n const message = stringifyArgs(args);\n buffer.push({ ts: Date.now(), level, message });\n if (buffer.length > MAX_BREADCRUMBS) buffer.shift();\n } catch {\n // never let breadcrumb collection crash user code\n }\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function clearBreadcrumbs(): void {\n buffer.length = 0;\n}\n\nfunction stringifyArgs(args: any[]): string {\n const parts: string[] = [];\n for (const a of args) {\n if (a instanceof Error) {\n parts.push(`${a.name}: ${a.message}`);\n } else if (typeof a === 'string') {\n parts.push(a);\n } else if (a == null) {\n parts.push(String(a));\n } else {\n try {\n parts.push(JSON.stringify(a));\n } catch {\n parts.push(String(a));\n }\n }\n }\n const joined = parts.join(' ');\n return joined.length > MAX_MESSAGE_CHARS\n ? joined.slice(0, MAX_MESSAGE_CHARS) + '…'\n : joined;\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport { getBreadcrumbs } from './breadcrumbs';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n breadcrumbs: getBreadcrumbs(),\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function installGlobalHandlers(config: ResolvedConfig): {\n removeErrorListener: () => void;\n removeRejectionListener: () => void;\n} {\n const onError = (ev: ErrorEvent) => {\n const err = ev.error;\n sendEvent(config, {\n level: 'error',\n message: ev.message ?? (err && err.message) ?? 'Unknown error',\n stack: err && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'window.error',\n severity: 'error',\n filename: ev.filename,\n line: ev.lineno,\n column: ev.colno,\n error_name: err && err.name,\n },\n });\n };\n\n const onRejection = (ev: PromiseRejectionEvent) => {\n const reason = ev.reason;\n const isErr = reason instanceof Error;\n sendEvent(config, {\n level: 'error',\n message: isErr\n ? reason.message\n : typeof reason === 'string'\n ? reason\n : safeJson(reason),\n stack: isErr && typeof reason.stack === 'string' ? reason.stack : undefined,\n attrs: {\n kind: 'unhandledrejection',\n severity: 'error',\n error_name: isErr ? reason.name : typeof reason,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return {\n removeErrorListener: () => window.removeEventListener('error', onError),\n removeRejectionListener: () =>\n window.removeEventListener('unhandledrejection', onRejection),\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\nimport { pushBreadcrumb } from './breadcrumbs';\n\n/**\n * Patches all four console levels (log / info / warn / error) to push entries\n * into the breadcrumb ring buffer. Does NOT forward anything to the broker —\n * forwarding is handled by patchConsoleError below.\n *\n * Always installed before the forwarding patches so the breadcrumb push runs\n * AFTER the forwarding sendEvent (which snapshots the buffer before the\n * current call lands in it — the forwarded event then sees N-1 lines and the\n * current console.error is itself the Nth, attached to the NEXT event).\n */\nexport function patchConsoleForBreadcrumbs(): () => void {\n const originals = {\n log: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n\n console.log = function patchedLog(...args: any[]) {\n pushBreadcrumb('log', args);\n return originals.log.apply(console, args as any);\n };\n console.info = function patchedInfo(...args: any[]) {\n pushBreadcrumb('info', args);\n return originals.info.apply(console, args as any);\n };\n console.warn = function patchedWarn(...args: any[]) {\n pushBreadcrumb('warn', args);\n return originals.warn.apply(console, args as any);\n };\n console.error = function patchedError(...args: any[]) {\n pushBreadcrumb('error', args);\n return originals.error.apply(console, args as any);\n };\n\n return () => {\n console.log = originals.log;\n console.info = originals.info;\n console.warn = originals.warn;\n console.error = originals.error;\n };\n}\n\nexport function patchConsoleError(config: ResolvedConfig): () => void {\n const original = console.error;\n\n console.error = function patchedError(...args: any[]) {\n try {\n const stackFrom = args.find((a) => a instanceof Error) as\n | Error\n | undefined;\n sendEvent(config, {\n level: 'warn',\n message: args\n .map((a) =>\n a instanceof Error\n ? a.message\n : typeof a === 'string'\n ? a\n : safeJson(a),\n )\n .join(' '),\n stack: stackFrom && typeof stackFrom.stack === 'string' ? stackFrom.stack : undefined,\n attrs: {\n kind: 'console.error',\n severity: 'warning',\n },\n });\n } catch {\n // never block user code\n }\n return original.apply(console, args as any);\n };\n\n return () => {\n console.error = original;\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Wraps `window.fetch` so failed requests (4xx → warning, 5xx → critical)\n * forward to the broker. Network failures (thrown errors) also forward.\n *\n * URL query string is stripped before logging to reduce PII leak risk.\n */\nexport function wrapFetch(config: ResolvedConfig): () => void {\n if (typeof fetch !== 'function') return () => undefined;\n const original = fetch.bind(globalThis);\n const brokerHost = safeHost(config.endpoint);\n\n const patched: typeof fetch = async (...args) => {\n const t0 = nowMs();\n const [input, init] = args;\n const method =\n (init && (init as RequestInit).method) ||\n (input instanceof Request ? input.method : 'GET');\n const url = urlString(input);\n\n // Never trip on requests to our own broker — would infinite-loop on outage.\n if (url.startsWith(config.endpoint) || safeHost(url) === brokerHost) {\n return original(...args);\n }\n\n try {\n const response = await original(...args);\n const status = response.status;\n const latency = Math.round(nowMs() - t0);\n\n if (status >= 400) {\n sendEvent(config, {\n level: status >= 500 ? 'error' : 'warn',\n message: `fetch ${method} ${stripQuery(url)} → ${status}`,\n attrs: {\n kind: 'fetch',\n severity: status >= 500 ? 'critical' : 'warning',\n method,\n url: stripQuery(url),\n status,\n latency_ms: latency,\n },\n });\n }\n\n return response;\n } catch (err: any) {\n const latency = Math.round(nowMs() - t0);\n sendEvent(config, {\n level: 'error',\n message: `fetch ${method} ${stripQuery(url)} failed: ${err?.message ?? String(err)}`,\n stack: err?.stack,\n attrs: {\n kind: 'fetch',\n severity: 'critical',\n method,\n url: stripQuery(url),\n latency_ms: latency,\n error_name: err?.name,\n },\n });\n throw err;\n }\n };\n\n (globalThis as any).fetch = patched;\n\n return () => {\n (globalThis as any).fetch = original;\n };\n}\n\nfunction urlString(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n if (input instanceof Request) return input.url;\n return String(input);\n}\n\nfunction stripQuery(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : url.slice(0, q);\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","/**\n * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.\n *\n * Priority:\n * 1. opts.release (handled in init.ts)\n * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\n * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA\n * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA\n * 5. process.env.GITHUB_SHA\n * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__\n * 7. <meta name=\"fixprompt-release\" content=\"...\">\n * 8. null\n */\n\nfunction fromImportMeta(): string | null {\n try {\n // Vite/Next/etc. — import.meta.env may exist at build time\n const meta = (import.meta as any).env;\n if (!meta) return null;\n return (\n meta.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||\n meta.VERCEL_GIT_COMMIT_SHA ||\n meta.NEXT_PUBLIC_COMMIT_SHA ||\n null\n );\n } catch {\n return null;\n }\n}\n\nfunction fromProcessEnv(): string | null {\n try {\n if (typeof process === 'undefined' || !process || !process.env) return null;\n return process.env.GITHUB_SHA || process.env.RELEASE || null;\n } catch {\n return null;\n }\n}\n\nfunction fromGlobal(): string | null {\n try {\n const g = globalThis as any;\n return typeof g.__FIXPROMPT_RELEASE__ === 'string'\n ? g.__FIXPROMPT_RELEASE__\n : null;\n } catch {\n return null;\n }\n}\n\nfunction fromMetaTag(): string | null {\n try {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector('meta[name=\"fixprompt-release\"]');\n const content = el?.getAttribute('content');\n return content && content.length > 0 ? content : null;\n } catch {\n return null;\n }\n}\n\nexport function detectRelease(explicit?: string): string | null {\n if (typeof explicit === 'string' && explicit.length > 0) return explicit;\n return (\n fromImportMeta() ||\n fromProcessEnv() ||\n fromGlobal() ||\n fromMetaTag() ||\n null\n );\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreBreadcrumbConsole?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n","import { installGlobalHandlers } from './handlers';\nimport { patchConsoleError, patchConsoleForBreadcrumbs } from './console';\nimport { wrapFetch as installFetchWrapper } from './fetch';\nimport { detectRelease } from './release';\nimport { getSessionId } from './session';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\nimport { clearBreadcrumbs } from './breadcrumbs';\nimport type { InitOptions, ResolvedConfig } from './types';\n\nconst DEFAULT_ENDPOINT = 'https://geosloghub-production.up.railway.app';\n\nfunction resolveConfig(opts: InitOptions): ResolvedConfig {\n if (!opts || typeof opts.projectKey !== 'string' || opts.projectKey.length === 0) {\n throw new Error('initFixPrompt: projectKey is required');\n }\n return {\n projectKey: opts.projectKey,\n endpoint: (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, ''),\n source: opts.source ?? 'browser',\n release: detectRelease(opts.release),\n service: opts.service ?? 'web',\n app: opts.app ?? 'browser',\n env: opts.env ?? 'prod',\n wrapFetch: opts.wrapFetch ?? false,\n patchConsoleError: opts.patchConsoleError ?? true,\n debug: opts.debug ?? false,\n };\n}\n\nexport function initFixPrompt(opts: InitOptions): void {\n const state = getState();\n if (state.initialized) {\n if (opts.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] already initialized — call ignored');\n }\n return;\n }\n\n if (typeof window === 'undefined') {\n // Not in a browser environment — degrade gracefully (e.g. SSR import).\n return;\n }\n\n const config = resolveConfig(opts);\n state.config = config;\n state.sessionId = getSessionId();\n\n // Install breadcrumb capture FIRST so any console call during init lands\n // in the ring buffer. Forwarding patches wrap these later — order is\n // important: forwarders snapshot the buffer BEFORE pushing the current\n // call, so the forwarded event sees the lines that ran before it.\n state.cleanup.restoreBreadcrumbConsole = patchConsoleForBreadcrumbs();\n\n const { removeErrorListener, removeRejectionListener } =\n installGlobalHandlers(config);\n state.cleanup.removeErrorListener = removeErrorListener;\n state.cleanup.removeRejectionListener = removeRejectionListener;\n\n if (config.patchConsoleError) {\n state.cleanup.restoreConsoleError = patchConsoleError(config);\n }\n if (config.wrapFetch) {\n state.cleanup.restoreFetch = installFetchWrapper(config);\n }\n\n state.initialized = true;\n\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.log('[fixprompt] initialized', {\n endpoint: config.endpoint,\n release: config.release,\n session_id: state.sessionId,\n });\n }\n}\n\n/**\n * Manually report an error or message. Useful for synthetic events\n * (\"Send Test Exception\") or domain-specific catch blocks.\n */\nexport function captureException(\n err: unknown,\n options: { synthetic?: boolean; attrs?: Record<string, any> } = {},\n): void {\n const state = getState();\n if (!state.initialized || !state.config) return;\n\n const isErr = err instanceof Error;\n sendEvent(state.config, {\n level: 'error',\n message: isErr ? err.message : String(err),\n stack: isErr && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'captureException',\n severity: 'error',\n error_name: isErr ? err.name : typeof err,\n ...(options.attrs ?? {}),\n },\n synthetic: options.synthetic === true,\n });\n}\n\n/**\n * Internal use only. Reverses every patch installed by initFixPrompt —\n * intended for tests and React HMR. Not part of the supported API.\n * @internal\n */\nexport function _resetForTests(): void {\n const state = getState();\n state.cleanup.restoreConsoleError?.();\n state.cleanup.restoreBreadcrumbConsole?.();\n state.cleanup.removeErrorListener?.();\n state.cleanup.removeRejectionListener?.();\n state.cleanup.restoreFetch?.();\n state.initialized = false;\n state.config = null;\n state.sessionId = null;\n state.cleanup = {};\n clearBreadcrumbs();\n}\n"],"mappings":"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","names":["safeJson"]}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
2
|
var SDK_NAME = "@fixprompt/browser";
|
|
3
|
-
var SDK_VERSION = "0.0.
|
|
3
|
+
var SDK_VERSION = "0.0.2";
|
|
4
4
|
|
|
5
5
|
// src/session.ts
|
|
6
6
|
var STORAGE_KEY = "fixprompt_sid";
|
|
@@ -28,6 +28,45 @@ function getSessionId() {
|
|
|
28
28
|
return uuid();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// src/breadcrumbs.ts
|
|
32
|
+
var MAX_BREADCRUMBS = 50;
|
|
33
|
+
var MAX_MESSAGE_CHARS = 500;
|
|
34
|
+
var buffer = [];
|
|
35
|
+
function pushBreadcrumb(level, args) {
|
|
36
|
+
try {
|
|
37
|
+
const message = stringifyArgs(args);
|
|
38
|
+
buffer.push({ ts: Date.now(), level, message });
|
|
39
|
+
if (buffer.length > MAX_BREADCRUMBS) buffer.shift();
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function getBreadcrumbs() {
|
|
44
|
+
return [...buffer];
|
|
45
|
+
}
|
|
46
|
+
function clearBreadcrumbs() {
|
|
47
|
+
buffer.length = 0;
|
|
48
|
+
}
|
|
49
|
+
function stringifyArgs(args) {
|
|
50
|
+
const parts = [];
|
|
51
|
+
for (const a of args) {
|
|
52
|
+
if (a instanceof Error) {
|
|
53
|
+
parts.push(`${a.name}: ${a.message}`);
|
|
54
|
+
} else if (typeof a === "string") {
|
|
55
|
+
parts.push(a);
|
|
56
|
+
} else if (a == null) {
|
|
57
|
+
parts.push(String(a));
|
|
58
|
+
} else {
|
|
59
|
+
try {
|
|
60
|
+
parts.push(JSON.stringify(a));
|
|
61
|
+
} catch {
|
|
62
|
+
parts.push(String(a));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const joined = parts.join(" ");
|
|
67
|
+
return joined.length > MAX_MESSAGE_CHARS ? joined.slice(0, MAX_MESSAGE_CHARS) + "\u2026" : joined;
|
|
68
|
+
}
|
|
69
|
+
|
|
31
70
|
// src/transport.ts
|
|
32
71
|
function sendEvent(config, payload) {
|
|
33
72
|
try {
|
|
@@ -45,7 +84,8 @@ function sendEvent(config, payload) {
|
|
|
45
84
|
session_id: getSessionId(),
|
|
46
85
|
release: config.release,
|
|
47
86
|
page_url: typeof location !== "undefined" ? location.href : void 0,
|
|
48
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
|
|
87
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
|
|
88
|
+
breadcrumbs: getBreadcrumbs()
|
|
49
89
|
},
|
|
50
90
|
synthetic: payload.synthetic
|
|
51
91
|
};
|
|
@@ -129,6 +169,36 @@ function safeJson(v) {
|
|
|
129
169
|
}
|
|
130
170
|
|
|
131
171
|
// src/console.ts
|
|
172
|
+
function patchConsoleForBreadcrumbs() {
|
|
173
|
+
const originals = {
|
|
174
|
+
log: console.log,
|
|
175
|
+
info: console.info,
|
|
176
|
+
warn: console.warn,
|
|
177
|
+
error: console.error
|
|
178
|
+
};
|
|
179
|
+
console.log = function patchedLog(...args) {
|
|
180
|
+
pushBreadcrumb("log", args);
|
|
181
|
+
return originals.log.apply(console, args);
|
|
182
|
+
};
|
|
183
|
+
console.info = function patchedInfo(...args) {
|
|
184
|
+
pushBreadcrumb("info", args);
|
|
185
|
+
return originals.info.apply(console, args);
|
|
186
|
+
};
|
|
187
|
+
console.warn = function patchedWarn(...args) {
|
|
188
|
+
pushBreadcrumb("warn", args);
|
|
189
|
+
return originals.warn.apply(console, args);
|
|
190
|
+
};
|
|
191
|
+
console.error = function patchedError(...args) {
|
|
192
|
+
pushBreadcrumb("error", args);
|
|
193
|
+
return originals.error.apply(console, args);
|
|
194
|
+
};
|
|
195
|
+
return () => {
|
|
196
|
+
console.log = originals.log;
|
|
197
|
+
console.info = originals.info;
|
|
198
|
+
console.warn = originals.warn;
|
|
199
|
+
console.error = originals.error;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
132
202
|
function patchConsoleError(config) {
|
|
133
203
|
const original = console.error;
|
|
134
204
|
console.error = function patchedError(...args) {
|
|
@@ -334,6 +404,7 @@ function initFixPrompt(opts) {
|
|
|
334
404
|
const config = resolveConfig(opts);
|
|
335
405
|
state.config = config;
|
|
336
406
|
state.sessionId = getSessionId();
|
|
407
|
+
state.cleanup.restoreBreadcrumbConsole = patchConsoleForBreadcrumbs();
|
|
337
408
|
const { removeErrorListener, removeRejectionListener } = installGlobalHandlers(config);
|
|
338
409
|
state.cleanup.removeErrorListener = removeErrorListener;
|
|
339
410
|
state.cleanup.removeRejectionListener = removeRejectionListener;
|
|
@@ -371,14 +442,16 @@ function captureException(err, options = {}) {
|
|
|
371
442
|
}
|
|
372
443
|
function _resetForTests() {
|
|
373
444
|
const state = getState();
|
|
445
|
+
state.cleanup.restoreConsoleError?.();
|
|
446
|
+
state.cleanup.restoreBreadcrumbConsole?.();
|
|
374
447
|
state.cleanup.removeErrorListener?.();
|
|
375
448
|
state.cleanup.removeRejectionListener?.();
|
|
376
|
-
state.cleanup.restoreConsoleError?.();
|
|
377
449
|
state.cleanup.restoreFetch?.();
|
|
378
450
|
state.initialized = false;
|
|
379
451
|
state.config = null;
|
|
380
452
|
state.sessionId = null;
|
|
381
453
|
state.cleanup = {};
|
|
454
|
+
clearBreadcrumbs();
|
|
382
455
|
}
|
|
383
456
|
export {
|
|
384
457
|
SDK_NAME,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts","../src/session.ts","../src/transport.ts","../src/handlers.ts","../src/console.ts","../src/fetch.ts","../src/release.ts","../src/state.ts","../src/init.ts"],"sourcesContent":["export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.1';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function installGlobalHandlers(config: ResolvedConfig): {\n removeErrorListener: () => void;\n removeRejectionListener: () => void;\n} {\n const onError = (ev: ErrorEvent) => {\n const err = ev.error;\n sendEvent(config, {\n level: 'error',\n message: ev.message ?? (err && err.message) ?? 'Unknown error',\n stack: err && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'window.error',\n severity: 'error',\n filename: ev.filename,\n line: ev.lineno,\n column: ev.colno,\n error_name: err && err.name,\n },\n });\n };\n\n const onRejection = (ev: PromiseRejectionEvent) => {\n const reason = ev.reason;\n const isErr = reason instanceof Error;\n sendEvent(config, {\n level: 'error',\n message: isErr\n ? reason.message\n : typeof reason === 'string'\n ? reason\n : safeJson(reason),\n stack: isErr && typeof reason.stack === 'string' ? reason.stack : undefined,\n attrs: {\n kind: 'unhandledrejection',\n severity: 'error',\n error_name: isErr ? reason.name : typeof reason,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return {\n removeErrorListener: () => window.removeEventListener('error', onError),\n removeRejectionListener: () =>\n window.removeEventListener('unhandledrejection', onRejection),\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function patchConsoleError(config: ResolvedConfig): () => void {\n const original = console.error;\n\n console.error = function patchedError(...args: any[]) {\n try {\n const stackFrom = args.find((a) => a instanceof Error) as\n | Error\n | undefined;\n sendEvent(config, {\n level: 'warn',\n message: args\n .map((a) =>\n a instanceof Error\n ? a.message\n : typeof a === 'string'\n ? a\n : safeJson(a),\n )\n .join(' '),\n stack: stackFrom && typeof stackFrom.stack === 'string' ? stackFrom.stack : undefined,\n attrs: {\n kind: 'console.error',\n severity: 'warning',\n },\n });\n } catch {\n // never block user code\n }\n return original.apply(console, args as any);\n };\n\n return () => {\n console.error = original;\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Wraps `window.fetch` so failed requests (4xx → warning, 5xx → critical)\n * forward to the broker. Network failures (thrown errors) also forward.\n *\n * URL query string is stripped before logging to reduce PII leak risk.\n */\nexport function wrapFetch(config: ResolvedConfig): () => void {\n if (typeof fetch !== 'function') return () => undefined;\n const original = fetch.bind(globalThis);\n const brokerHost = safeHost(config.endpoint);\n\n const patched: typeof fetch = async (...args) => {\n const t0 = nowMs();\n const [input, init] = args;\n const method =\n (init && (init as RequestInit).method) ||\n (input instanceof Request ? input.method : 'GET');\n const url = urlString(input);\n\n // Never trip on requests to our own broker — would infinite-loop on outage.\n if (url.startsWith(config.endpoint) || safeHost(url) === brokerHost) {\n return original(...args);\n }\n\n try {\n const response = await original(...args);\n const status = response.status;\n const latency = Math.round(nowMs() - t0);\n\n if (status >= 400) {\n sendEvent(config, {\n level: status >= 500 ? 'error' : 'warn',\n message: `fetch ${method} ${stripQuery(url)} → ${status}`,\n attrs: {\n kind: 'fetch',\n severity: status >= 500 ? 'critical' : 'warning',\n method,\n url: stripQuery(url),\n status,\n latency_ms: latency,\n },\n });\n }\n\n return response;\n } catch (err: any) {\n const latency = Math.round(nowMs() - t0);\n sendEvent(config, {\n level: 'error',\n message: `fetch ${method} ${stripQuery(url)} failed: ${err?.message ?? String(err)}`,\n stack: err?.stack,\n attrs: {\n kind: 'fetch',\n severity: 'critical',\n method,\n url: stripQuery(url),\n latency_ms: latency,\n error_name: err?.name,\n },\n });\n throw err;\n }\n };\n\n (globalThis as any).fetch = patched;\n\n return () => {\n (globalThis as any).fetch = original;\n };\n}\n\nfunction urlString(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n if (input instanceof Request) return input.url;\n return String(input);\n}\n\nfunction stripQuery(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : url.slice(0, q);\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","/**\n * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.\n *\n * Priority:\n * 1. opts.release (handled in init.ts)\n * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\n * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA\n * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA\n * 5. process.env.GITHUB_SHA\n * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__\n * 7. <meta name=\"fixprompt-release\" content=\"...\">\n * 8. null\n */\n\nfunction fromImportMeta(): string | null {\n try {\n // Vite/Next/etc. — import.meta.env may exist at build time\n const meta = (import.meta as any).env;\n if (!meta) return null;\n return (\n meta.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||\n meta.VERCEL_GIT_COMMIT_SHA ||\n meta.NEXT_PUBLIC_COMMIT_SHA ||\n null\n );\n } catch {\n return null;\n }\n}\n\nfunction fromProcessEnv(): string | null {\n try {\n if (typeof process === 'undefined' || !process || !process.env) return null;\n return process.env.GITHUB_SHA || process.env.RELEASE || null;\n } catch {\n return null;\n }\n}\n\nfunction fromGlobal(): string | null {\n try {\n const g = globalThis as any;\n return typeof g.__FIXPROMPT_RELEASE__ === 'string'\n ? g.__FIXPROMPT_RELEASE__\n : null;\n } catch {\n return null;\n }\n}\n\nfunction fromMetaTag(): string | null {\n try {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector('meta[name=\"fixprompt-release\"]');\n const content = el?.getAttribute('content');\n return content && content.length > 0 ? content : null;\n } catch {\n return null;\n }\n}\n\nexport function detectRelease(explicit?: string): string | null {\n if (typeof explicit === 'string' && explicit.length > 0) return explicit;\n return (\n fromImportMeta() ||\n fromProcessEnv() ||\n fromGlobal() ||\n fromMetaTag() ||\n null\n );\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n","import { installGlobalHandlers } from './handlers';\nimport { patchConsoleError } from './console';\nimport { wrapFetch as installFetchWrapper } from './fetch';\nimport { detectRelease } from './release';\nimport { getSessionId } from './session';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\nimport type { InitOptions, ResolvedConfig } from './types';\n\nconst DEFAULT_ENDPOINT = 'https://geosloghub-production.up.railway.app';\n\nfunction resolveConfig(opts: InitOptions): ResolvedConfig {\n if (!opts || typeof opts.projectKey !== 'string' || opts.projectKey.length === 0) {\n throw new Error('initFixPrompt: projectKey is required');\n }\n return {\n projectKey: opts.projectKey,\n endpoint: (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, ''),\n source: opts.source ?? 'browser',\n release: detectRelease(opts.release),\n service: opts.service ?? 'web',\n app: opts.app ?? 'browser',\n env: opts.env ?? 'prod',\n wrapFetch: opts.wrapFetch ?? false,\n patchConsoleError: opts.patchConsoleError ?? true,\n debug: opts.debug ?? false,\n };\n}\n\nexport function initFixPrompt(opts: InitOptions): void {\n const state = getState();\n if (state.initialized) {\n if (opts.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] already initialized — call ignored');\n }\n return;\n }\n\n if (typeof window === 'undefined') {\n // Not in a browser environment — degrade gracefully (e.g. SSR import).\n return;\n }\n\n const config = resolveConfig(opts);\n state.config = config;\n state.sessionId = getSessionId();\n\n const { removeErrorListener, removeRejectionListener } =\n installGlobalHandlers(config);\n state.cleanup.removeErrorListener = removeErrorListener;\n state.cleanup.removeRejectionListener = removeRejectionListener;\n\n if (config.patchConsoleError) {\n state.cleanup.restoreConsoleError = patchConsoleError(config);\n }\n if (config.wrapFetch) {\n state.cleanup.restoreFetch = installFetchWrapper(config);\n }\n\n state.initialized = true;\n\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.log('[fixprompt] initialized', {\n endpoint: config.endpoint,\n release: config.release,\n session_id: state.sessionId,\n });\n }\n}\n\n/**\n * Manually report an error or message. Useful for synthetic events\n * (\"Send Test Exception\") or domain-specific catch blocks.\n */\nexport function captureException(\n err: unknown,\n options: { synthetic?: boolean; attrs?: Record<string, any> } = {},\n): void {\n const state = getState();\n if (!state.initialized || !state.config) return;\n\n const isErr = err instanceof Error;\n sendEvent(state.config, {\n level: 'error',\n message: isErr ? err.message : String(err),\n stack: isErr && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'captureException',\n severity: 'error',\n error_name: isErr ? err.name : typeof err,\n ...(options.attrs ?? {}),\n },\n synthetic: options.synthetic === true,\n });\n}\n\n/**\n * Internal use only. Reverses every patch installed by initFixPrompt —\n * intended for tests and React HMR. Not part of the supported API.\n * @internal\n */\nexport function _resetForTests(): void {\n const state = getState();\n state.cleanup.removeErrorListener?.();\n state.cleanup.removeRejectionListener?.();\n state.cleanup.restoreConsoleError?.();\n state.cleanup.restoreFetch?.();\n state.initialized = false;\n state.config = null;\n state.sessionId = null;\n state.cleanup = {};\n}\n"],"mappings":"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","names":["safeJson"]}
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/session.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/handlers.ts","../src/console.ts","../src/fetch.ts","../src/release.ts","../src/state.ts","../src/init.ts"],"sourcesContent":["export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.2';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","/**\n * In-memory ring buffer of the most recent console log lines.\n *\n * These are NEVER streamed to Loki on their own (would balloon volume + cost\n * without much value). Instead, every outgoing event from the SDK attaches\n * the current buffer as `attrs.breadcrumbs`, giving the broker + dashboard\n * the 50 lines that ran in the seconds before an error fired.\n *\n * That context is what the Fix Prompt template uses to give Cursor/Claude\n * meaningful prelude — \"this error happened, and HERE'S WHAT THE APP WAS\n * DOING right before it.\" Without it, the AI just sees a stack with no\n * surrounding state.\n */\n\nconst MAX_BREADCRUMBS = 50;\nconst MAX_MESSAGE_CHARS = 500;\n\nexport interface Breadcrumb {\n /** Wall-clock ms timestamp. */\n ts: number;\n /** One of the four console levels (log / info / warn / error). */\n level: 'log' | 'info' | 'warn' | 'error';\n /** Stringified console args, capped at 500 chars. */\n message: string;\n}\n\nconst buffer: Breadcrumb[] = [];\n\nexport function pushBreadcrumb(level: Breadcrumb['level'], args: any[]): void {\n try {\n const message = stringifyArgs(args);\n buffer.push({ ts: Date.now(), level, message });\n if (buffer.length > MAX_BREADCRUMBS) buffer.shift();\n } catch {\n // never let breadcrumb collection crash user code\n }\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function clearBreadcrumbs(): void {\n buffer.length = 0;\n}\n\nfunction stringifyArgs(args: any[]): string {\n const parts: string[] = [];\n for (const a of args) {\n if (a instanceof Error) {\n parts.push(`${a.name}: ${a.message}`);\n } else if (typeof a === 'string') {\n parts.push(a);\n } else if (a == null) {\n parts.push(String(a));\n } else {\n try {\n parts.push(JSON.stringify(a));\n } catch {\n parts.push(String(a));\n }\n }\n }\n const joined = parts.join(' ');\n return joined.length > MAX_MESSAGE_CHARS\n ? joined.slice(0, MAX_MESSAGE_CHARS) + '…'\n : joined;\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport { getBreadcrumbs } from './breadcrumbs';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n breadcrumbs: getBreadcrumbs(),\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\nexport function installGlobalHandlers(config: ResolvedConfig): {\n removeErrorListener: () => void;\n removeRejectionListener: () => void;\n} {\n const onError = (ev: ErrorEvent) => {\n const err = ev.error;\n sendEvent(config, {\n level: 'error',\n message: ev.message ?? (err && err.message) ?? 'Unknown error',\n stack: err && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'window.error',\n severity: 'error',\n filename: ev.filename,\n line: ev.lineno,\n column: ev.colno,\n error_name: err && err.name,\n },\n });\n };\n\n const onRejection = (ev: PromiseRejectionEvent) => {\n const reason = ev.reason;\n const isErr = reason instanceof Error;\n sendEvent(config, {\n level: 'error',\n message: isErr\n ? reason.message\n : typeof reason === 'string'\n ? reason\n : safeJson(reason),\n stack: isErr && typeof reason.stack === 'string' ? reason.stack : undefined,\n attrs: {\n kind: 'unhandledrejection',\n severity: 'error',\n error_name: isErr ? reason.name : typeof reason,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onRejection);\n\n return {\n removeErrorListener: () => window.removeEventListener('error', onError),\n removeRejectionListener: () =>\n window.removeEventListener('unhandledrejection', onRejection),\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\nimport { pushBreadcrumb } from './breadcrumbs';\n\n/**\n * Patches all four console levels (log / info / warn / error) to push entries\n * into the breadcrumb ring buffer. Does NOT forward anything to the broker —\n * forwarding is handled by patchConsoleError below.\n *\n * Always installed before the forwarding patches so the breadcrumb push runs\n * AFTER the forwarding sendEvent (which snapshots the buffer before the\n * current call lands in it — the forwarded event then sees N-1 lines and the\n * current console.error is itself the Nth, attached to the NEXT event).\n */\nexport function patchConsoleForBreadcrumbs(): () => void {\n const originals = {\n log: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n\n console.log = function patchedLog(...args: any[]) {\n pushBreadcrumb('log', args);\n return originals.log.apply(console, args as any);\n };\n console.info = function patchedInfo(...args: any[]) {\n pushBreadcrumb('info', args);\n return originals.info.apply(console, args as any);\n };\n console.warn = function patchedWarn(...args: any[]) {\n pushBreadcrumb('warn', args);\n return originals.warn.apply(console, args as any);\n };\n console.error = function patchedError(...args: any[]) {\n pushBreadcrumb('error', args);\n return originals.error.apply(console, args as any);\n };\n\n return () => {\n console.log = originals.log;\n console.info = originals.info;\n console.warn = originals.warn;\n console.error = originals.error;\n };\n}\n\nexport function patchConsoleError(config: ResolvedConfig): () => void {\n const original = console.error;\n\n console.error = function patchedError(...args: any[]) {\n try {\n const stackFrom = args.find((a) => a instanceof Error) as\n | Error\n | undefined;\n sendEvent(config, {\n level: 'warn',\n message: args\n .map((a) =>\n a instanceof Error\n ? a.message\n : typeof a === 'string'\n ? a\n : safeJson(a),\n )\n .join(' '),\n stack: stackFrom && typeof stackFrom.stack === 'string' ? stackFrom.stack : undefined,\n attrs: {\n kind: 'console.error',\n severity: 'warning',\n },\n });\n } catch {\n // never block user code\n }\n return original.apply(console, args as any);\n };\n\n return () => {\n console.error = original;\n };\n}\n\nfunction safeJson(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n","import { sendEvent } from './transport';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Wraps `window.fetch` so failed requests (4xx → warning, 5xx → critical)\n * forward to the broker. Network failures (thrown errors) also forward.\n *\n * URL query string is stripped before logging to reduce PII leak risk.\n */\nexport function wrapFetch(config: ResolvedConfig): () => void {\n if (typeof fetch !== 'function') return () => undefined;\n const original = fetch.bind(globalThis);\n const brokerHost = safeHost(config.endpoint);\n\n const patched: typeof fetch = async (...args) => {\n const t0 = nowMs();\n const [input, init] = args;\n const method =\n (init && (init as RequestInit).method) ||\n (input instanceof Request ? input.method : 'GET');\n const url = urlString(input);\n\n // Never trip on requests to our own broker — would infinite-loop on outage.\n if (url.startsWith(config.endpoint) || safeHost(url) === brokerHost) {\n return original(...args);\n }\n\n try {\n const response = await original(...args);\n const status = response.status;\n const latency = Math.round(nowMs() - t0);\n\n if (status >= 400) {\n sendEvent(config, {\n level: status >= 500 ? 'error' : 'warn',\n message: `fetch ${method} ${stripQuery(url)} → ${status}`,\n attrs: {\n kind: 'fetch',\n severity: status >= 500 ? 'critical' : 'warning',\n method,\n url: stripQuery(url),\n status,\n latency_ms: latency,\n },\n });\n }\n\n return response;\n } catch (err: any) {\n const latency = Math.round(nowMs() - t0);\n sendEvent(config, {\n level: 'error',\n message: `fetch ${method} ${stripQuery(url)} failed: ${err?.message ?? String(err)}`,\n stack: err?.stack,\n attrs: {\n kind: 'fetch',\n severity: 'critical',\n method,\n url: stripQuery(url),\n latency_ms: latency,\n error_name: err?.name,\n },\n });\n throw err;\n }\n };\n\n (globalThis as any).fetch = patched;\n\n return () => {\n (globalThis as any).fetch = original;\n };\n}\n\nfunction urlString(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n if (input instanceof Request) return input.url;\n return String(input);\n}\n\nfunction stripQuery(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : url.slice(0, q);\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\nfunction nowMs(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","/**\n * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.\n *\n * Priority:\n * 1. opts.release (handled in init.ts)\n * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\n * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA\n * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA\n * 5. process.env.GITHUB_SHA\n * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__\n * 7. <meta name=\"fixprompt-release\" content=\"...\">\n * 8. null\n */\n\nfunction fromImportMeta(): string | null {\n try {\n // Vite/Next/etc. — import.meta.env may exist at build time\n const meta = (import.meta as any).env;\n if (!meta) return null;\n return (\n meta.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||\n meta.VERCEL_GIT_COMMIT_SHA ||\n meta.NEXT_PUBLIC_COMMIT_SHA ||\n null\n );\n } catch {\n return null;\n }\n}\n\nfunction fromProcessEnv(): string | null {\n try {\n if (typeof process === 'undefined' || !process || !process.env) return null;\n return process.env.GITHUB_SHA || process.env.RELEASE || null;\n } catch {\n return null;\n }\n}\n\nfunction fromGlobal(): string | null {\n try {\n const g = globalThis as any;\n return typeof g.__FIXPROMPT_RELEASE__ === 'string'\n ? g.__FIXPROMPT_RELEASE__\n : null;\n } catch {\n return null;\n }\n}\n\nfunction fromMetaTag(): string | null {\n try {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector('meta[name=\"fixprompt-release\"]');\n const content = el?.getAttribute('content');\n return content && content.length > 0 ? content : null;\n } catch {\n return null;\n }\n}\n\nexport function detectRelease(explicit?: string): string | null {\n if (typeof explicit === 'string' && explicit.length > 0) return explicit;\n return (\n fromImportMeta() ||\n fromProcessEnv() ||\n fromGlobal() ||\n fromMetaTag() ||\n null\n );\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreBreadcrumbConsole?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n","import { installGlobalHandlers } from './handlers';\nimport { patchConsoleError, patchConsoleForBreadcrumbs } from './console';\nimport { wrapFetch as installFetchWrapper } from './fetch';\nimport { detectRelease } from './release';\nimport { getSessionId } from './session';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\nimport { clearBreadcrumbs } from './breadcrumbs';\nimport type { InitOptions, ResolvedConfig } from './types';\n\nconst DEFAULT_ENDPOINT = 'https://geosloghub-production.up.railway.app';\n\nfunction resolveConfig(opts: InitOptions): ResolvedConfig {\n if (!opts || typeof opts.projectKey !== 'string' || opts.projectKey.length === 0) {\n throw new Error('initFixPrompt: projectKey is required');\n }\n return {\n projectKey: opts.projectKey,\n endpoint: (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/$/, ''),\n source: opts.source ?? 'browser',\n release: detectRelease(opts.release),\n service: opts.service ?? 'web',\n app: opts.app ?? 'browser',\n env: opts.env ?? 'prod',\n wrapFetch: opts.wrapFetch ?? false,\n patchConsoleError: opts.patchConsoleError ?? true,\n debug: opts.debug ?? false,\n };\n}\n\nexport function initFixPrompt(opts: InitOptions): void {\n const state = getState();\n if (state.initialized) {\n if (opts.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] already initialized — call ignored');\n }\n return;\n }\n\n if (typeof window === 'undefined') {\n // Not in a browser environment — degrade gracefully (e.g. SSR import).\n return;\n }\n\n const config = resolveConfig(opts);\n state.config = config;\n state.sessionId = getSessionId();\n\n // Install breadcrumb capture FIRST so any console call during init lands\n // in the ring buffer. Forwarding patches wrap these later — order is\n // important: forwarders snapshot the buffer BEFORE pushing the current\n // call, so the forwarded event sees the lines that ran before it.\n state.cleanup.restoreBreadcrumbConsole = patchConsoleForBreadcrumbs();\n\n const { removeErrorListener, removeRejectionListener } =\n installGlobalHandlers(config);\n state.cleanup.removeErrorListener = removeErrorListener;\n state.cleanup.removeRejectionListener = removeRejectionListener;\n\n if (config.patchConsoleError) {\n state.cleanup.restoreConsoleError = patchConsoleError(config);\n }\n if (config.wrapFetch) {\n state.cleanup.restoreFetch = installFetchWrapper(config);\n }\n\n state.initialized = true;\n\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.log('[fixprompt] initialized', {\n endpoint: config.endpoint,\n release: config.release,\n session_id: state.sessionId,\n });\n }\n}\n\n/**\n * Manually report an error or message. Useful for synthetic events\n * (\"Send Test Exception\") or domain-specific catch blocks.\n */\nexport function captureException(\n err: unknown,\n options: { synthetic?: boolean; attrs?: Record<string, any> } = {},\n): void {\n const state = getState();\n if (!state.initialized || !state.config) return;\n\n const isErr = err instanceof Error;\n sendEvent(state.config, {\n level: 'error',\n message: isErr ? err.message : String(err),\n stack: isErr && typeof err.stack === 'string' ? err.stack : undefined,\n attrs: {\n kind: 'captureException',\n severity: 'error',\n error_name: isErr ? err.name : typeof err,\n ...(options.attrs ?? {}),\n },\n synthetic: options.synthetic === true,\n });\n}\n\n/**\n * Internal use only. Reverses every patch installed by initFixPrompt —\n * intended for tests and React HMR. Not part of the supported API.\n * @internal\n */\nexport function _resetForTests(): void {\n const state = getState();\n state.cleanup.restoreConsoleError?.();\n state.cleanup.restoreBreadcrumbConsole?.();\n state.cleanup.removeErrorListener?.();\n state.cleanup.removeRejectionListener?.();\n state.cleanup.restoreFetch?.();\n state.initialized = false;\n state.config = null;\n state.sessionId = null;\n state.cleanup = {};\n clearBreadcrumbs();\n}\n"],"mappings":"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","names":["safeJson"]}
|
package/dist/react.cjs
CHANGED
|
@@ -37,7 +37,7 @@ var React = __toESM(require("react"), 1);
|
|
|
37
37
|
|
|
38
38
|
// src/version.ts
|
|
39
39
|
var SDK_NAME = "@fixprompt/browser";
|
|
40
|
-
var SDK_VERSION = "0.0.
|
|
40
|
+
var SDK_VERSION = "0.0.2";
|
|
41
41
|
|
|
42
42
|
// src/session.ts
|
|
43
43
|
var STORAGE_KEY = "fixprompt_sid";
|
|
@@ -65,6 +65,12 @@ function getSessionId() {
|
|
|
65
65
|
return uuid();
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// src/breadcrumbs.ts
|
|
69
|
+
var buffer = [];
|
|
70
|
+
function getBreadcrumbs() {
|
|
71
|
+
return [...buffer];
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
// src/transport.ts
|
|
69
75
|
function sendEvent(config, payload) {
|
|
70
76
|
try {
|
|
@@ -82,7 +88,8 @@ function sendEvent(config, payload) {
|
|
|
82
88
|
session_id: getSessionId(),
|
|
83
89
|
release: config.release,
|
|
84
90
|
page_url: typeof location !== "undefined" ? location.href : void 0,
|
|
85
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
|
|
91
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
|
|
92
|
+
breadcrumbs: getBreadcrumbs()
|
|
86
93
|
},
|
|
87
94
|
synthetic: payload.synthetic
|
|
88
95
|
};
|
package/dist/react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/version.ts","../src/session.ts","../src/transport.ts","../src/state.ts"],"sourcesContent":["import * as React from 'react';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\n\nexport interface FixPromptErrorBoundaryProps {\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class FixPromptErrorBoundary extends React.Component<\n FixPromptErrorBoundaryProps,\n State\n> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo): void {\n const sdk = getState();\n if (sdk.initialized && sdk.config) {\n sendEvent(sdk.config, {\n level: 'error',\n message: error.message,\n stack: typeof error.stack === 'string' ? error.stack : undefined,\n attrs: {\n kind: 'react.errorBoundary',\n severity: 'error',\n error_name: error.name,\n component_stack: info.componentStack,\n },\n });\n }\n this.props.onError?.(error, info);\n }\n\n render(): React.ReactNode {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === 'function') {\n return fallback(this.state.error);\n }\n return fallback ?? null;\n }\n return this.props.children ?? null;\n }\n}\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.1';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/version.ts","../src/session.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/state.ts"],"sourcesContent":["import * as React from 'react';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\n\nexport interface FixPromptErrorBoundaryProps {\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class FixPromptErrorBoundary extends React.Component<\n FixPromptErrorBoundaryProps,\n State\n> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo): void {\n const sdk = getState();\n if (sdk.initialized && sdk.config) {\n sendEvent(sdk.config, {\n level: 'error',\n message: error.message,\n stack: typeof error.stack === 'string' ? error.stack : undefined,\n attrs: {\n kind: 'react.errorBoundary',\n severity: 'error',\n error_name: error.name,\n component_stack: info.componentStack,\n },\n });\n }\n this.props.onError?.(error, info);\n }\n\n render(): React.ReactNode {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === 'function') {\n return fallback(this.state.error);\n }\n return fallback ?? null;\n }\n return this.props.children ?? null;\n }\n}\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.2';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","/**\n * In-memory ring buffer of the most recent console log lines.\n *\n * These are NEVER streamed to Loki on their own (would balloon volume + cost\n * without much value). Instead, every outgoing event from the SDK attaches\n * the current buffer as `attrs.breadcrumbs`, giving the broker + dashboard\n * the 50 lines that ran in the seconds before an error fired.\n *\n * That context is what the Fix Prompt template uses to give Cursor/Claude\n * meaningful prelude — \"this error happened, and HERE'S WHAT THE APP WAS\n * DOING right before it.\" Without it, the AI just sees a stack with no\n * surrounding state.\n */\n\nconst MAX_BREADCRUMBS = 50;\nconst MAX_MESSAGE_CHARS = 500;\n\nexport interface Breadcrumb {\n /** Wall-clock ms timestamp. */\n ts: number;\n /** One of the four console levels (log / info / warn / error). */\n level: 'log' | 'info' | 'warn' | 'error';\n /** Stringified console args, capped at 500 chars. */\n message: string;\n}\n\nconst buffer: Breadcrumb[] = [];\n\nexport function pushBreadcrumb(level: Breadcrumb['level'], args: any[]): void {\n try {\n const message = stringifyArgs(args);\n buffer.push({ ts: Date.now(), level, message });\n if (buffer.length > MAX_BREADCRUMBS) buffer.shift();\n } catch {\n // never let breadcrumb collection crash user code\n }\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function clearBreadcrumbs(): void {\n buffer.length = 0;\n}\n\nfunction stringifyArgs(args: any[]): string {\n const parts: string[] = [];\n for (const a of args) {\n if (a instanceof Error) {\n parts.push(`${a.name}: ${a.message}`);\n } else if (typeof a === 'string') {\n parts.push(a);\n } else if (a == null) {\n parts.push(String(a));\n } else {\n try {\n parts.push(JSON.stringify(a));\n } catch {\n parts.push(String(a));\n }\n }\n }\n const joined = parts.join(' ');\n return joined.length > MAX_MESSAGE_CHARS\n ? joined.slice(0, MAX_MESSAGE_CHARS) + '…'\n : joined;\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport { getBreadcrumbs } from './breadcrumbs';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n breadcrumbs: getBreadcrumbs(),\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreBreadcrumbConsole?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n"],"mappings":"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","names":[]}
|
package/dist/react.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
|
|
4
4
|
// src/version.ts
|
|
5
5
|
var SDK_NAME = "@fixprompt/browser";
|
|
6
|
-
var SDK_VERSION = "0.0.
|
|
6
|
+
var SDK_VERSION = "0.0.2";
|
|
7
7
|
|
|
8
8
|
// src/session.ts
|
|
9
9
|
var STORAGE_KEY = "fixprompt_sid";
|
|
@@ -31,6 +31,12 @@ function getSessionId() {
|
|
|
31
31
|
return uuid();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// src/breadcrumbs.ts
|
|
35
|
+
var buffer = [];
|
|
36
|
+
function getBreadcrumbs() {
|
|
37
|
+
return [...buffer];
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
// src/transport.ts
|
|
35
41
|
function sendEvent(config, payload) {
|
|
36
42
|
try {
|
|
@@ -48,7 +54,8 @@ function sendEvent(config, payload) {
|
|
|
48
54
|
session_id: getSessionId(),
|
|
49
55
|
release: config.release,
|
|
50
56
|
page_url: typeof location !== "undefined" ? location.href : void 0,
|
|
51
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
|
|
57
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0,
|
|
58
|
+
breadcrumbs: getBreadcrumbs()
|
|
52
59
|
},
|
|
53
60
|
synthetic: payload.synthetic
|
|
54
61
|
};
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/version.ts","../src/session.ts","../src/transport.ts","../src/state.ts"],"sourcesContent":["import * as React from 'react';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\n\nexport interface FixPromptErrorBoundaryProps {\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class FixPromptErrorBoundary extends React.Component<\n FixPromptErrorBoundaryProps,\n State\n> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo): void {\n const sdk = getState();\n if (sdk.initialized && sdk.config) {\n sendEvent(sdk.config, {\n level: 'error',\n message: error.message,\n stack: typeof error.stack === 'string' ? error.stack : undefined,\n attrs: {\n kind: 'react.errorBoundary',\n severity: 'error',\n error_name: error.name,\n component_stack: info.componentStack,\n },\n });\n }\n this.props.onError?.(error, info);\n }\n\n render(): React.ReactNode {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === 'function') {\n return fallback(this.state.error);\n }\n return fallback ?? null;\n }\n return this.props.children ?? null;\n }\n}\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.1';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/version.ts","../src/session.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/state.ts"],"sourcesContent":["import * as React from 'react';\nimport { sendEvent } from './transport';\nimport { getState } from './state';\n\nexport interface FixPromptErrorBoundaryProps {\n fallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n onError?: (error: Error, info: React.ErrorInfo) => void;\n children?: React.ReactNode;\n}\n\ninterface State {\n error: Error | null;\n}\n\nexport class FixPromptErrorBoundary extends React.Component<\n FixPromptErrorBoundaryProps,\n State\n> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo): void {\n const sdk = getState();\n if (sdk.initialized && sdk.config) {\n sendEvent(sdk.config, {\n level: 'error',\n message: error.message,\n stack: typeof error.stack === 'string' ? error.stack : undefined,\n attrs: {\n kind: 'react.errorBoundary',\n severity: 'error',\n error_name: error.name,\n component_stack: info.componentStack,\n },\n });\n }\n this.props.onError?.(error, info);\n }\n\n render(): React.ReactNode {\n if (this.state.error) {\n const { fallback } = this.props;\n if (typeof fallback === 'function') {\n return fallback(this.state.error);\n }\n return fallback ?? null;\n }\n return this.props.children ?? null;\n }\n}\n","export const SDK_NAME = '@fixprompt/browser';\nexport const SDK_VERSION = '0.0.2';\n","const STORAGE_KEY = 'fixprompt_sid';\n\nfunction uuid(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n // RFC 4122 v4-ish fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getSessionId(): string {\n try {\n if (typeof sessionStorage !== 'undefined') {\n const existing = sessionStorage.getItem(STORAGE_KEY);\n if (existing) return existing;\n const sid = uuid();\n sessionStorage.setItem(STORAGE_KEY, sid);\n return sid;\n }\n } catch {\n // sessionStorage may throw under quota / privacy modes\n }\n return uuid();\n}\n","/**\n * In-memory ring buffer of the most recent console log lines.\n *\n * These are NEVER streamed to Loki on their own (would balloon volume + cost\n * without much value). Instead, every outgoing event from the SDK attaches\n * the current buffer as `attrs.breadcrumbs`, giving the broker + dashboard\n * the 50 lines that ran in the seconds before an error fired.\n *\n * That context is what the Fix Prompt template uses to give Cursor/Claude\n * meaningful prelude — \"this error happened, and HERE'S WHAT THE APP WAS\n * DOING right before it.\" Without it, the AI just sees a stack with no\n * surrounding state.\n */\n\nconst MAX_BREADCRUMBS = 50;\nconst MAX_MESSAGE_CHARS = 500;\n\nexport interface Breadcrumb {\n /** Wall-clock ms timestamp. */\n ts: number;\n /** One of the four console levels (log / info / warn / error). */\n level: 'log' | 'info' | 'warn' | 'error';\n /** Stringified console args, capped at 500 chars. */\n message: string;\n}\n\nconst buffer: Breadcrumb[] = [];\n\nexport function pushBreadcrumb(level: Breadcrumb['level'], args: any[]): void {\n try {\n const message = stringifyArgs(args);\n buffer.push({ ts: Date.now(), level, message });\n if (buffer.length > MAX_BREADCRUMBS) buffer.shift();\n } catch {\n // never let breadcrumb collection crash user code\n }\n}\n\nexport function getBreadcrumbs(): Breadcrumb[] {\n return [...buffer];\n}\n\nexport function clearBreadcrumbs(): void {\n buffer.length = 0;\n}\n\nfunction stringifyArgs(args: any[]): string {\n const parts: string[] = [];\n for (const a of args) {\n if (a instanceof Error) {\n parts.push(`${a.name}: ${a.message}`);\n } else if (typeof a === 'string') {\n parts.push(a);\n } else if (a == null) {\n parts.push(String(a));\n } else {\n try {\n parts.push(JSON.stringify(a));\n } catch {\n parts.push(String(a));\n }\n }\n }\n const joined = parts.join(' ');\n return joined.length > MAX_MESSAGE_CHARS\n ? joined.slice(0, MAX_MESSAGE_CHARS) + '…'\n : joined;\n}\n","import { SDK_NAME, SDK_VERSION } from './version';\nimport { getSessionId } from './session';\nimport { getBreadcrumbs } from './breadcrumbs';\nimport type { EventPayload, ResolvedConfig } from './types';\n\n/**\n * Sends one event to the broker. Fire-and-forget — never throws.\n *\n * Uses navigator.sendBeacon when available for pagehide reliability;\n * falls back to fetch with keepalive.\n */\nexport function sendEvent(\n config: ResolvedConfig,\n payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {\n service?: string;\n app?: string;\n env?: string;\n },\n): void {\n try {\n const body: EventPayload = {\n service: payload.service ?? config.service,\n app: payload.app ?? config.app,\n env: payload.env ?? config.env,\n level: payload.level,\n message: payload.message,\n stack: payload.stack,\n attrs: {\n ...payload.attrs,\n sdk: SDK_NAME,\n sdk_version: SDK_VERSION,\n session_id: getSessionId(),\n release: config.release,\n page_url: typeof location !== 'undefined' ? location.href : undefined,\n user_agent:\n typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n breadcrumbs: getBreadcrumbs(),\n },\n synthetic: payload.synthetic,\n };\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/ingest/log`;\n const json = JSON.stringify(body);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'x-loghub-source': config.source,\n 'x-loghub-key': config.projectKey,\n } as Record<string, string>;\n\n if (\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function' &&\n // sendBeacon does not allow custom headers, so we use it only as\n // a last-resort fallback once we add a URL-token path. Until then,\n // prefer fetch+keepalive so auth headers are honored.\n false\n ) {\n const blob = new Blob([json], { type: 'application/json' });\n navigator.sendBeacon(url, blob);\n return;\n }\n\n if (typeof fetch === 'function') {\n void fetch(url, {\n method: 'POST',\n headers,\n body: json,\n keepalive: true,\n mode: 'cors',\n credentials: 'omit',\n }).catch(() => undefined);\n }\n } catch (err) {\n if (config.debug) {\n // eslint-disable-next-line no-console\n console.warn('[fixprompt] transport error', err);\n }\n }\n}\n","import type { ResolvedConfig } from './types';\n\nexport interface CleanupHooks {\n removeErrorListener?: () => void;\n removeRejectionListener?: () => void;\n restoreConsoleError?: () => void;\n restoreBreadcrumbConsole?: () => void;\n restoreFetch?: () => void;\n}\n\ninterface SdkState {\n initialized: boolean;\n config: ResolvedConfig | null;\n sessionId: string | null;\n cleanup: CleanupHooks;\n}\n\nconst STATE_KEY = '__fixprompt_browser_state__';\n\nfunction globalScope(): any {\n if (typeof globalThis !== 'undefined') return globalThis;\n if (typeof window !== 'undefined') return window;\n return {};\n}\n\nexport function getState(): SdkState {\n const g = globalScope();\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = {\n initialized: false,\n config: null,\n sessionId: null,\n cleanup: {},\n } satisfies SdkState;\n }\n return g[STATE_KEY] as SdkState;\n}\n\nexport function resetStateForTests(): void {\n const g = globalScope();\n delete g[STATE_KEY];\n}\n"],"mappings":"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","names":[]}
|
package/dist/state.d.ts
CHANGED
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const SDK_NAME = "@fixprompt/browser";
|
|
2
|
-
export declare const SDK_VERSION = "0.0.
|
|
2
|
+
export declare const SDK_VERSION = "0.0.2";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fixprompt/browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "FixPrompt browser SDK — captures errors, unhandled rejections, console.error, and (optionally) failed fetches; forwards to the FixPrompt broker.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|