@fogg/bug-reporter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/chunk-6TCI6T2U.cjs +45 -0
  4. package/dist/chunk-6TCI6T2U.cjs.map +1 -0
  5. package/dist/chunk-S2YRP4GT.js +22 -0
  6. package/dist/chunk-S2YRP4GT.js.map +1 -0
  7. package/dist/index.cjs +1963 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.cts +331 -0
  10. package/dist/index.d.ts +331 -0
  11. package/dist/index.js +1956 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/recording-ML63ZQ6A.cjs +120 -0
  14. package/dist/recording-ML63ZQ6A.cjs.map +1 -0
  15. package/dist/recording-YSR6IORT.js +118 -0
  16. package/dist/recording-YSR6IORT.js.map +1 -0
  17. package/dist/screenshot-F4W72WRK.js +176 -0
  18. package/dist/screenshot-F4W72WRK.js.map +1 -0
  19. package/dist/screenshot-FRAZAS6B.cjs +178 -0
  20. package/dist/screenshot-FRAZAS6B.cjs.map +1 -0
  21. package/dist/styles/index.css +495 -0
  22. package/dist/styles/index.css.map +1 -0
  23. package/dist/styles/index.d.cts +2 -0
  24. package/dist/styles/index.d.ts +2 -0
  25. package/docs/backend-local.md +16 -0
  26. package/docs/backend-s3.md +31 -0
  27. package/docs/browser-compatibility.md +8 -0
  28. package/docs/framework-cra.md +10 -0
  29. package/docs/framework-nextjs.md +16 -0
  30. package/docs/framework-remix.md +6 -0
  31. package/docs/framework-vite.md +21 -0
  32. package/docs/known-limitations.md +6 -0
  33. package/docs/quickstart.md +26 -0
  34. package/docs/security.md +9 -0
  35. package/examples/backend-local/README.md +11 -0
  36. package/examples/backend-local/package.json +13 -0
  37. package/examples/backend-local/src/server.mjs +31 -0
  38. package/examples/backend-s3-presign/README.md +14 -0
  39. package/examples/backend-s3-presign/package.json +13 -0
  40. package/examples/backend-s3-presign/src/server.mjs +53 -0
  41. package/examples/sandbox-vite/README.md +25 -0
  42. package/examples/sandbox-vite/index.html +12 -0
  43. package/examples/sandbox-vite/package-lock.json +1880 -0
  44. package/examples/sandbox-vite/package.json +24 -0
  45. package/examples/sandbox-vite/src/App.tsx +200 -0
  46. package/examples/sandbox-vite/src/main.tsx +10 -0
  47. package/examples/sandbox-vite/src/sandbox.css +74 -0
  48. package/examples/sandbox-vite/tsconfig.json +14 -0
  49. package/examples/sandbox-vite/vite.config.ts +9 -0
  50. package/package.json +93 -0
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "bug-reporter-sandbox-vite",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "prepare:sdk": "npm --prefix ../.. run build",
8
+ "dev": "npm run prepare:sdk && vite",
9
+ "build": "npm run prepare:sdk && vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@fogg/bug-reporter": "file:../..",
14
+ "react": "^18.3.1",
15
+ "react-dom": "^18.3.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^18.3.24",
19
+ "@types/react-dom": "^18.3.7",
20
+ "@vitejs/plugin-react": "^5.0.2",
21
+ "typescript": "^5.9.2",
22
+ "vite": "^7.1.3"
23
+ }
24
+ }
@@ -0,0 +1,200 @@
1
+ import { useState } from "react";
2
+ import { BugReporter } from "@fogg/bug-reporter";
3
+ import type { CustomFormProps } from "@fogg/bug-reporter";
4
+
5
+ declare global {
6
+ interface Window {
7
+ __bugReporterSandboxFetchMocked?: boolean;
8
+ }
9
+ }
10
+
11
+ function parseBody(body: BodyInit | null | undefined): unknown {
12
+ if (typeof body !== "string") {
13
+ return undefined;
14
+ }
15
+ try {
16
+ return JSON.parse(body);
17
+ } catch {
18
+ return body;
19
+ }
20
+ }
21
+
22
+ if (!window.__bugReporterSandboxFetchMocked) {
23
+ const originalFetch = window.fetch.bind(window);
24
+ window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
25
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
26
+
27
+ if (url.includes("/sandbox/upload")) {
28
+ return new Response(
29
+ JSON.stringify({
30
+ url: `https://example.local/assets/${Date.now()}`,
31
+ key: `assets/${Date.now()}`
32
+ }),
33
+ { status: 200, headers: { "content-type": "application/json" } }
34
+ );
35
+ }
36
+
37
+ if (url.includes("/sandbox/report")) {
38
+ const payload = parseBody(init?.body);
39
+ console.log("[sandbox] submitted payload", payload);
40
+
41
+ return new Response(
42
+ JSON.stringify({
43
+ id: `report-${Date.now()}`,
44
+ message: "Received in sandbox"
45
+ }),
46
+ { status: 200, headers: { "content-type": "application/json" } }
47
+ );
48
+ }
49
+
50
+ if (url.includes("/sandbox/fake-api-failure")) {
51
+ return new Response(
52
+ JSON.stringify({
53
+ error: "Simulated backend outage"
54
+ }),
55
+ { status: 503, headers: { "content-type": "application/json" } }
56
+ );
57
+ }
58
+
59
+ if (url.includes("/sandbox/success-with-bad-data")) {
60
+ return new Response(
61
+ JSON.stringify({
62
+ ok: true,
63
+ payload: null
64
+ }),
65
+ { status: 200, headers: { "content-type": "application/json" } }
66
+ );
67
+ }
68
+
69
+ return originalFetch(input, init);
70
+ };
71
+ window.__bugReporterSandboxFetchMocked = true;
72
+ }
73
+
74
+ function SeverityCustomForm({ attributes, updateAttribute }: CustomFormProps) {
75
+ const severityLevel = typeof attributes.severityLevel === "string" ? attributes.severityLevel : "";
76
+
77
+ return (
78
+ <label className="br-field">
79
+ What is the severity level?
80
+ <select value={severityLevel} onChange={(event) => updateAttribute("severityLevel", event.target.value)}>
81
+ <option value="" disabled>
82
+ Select severity level
83
+ </option>
84
+ <option value="preventing_release_campaign">Is preventing to release a Campaign</option>
85
+ <option value="campaign_already_live">My campaign is already live</option>
86
+ <option value="campaign_live_in_few_hours">The campaign will be live in a few hours</option>
87
+ <option value="campaign_live_later_today">The campaign will be live later today</option>
88
+ </select>
89
+ </label>
90
+ );
91
+ }
92
+
93
+ export function App() {
94
+ const [scenarioStatus, setScenarioStatus] = useState("No scenario triggered yet.");
95
+
96
+ const simulateApiFailure = async () => {
97
+ try {
98
+ const response = await fetch("/sandbox/fake-api-failure", { method: "POST" });
99
+ if (!response.ok) {
100
+ const body = await response.json().catch(() => null);
101
+ throw new Error(`API failure ${response.status}: ${JSON.stringify(body)}`);
102
+ }
103
+ } catch (error) {
104
+ console.error("[sandbox] simulated API failure", error);
105
+ setScenarioStatus("Triggered: fake API failure (503).");
106
+ }
107
+ };
108
+
109
+ const simulateConsoleErrors = () => {
110
+ console.error("[sandbox] console error #1: failed to parse widget config.");
111
+ console.error("[sandbox] console error #2", { reason: "Unexpected null value", source: "sandbox-button" });
112
+ console.warn("[sandbox] warning: stale feature flags detected.");
113
+ setScenarioStatus("Triggered: console error burst.");
114
+ };
115
+
116
+ const simulate200WithFrontendErrors = async () => {
117
+ try {
118
+ const response = await fetch("/sandbox/success-with-bad-data", { method: "GET" });
119
+ const body = (await response.json()) as { payload?: { items?: Array<{ id: string }> } | null };
120
+
121
+ // Intentional frontend processing bug to simulate runtime errors after a 200 response.
122
+ const firstItemId = body.payload!.items![0]!.id;
123
+ console.log("[sandbox] first item id", firstItemId);
124
+ } catch (error) {
125
+ console.error("[sandbox] 200 response but frontend processing failed", error);
126
+ setScenarioStatus("Triggered: 200 response with frontend runtime error.");
127
+ }
128
+ };
129
+
130
+ return (
131
+ <div className="page">
132
+ <main className="card">
133
+ <h1>bug-reporter local sandbox</h1>
134
+ <p>
135
+ Use the floating launcher at the bottom-right to test screenshot capture, optional annotation, recording,
136
+ diagnostics, upload flow, and final submission.
137
+ </p>
138
+
139
+ <section className="demo-block">
140
+ <h2>Visible content</h2>
141
+ <p>Use this text to validate screenshot selection and report details.</p>
142
+ </section>
143
+
144
+ <section className="demo-block">
145
+ <h2>Scenario buttons</h2>
146
+ <p>Use these to generate reproducible logs and request traces in bug-reporter diagnostics.</p>
147
+ <div className="sandbox-actions">
148
+ <button type="button" className="sandbox-btn" onClick={() => void simulateApiFailure()}>
149
+ Simulate fake API failure
150
+ </button>
151
+ <button type="button" className="sandbox-btn" onClick={simulateConsoleErrors}>
152
+ Simulate console errors
153
+ </button>
154
+ <button type="button" className="sandbox-btn" onClick={() => void simulate200WithFrontendErrors()}>
155
+ Simulate 200 + frontend error
156
+ </button>
157
+ </div>
158
+ <p className="sandbox-status">{scenarioStatus}</p>
159
+ </section>
160
+
161
+ <section className="demo-block sensitive" data-bug-reporter-mask="true">
162
+ <h2>Masked region</h2>
163
+ <p>This block should appear blurred in screenshots because of mask selectors.</p>
164
+ </section>
165
+ </main>
166
+
167
+ <BugReporter
168
+ CustomForm={SeverityCustomForm}
169
+ themeMode="light"
170
+ buttonColor="#374151"
171
+ config={{
172
+ apiEndpoint: "/sandbox/report",
173
+ projectId: "sandbox-vite",
174
+ appVersion: "0.0.1",
175
+ environment: "development",
176
+ storage: {
177
+ mode: "proxy",
178
+ proxy: {
179
+ uploadEndpoint: "/sandbox/upload"
180
+ }
181
+ },
182
+ features: {
183
+ screenshot: true,
184
+ recording: true,
185
+ annotations: true,
186
+ consoleLogs: true,
187
+ networkInfo: true
188
+ },
189
+ diagnostics: {
190
+ consoleBufferSize: 50,
191
+ requestBufferSize: 100
192
+ },
193
+ privacy: {
194
+ maskSelectors: ["[data-bug-reporter-mask='true']", "input[type='password']"]
195
+ }
196
+ }}
197
+ />
198
+ </div>
199
+ );
200
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./sandbox.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
@@ -0,0 +1,74 @@
1
+ :root {
2
+ font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
3
+ line-height: 1.4;
4
+ color: #0f172a;
5
+ background: #eef3f8;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ }
15
+
16
+ .page {
17
+ min-height: 100vh;
18
+ padding: 32px 16px 96px;
19
+ }
20
+
21
+ .card {
22
+ max-width: 840px;
23
+ margin: 0 auto;
24
+ background: #fff;
25
+ border-radius: 16px;
26
+ border: 1px solid #d9e3ef;
27
+ padding: 24px;
28
+ box-shadow: 0 12px 24px rgba(15, 23, 42, 0.08);
29
+ }
30
+
31
+ h1 {
32
+ margin: 0 0 8px;
33
+ }
34
+
35
+ .demo-block {
36
+ margin-top: 16px;
37
+ padding: 12px;
38
+ border-radius: 12px;
39
+ border: 1px solid #d9e3ef;
40
+ background: #f8fbff;
41
+ }
42
+
43
+ .sandbox-actions {
44
+ display: flex;
45
+ flex-wrap: wrap;
46
+ gap: 10px;
47
+ margin-top: 10px;
48
+ }
49
+
50
+ .sandbox-btn {
51
+ border: 1px solid #c7d6e7;
52
+ background: #fff;
53
+ color: #0f172a;
54
+ border-radius: 10px;
55
+ padding: 8px 12px;
56
+ font-size: 13px;
57
+ font-weight: 600;
58
+ cursor: pointer;
59
+ }
60
+
61
+ .sandbox-btn:hover {
62
+ background: #eef5ff;
63
+ }
64
+
65
+ .sandbox-status {
66
+ margin-top: 10px;
67
+ font-size: 13px;
68
+ color: #334155;
69
+ }
70
+
71
+ .sensitive {
72
+ background: #fff8f8;
73
+ border-color: #ffd9d9;
74
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "types": ["vite/client"]
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 5174
8
+ }
9
+ });
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@fogg/bug-reporter",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready React bug reporting SDK with screenshot, recording, diagnostics, and pluggable storage backends.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "docs",
14
+ "examples"
15
+ ],
16
+ "sideEffects": [
17
+ "**/*.css"
18
+ ],
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./styles.css": "./dist/index.css"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "changeset": "changeset",
32
+ "clean": "rm -rf dist",
33
+ "build": "npm run clean && tsup",
34
+ "typecheck": "tsc --noEmit",
35
+ "lint": "eslint .",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "test:e2e": "playwright test",
39
+ "test:e2e:site": "vite --config tests/e2e/site/vite.config.ts --host 127.0.0.1 --port 4173",
40
+ "release:verify": "npm run lint && npm run typecheck && npm test && npm run build",
41
+ "version:packages": "changeset version",
42
+ "publish:dry-run": "npm run release:verify && npm publish --dry-run --ignore-scripts",
43
+ "publish:npm": "npm publish",
44
+ "release": "changeset publish",
45
+ "sandbox:vite": "npm run sandbox:vite:install && npm --prefix examples/sandbox-vite run dev",
46
+ "sandbox:vite:build": "npm run sandbox:vite:install && npm --prefix examples/sandbox-vite run build",
47
+ "sandbox:vite:install": "npm --prefix examples/sandbox-vite install",
48
+ "prepublishOnly": "npm run release:verify"
49
+ },
50
+ "peerDependencies": {
51
+ "react": ">=16.8.0",
52
+ "react-dom": ">=16.8.0"
53
+ },
54
+ "dependencies": {
55
+ "html2canvas": "^1.4.1"
56
+ },
57
+ "devDependencies": {
58
+ "@changesets/cli": "^2.29.6",
59
+ "@playwright/test": "^1.56.1",
60
+ "@testing-library/jest-dom": "^6.9.1",
61
+ "@testing-library/react": "^16.3.0",
62
+ "@testing-library/user-event": "^14.6.1",
63
+ "@types/node": "^24.3.1",
64
+ "@types/react": "^19.1.11",
65
+ "@types/react-dom": "^19.1.7",
66
+ "@typescript-eslint/eslint-plugin": "^8.42.0",
67
+ "@typescript-eslint/parser": "^8.42.0",
68
+ "@vitejs/plugin-react": "^5.0.2",
69
+ "eslint": "^9.34.0",
70
+ "eslint-config-prettier": "^10.1.8",
71
+ "jsdom": "^26.1.0",
72
+ "typescript": "^5.9.2",
73
+ "tsup": "^8.5.0",
74
+ "vite": "^7.1.3",
75
+ "vitest": "^3.2.4"
76
+ },
77
+ "keywords": [
78
+ "react",
79
+ "bug-reporting",
80
+ "screenshot",
81
+ "screen-recording",
82
+ "diagnostics",
83
+ "sdk"
84
+ ],
85
+ "repository": {
86
+ "type": "git",
87
+ "url": "git+https://github.com/your-org/bug-reporter.git"
88
+ },
89
+ "bugs": {
90
+ "url": "https://github.com/your-org/bug-reporter/issues"
91
+ },
92
+ "homepage": "https://github.com/your-org/bug-reporter#readme"
93
+ }