@fixprompt/browser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js ADDED
@@ -0,0 +1,147 @@
1
+ // src/react.tsx
2
+ import * as React from "react";
3
+
4
+ // src/version.ts
5
+ var SDK_NAME = "@fixprompt/browser";
6
+ var SDK_VERSION = "0.0.1";
7
+
8
+ // src/session.ts
9
+ var STORAGE_KEY = "fixprompt_sid";
10
+ function uuid() {
11
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
12
+ return crypto.randomUUID();
13
+ }
14
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
15
+ const r = Math.random() * 16 | 0;
16
+ const v = c === "x" ? r : r & 3 | 8;
17
+ return v.toString(16);
18
+ });
19
+ }
20
+ function getSessionId() {
21
+ try {
22
+ if (typeof sessionStorage !== "undefined") {
23
+ const existing = sessionStorage.getItem(STORAGE_KEY);
24
+ if (existing) return existing;
25
+ const sid = uuid();
26
+ sessionStorage.setItem(STORAGE_KEY, sid);
27
+ return sid;
28
+ }
29
+ } catch {
30
+ }
31
+ return uuid();
32
+ }
33
+
34
+ // src/transport.ts
35
+ function sendEvent(config, payload) {
36
+ try {
37
+ const body = {
38
+ service: payload.service ?? config.service,
39
+ app: payload.app ?? config.app,
40
+ env: payload.env ?? config.env,
41
+ level: payload.level,
42
+ message: payload.message,
43
+ stack: payload.stack,
44
+ attrs: {
45
+ ...payload.attrs,
46
+ sdk: SDK_NAME,
47
+ sdk_version: SDK_VERSION,
48
+ session_id: getSessionId(),
49
+ release: config.release,
50
+ page_url: typeof location !== "undefined" ? location.href : void 0,
51
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
52
+ },
53
+ synthetic: payload.synthetic
54
+ };
55
+ const url = `${config.endpoint.replace(/\/$/, "")}/ingest/log`;
56
+ const json = JSON.stringify(body);
57
+ const headers = {
58
+ "Content-Type": "application/json",
59
+ "x-loghub-source": config.source,
60
+ "x-loghub-key": config.projectKey
61
+ };
62
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && // sendBeacon does not allow custom headers, so we use it only as
63
+ // a last-resort fallback once we add a URL-token path. Until then,
64
+ // prefer fetch+keepalive so auth headers are honored.
65
+ false) {
66
+ const blob = new Blob([json], { type: "application/json" });
67
+ navigator.sendBeacon(url, blob);
68
+ return;
69
+ }
70
+ if (typeof fetch === "function") {
71
+ void fetch(url, {
72
+ method: "POST",
73
+ headers,
74
+ body: json,
75
+ keepalive: true,
76
+ mode: "cors",
77
+ credentials: "omit"
78
+ }).catch(() => void 0);
79
+ }
80
+ } catch (err) {
81
+ if (config.debug) {
82
+ console.warn("[fixprompt] transport error", err);
83
+ }
84
+ }
85
+ }
86
+
87
+ // src/state.ts
88
+ var STATE_KEY = "__fixprompt_browser_state__";
89
+ function globalScope() {
90
+ if (typeof globalThis !== "undefined") return globalThis;
91
+ if (typeof window !== "undefined") return window;
92
+ return {};
93
+ }
94
+ function getState() {
95
+ const g = globalScope();
96
+ if (!g[STATE_KEY]) {
97
+ g[STATE_KEY] = {
98
+ initialized: false,
99
+ config: null,
100
+ sessionId: null,
101
+ cleanup: {}
102
+ };
103
+ }
104
+ return g[STATE_KEY];
105
+ }
106
+
107
+ // src/react.tsx
108
+ var FixPromptErrorBoundary = class extends React.Component {
109
+ constructor() {
110
+ super(...arguments);
111
+ this.state = { error: null };
112
+ }
113
+ static getDerivedStateFromError(error) {
114
+ return { error };
115
+ }
116
+ componentDidCatch(error, info) {
117
+ const sdk = getState();
118
+ if (sdk.initialized && sdk.config) {
119
+ sendEvent(sdk.config, {
120
+ level: "error",
121
+ message: error.message,
122
+ stack: typeof error.stack === "string" ? error.stack : void 0,
123
+ attrs: {
124
+ kind: "react.errorBoundary",
125
+ severity: "error",
126
+ error_name: error.name,
127
+ component_stack: info.componentStack
128
+ }
129
+ });
130
+ }
131
+ this.props.onError?.(error, info);
132
+ }
133
+ render() {
134
+ if (this.state.error) {
135
+ const { fallback } = this.props;
136
+ if (typeof fallback === "function") {
137
+ return fallback(this.state.error);
138
+ }
139
+ return fallback ?? null;
140
+ }
141
+ return this.props.children ?? null;
142
+ }
143
+ };
144
+ export {
145
+ FixPromptErrorBoundary
146
+ };
147
+ //# sourceMappingURL=react.js.map
@@ -0,0 +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":";AAAA,YAAY,WAAW;;;ACAhB,IAAM,WAAW;AACjB,IAAM,cAAc;;;ACD3B,IAAM,cAAc;AAEpB,SAAS,OAAe;AACtB,MACE,OAAO,WAAW,eAClB,OAAO,OAAO,eAAe,YAC7B;AACA,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,uCAAuC,QAAQ,SAAS,CAAC,MAAM;AACpE,UAAM,IAAK,KAAK,OAAO,IAAI,KAAM;AACjC,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAO;AACtC,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB,CAAC;AACH;AAEO,SAAS,eAAuB;AACrC,MAAI;AACF,QAAI,OAAO,mBAAmB,aAAa;AACzC,YAAM,WAAW,eAAe,QAAQ,WAAW;AACnD,UAAI,SAAU,QAAO;AACrB,YAAM,MAAM,KAAK;AACjB,qBAAe,QAAQ,aAAa,GAAG;AACvC,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,KAAK;AACd;;;ACpBO,SAAS,UACd,QACA,SAKM;AACN,MAAI;AACF,UAAM,OAAqB;AAAA,MACzB,SAAS,QAAQ,WAAW,OAAO;AAAA,MACnC,KAAK,QAAQ,OAAO,OAAO;AAAA,MAC3B,KAAK,QAAQ,OAAO,OAAO;AAAA,MAC3B,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,OAAO,QAAQ;AAAA,MACf,OAAO;AAAA,QACL,GAAG,QAAQ;AAAA,QACX,KAAK;AAAA,QACL,aAAa;AAAA,QACb,YAAY,aAAa;AAAA,QACzB,SAAS,OAAO;AAAA,QAChB,UAAU,OAAO,aAAa,cAAc,SAAS,OAAO;AAAA,QAC5D,YACE,OAAO,cAAc,cAAc,UAAU,YAAY;AAAA,MAC7D;AAAA,MACA,WAAW,QAAQ;AAAA,IACrB;AAEA,UAAM,MAAM,GAAG,OAAO,SAAS,QAAQ,OAAO,EAAE,CAAC;AACjD,UAAM,OAAO,KAAK,UAAU,IAAI;AAEhC,UAAM,UAAU;AAAA,MACd,gBAAgB;AAAA,MAChB,mBAAmB,OAAO;AAAA,MAC1B,gBAAgB,OAAO;AAAA,IACzB;AAEA,QACE,OAAO,cAAc,eACrB,OAAO,UAAU,eAAe;AAAA;AAAA;AAAA,IAIhC,OACA;AACA,YAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC1D,gBAAU,WAAW,KAAK,IAAI;AAC9B;AAAA,IACF;AAEA,QAAI,OAAO,UAAU,YAAY;AAC/B,WAAK,MAAM,KAAK;AAAA,QACd,QAAQ;AAAA,QACR;AAAA,QACA,MAAM;AAAA,QACN,WAAW;AAAA,QACX,MAAM;AAAA,QACN,aAAa;AAAA,MACf,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1B;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,OAAO,OAAO;AAEhB,cAAQ,KAAK,+BAA+B,GAAG;AAAA,IACjD;AAAA,EACF;AACF;;;AC7DA,IAAM,YAAY;AAElB,SAAS,cAAmB;AAC1B,MAAI,OAAO,eAAe,YAAa,QAAO;AAC9C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,CAAC;AACV;AAEO,SAAS,WAAqB;AACnC,QAAM,IAAI,YAAY;AACtB,MAAI,CAAC,EAAE,SAAS,GAAG;AACjB,MAAE,SAAS,IAAI;AAAA,MACb,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,SAAS,CAAC;AAAA,IACZ;AAAA,EACF;AACA,SAAO,EAAE,SAAS;AACpB;;;AJrBO,IAAM,yBAAN,cAA2C,gBAGhD;AAAA,EAHK;AAAA;AAIL,iBAAe,EAAE,OAAO,KAAK;AAAA;AAAA,EAE7B,OAAO,yBAAyB,OAAqB;AACnD,WAAO,EAAE,MAAM;AAAA,EACjB;AAAA,EAEA,kBAAkB,OAAc,MAA6B;AAC3D,UAAM,MAAM,SAAS;AACrB,QAAI,IAAI,eAAe,IAAI,QAAQ;AACjC,gBAAU,IAAI,QAAQ;AAAA,QACpB,OAAO;AAAA,QACP,SAAS,MAAM;AAAA,QACf,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AAAA,QACvD,OAAO;AAAA,UACL,MAAM;AAAA,UACN,UAAU;AAAA,UACV,YAAY,MAAM;AAAA,UAClB,iBAAiB,KAAK;AAAA,QACxB;AAAA,MACF,CAAC;AAAA,IACH;AACA,SAAK,MAAM,UAAU,OAAO,IAAI;AAAA,EAClC;AAAA,EAEA,SAA0B;AACxB,QAAI,KAAK,MAAM,OAAO;AACpB,YAAM,EAAE,SAAS,IAAI,KAAK;AAC1B,UAAI,OAAO,aAAa,YAAY;AAClC,eAAO,SAAS,KAAK,MAAM,KAAK;AAAA,MAClC;AACA,aAAO,YAAY;AAAA,IACrB;AACA,WAAO,KAAK,MAAM,YAAY;AAAA,EAChC;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Release auto-detect cascade — see WEEK1_TICKETS.md W1-12.
3
+ *
4
+ * Priority:
5
+ * 1. opts.release (handled in init.ts)
6
+ * 2. import.meta.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA
7
+ * 3. import.meta.env.VERCEL_GIT_COMMIT_SHA
8
+ * 4. import.meta.env.NEXT_PUBLIC_COMMIT_SHA
9
+ * 5. process.env.GITHUB_SHA
10
+ * 6. process.env.RELEASE / globalThis.__FIXPROMPT_RELEASE__
11
+ * 7. <meta name="fixprompt-release" content="...">
12
+ * 8. null
13
+ */
14
+ export declare function detectRelease(explicit?: string): string | null;
@@ -0,0 +1 @@
1
+ export declare function getSessionId(): string;
@@ -0,0 +1,16 @@
1
+ import type { ResolvedConfig } from './types';
2
+ export interface CleanupHooks {
3
+ removeErrorListener?: () => void;
4
+ removeRejectionListener?: () => void;
5
+ restoreConsoleError?: () => void;
6
+ restoreFetch?: () => void;
7
+ }
8
+ interface SdkState {
9
+ initialized: boolean;
10
+ config: ResolvedConfig | null;
11
+ sessionId: string | null;
12
+ cleanup: CleanupHooks;
13
+ }
14
+ export declare function getState(): SdkState;
15
+ export declare function resetStateForTests(): void;
16
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { EventPayload, ResolvedConfig } from './types';
2
+ /**
3
+ * Sends one event to the broker. Fire-and-forget — never throws.
4
+ *
5
+ * Uses navigator.sendBeacon when available for pagehide reliability;
6
+ * falls back to fetch with keepalive.
7
+ */
8
+ export declare function sendEvent(config: ResolvedConfig, payload: Omit<EventPayload, 'service' | 'app' | 'env'> & {
9
+ service?: string;
10
+ app?: string;
11
+ env?: string;
12
+ }): void;
@@ -0,0 +1,43 @@
1
+ export type Severity = 'critical' | 'error' | 'warning' | 'info';
2
+ export interface InitOptions {
3
+ /** Project key issued by FixPrompt (kept opaque; never log). */
4
+ projectKey: string;
5
+ /** Broker base URL. Defaults to https://geosloghub-production.up.railway.app */
6
+ endpoint?: string;
7
+ /** Source identifier the broker uses to look up the project. Defaults to `${slug}-prod`-style values supplied by the dashboard. */
8
+ source?: string;
9
+ /** Override release auto-detect (see release.ts). */
10
+ release?: string;
11
+ /** App/service labels surfaced in Loki. */
12
+ service?: string;
13
+ app?: string;
14
+ env?: string;
15
+ /** Enable fetch() wrapper (default: false). */
16
+ wrapFetch?: boolean;
17
+ /** Enable console.error patch (default: true). */
18
+ patchConsoleError?: boolean;
19
+ /** Verbose logging on console — debug only. */
20
+ debug?: boolean;
21
+ }
22
+ export interface EventPayload {
23
+ service: string;
24
+ app: string;
25
+ env: string;
26
+ level: 'debug' | 'info' | 'warn' | 'error';
27
+ message: string;
28
+ stack?: string;
29
+ attrs: Record<string, any>;
30
+ synthetic?: boolean;
31
+ }
32
+ export interface ResolvedConfig {
33
+ projectKey: string;
34
+ endpoint: string;
35
+ source: string;
36
+ release: string | null;
37
+ service: string;
38
+ app: string;
39
+ env: string;
40
+ wrapFetch: boolean;
41
+ patchConsoleError: boolean;
42
+ debug: boolean;
43
+ }
@@ -0,0 +1,2 @@
1
+ export declare const SDK_NAME = "@fixprompt/browser";
2
+ export declare const SDK_VERSION = "0.0.1";
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@fixprompt/browser",
3
+ "version": "0.0.1",
4
+ "description": "FixPrompt browser SDK — captures errors, unhandled rejections, console.error, and (optionally) failed fetches; forwards to the FixPrompt broker.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Geos-LLC/FixPrompt.git",
9
+ "directory": "browser-sdk"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "unpkg": "./dist/index.global.js",
19
+ "jsdelivr": "./dist/index.global.js",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "require": "./dist/index.cjs",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./react": {
28
+ "types": "./dist/react.d.ts",
29
+ "import": "./dist/react.js",
30
+ "require": "./dist/react.cjs",
31
+ "default": "./dist/react.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": ["dist", "README.md"],
36
+ "sideEffects": false,
37
+ "scripts": {
38
+ "build": "tsup --no-dts && tsc --emitDeclarationOnly --declaration --outDir dist",
39
+ "dev": "tsup --watch --no-dts",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "typecheck": "tsc --noEmit",
43
+ "prepare": "npm run build",
44
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
45
+ },
46
+ "peerDependencies": {
47
+ "react": ">=16.8"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "react": { "optional": true }
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^20.0.0",
54
+ "@types/react": "^18.0.0",
55
+ "happy-dom": "^15.0.0",
56
+ "react": "^18.0.0",
57
+ "tsup": "^8.3.5",
58
+ "typescript": "^5.4.0",
59
+ "vitest": "^2.1.0"
60
+ }
61
+ }