@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,118 @@
1
+ import { BugReporterError } from './chunk-S2YRP4GT.js';
2
+
3
+ // src/core/recording.ts
4
+ function pickMimeType() {
5
+ const candidates = ["video/webm;codecs=vp9", "video/webm;codecs=vp8", "video/webm"];
6
+ for (const candidate of candidates) {
7
+ if (typeof MediaRecorder !== "undefined" && MediaRecorder.isTypeSupported(candidate)) {
8
+ return candidate;
9
+ }
10
+ }
11
+ return "video/webm";
12
+ }
13
+ async function startScreenRecording(options) {
14
+ if (!navigator.mediaDevices?.getDisplayMedia) {
15
+ throw new BugReporterError("RECORDING_ERROR", "Screen recording is not supported by this browser.");
16
+ }
17
+ let stream;
18
+ try {
19
+ stream = await navigator.mediaDevices.getDisplayMedia({
20
+ video: true,
21
+ audio: false
22
+ });
23
+ } catch (error) {
24
+ throw new BugReporterError("PERMISSION_DENIED", "Permission denied for screen recording.", error);
25
+ }
26
+ const mimeType = pickMimeType();
27
+ let recorder;
28
+ try {
29
+ recorder = new MediaRecorder(stream, { mimeType });
30
+ } catch (error) {
31
+ stream.getTracks().forEach((track) => track.stop());
32
+ throw new BugReporterError("RECORDING_ERROR", "Could not initialize MediaRecorder.", error);
33
+ }
34
+ const chunks = [];
35
+ const startedAt = Date.now();
36
+ let latestSize = 0;
37
+ let tickSeconds = 0;
38
+ let completed = false;
39
+ let isCancelled = false;
40
+ let resolvePromise;
41
+ let rejectPromise;
42
+ const promise = new Promise((resolve, reject) => {
43
+ resolvePromise = resolve;
44
+ rejectPromise = reject;
45
+ });
46
+ const tickInterval = window.setInterval(() => {
47
+ tickSeconds += 1;
48
+ options.onTick?.(tickSeconds);
49
+ }, 1e3);
50
+ const hardStop = window.setTimeout(() => {
51
+ if (recorder.state !== "inactive") {
52
+ recorder.stop();
53
+ }
54
+ }, options.maxSeconds * 1e3);
55
+ const teardown = () => {
56
+ if (completed) {
57
+ return;
58
+ }
59
+ completed = true;
60
+ window.clearInterval(tickInterval);
61
+ window.clearTimeout(hardStop);
62
+ stream.getTracks().forEach((track) => track.stop());
63
+ };
64
+ recorder.addEventListener("dataavailable", (event) => {
65
+ if (!event.data || event.data.size === 0) {
66
+ return;
67
+ }
68
+ chunks.push(event.data);
69
+ latestSize += event.data.size;
70
+ if (latestSize > options.maxBytes) {
71
+ isCancelled = true;
72
+ recorder.stop();
73
+ rejectPromise(
74
+ new BugReporterError(
75
+ "VALIDATION_ERROR",
76
+ `Recording exceeds max size (${Math.round(options.maxBytes / 1024 / 1024)}MB).`
77
+ )
78
+ );
79
+ }
80
+ });
81
+ recorder.addEventListener("error", (event) => {
82
+ teardown();
83
+ rejectPromise(new BugReporterError("RECORDING_ERROR", "Recording failed.", event));
84
+ });
85
+ recorder.addEventListener("stop", () => {
86
+ teardown();
87
+ if (isCancelled) {
88
+ return;
89
+ }
90
+ const blob = new Blob(chunks, { type: mimeType });
91
+ resolvePromise({
92
+ blob,
93
+ mimeType,
94
+ durationMs: Date.now() - startedAt
95
+ });
96
+ });
97
+ recorder.start(300);
98
+ return {
99
+ stop: () => {
100
+ if (recorder.state !== "inactive") {
101
+ recorder.stop();
102
+ }
103
+ },
104
+ cancel: () => {
105
+ isCancelled = true;
106
+ if (recorder.state !== "inactive") {
107
+ recorder.stop();
108
+ }
109
+ teardown();
110
+ rejectPromise(new BugReporterError("ABORTED", "Recording cancelled by user."));
111
+ },
112
+ promise
113
+ };
114
+ }
115
+
116
+ export { startScreenRecording };
117
+ //# sourceMappingURL=recording-YSR6IORT.js.map
118
+ //# sourceMappingURL=recording-YSR6IORT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/recording.ts"],"names":[],"mappings":"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","file":"recording-YSR6IORT.js","sourcesContent":["import { BugReporterError } from \"../types\";\n\nexport type RecordingResult = {\n blob: Blob;\n mimeType: string;\n durationMs: number;\n};\n\nexport type ActiveRecording = {\n stop: () => void;\n cancel: () => void;\n promise: Promise<RecordingResult>;\n};\n\ntype StartRecordingOptions = {\n maxSeconds: number;\n maxBytes: number;\n onTick?: (seconds: number) => void;\n};\n\nfunction pickMimeType(): string {\n const candidates = [\"video/webm;codecs=vp9\", \"video/webm;codecs=vp8\", \"video/webm\"];\n for (const candidate of candidates) {\n if (typeof MediaRecorder !== \"undefined\" && MediaRecorder.isTypeSupported(candidate)) {\n return candidate;\n }\n }\n return \"video/webm\";\n}\n\nexport async function startScreenRecording(options: StartRecordingOptions): Promise<ActiveRecording> {\n if (!navigator.mediaDevices?.getDisplayMedia) {\n throw new BugReporterError(\"RECORDING_ERROR\", \"Screen recording is not supported by this browser.\");\n }\n\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n video: true,\n audio: false\n });\n } catch (error) {\n throw new BugReporterError(\"PERMISSION_DENIED\", \"Permission denied for screen recording.\", error);\n }\n\n const mimeType = pickMimeType();\n let recorder: MediaRecorder;\n try {\n recorder = new MediaRecorder(stream, { mimeType });\n } catch (error) {\n stream.getTracks().forEach((track) => track.stop());\n throw new BugReporterError(\"RECORDING_ERROR\", \"Could not initialize MediaRecorder.\", error);\n }\n\n const chunks: Blob[] = [];\n const startedAt = Date.now();\n let latestSize = 0;\n let tickSeconds = 0;\n let completed = false;\n let isCancelled = false;\n\n let resolvePromise: (value: RecordingResult) => void;\n let rejectPromise: (reason?: unknown) => void;\n\n const promise = new Promise<RecordingResult>((resolve, reject) => {\n resolvePromise = resolve;\n rejectPromise = reject;\n });\n\n const tickInterval = window.setInterval(() => {\n tickSeconds += 1;\n options.onTick?.(tickSeconds);\n }, 1000);\n\n const hardStop = window.setTimeout(() => {\n if (recorder.state !== \"inactive\") {\n recorder.stop();\n }\n }, options.maxSeconds * 1000);\n\n const teardown = () => {\n if (completed) {\n return;\n }\n completed = true;\n window.clearInterval(tickInterval);\n window.clearTimeout(hardStop);\n stream.getTracks().forEach((track) => track.stop());\n };\n\n recorder.addEventListener(\"dataavailable\", (event) => {\n if (!event.data || event.data.size === 0) {\n return;\n }\n chunks.push(event.data);\n latestSize += event.data.size;\n if (latestSize > options.maxBytes) {\n isCancelled = true;\n recorder.stop();\n rejectPromise(\n new BugReporterError(\n \"VALIDATION_ERROR\",\n `Recording exceeds max size (${Math.round(options.maxBytes / 1024 / 1024)}MB).`\n )\n );\n }\n });\n\n recorder.addEventListener(\"error\", (event) => {\n teardown();\n rejectPromise(new BugReporterError(\"RECORDING_ERROR\", \"Recording failed.\", event));\n });\n\n recorder.addEventListener(\"stop\", () => {\n teardown();\n if (isCancelled) {\n return;\n }\n const blob = new Blob(chunks, { type: mimeType });\n resolvePromise({\n blob,\n mimeType,\n durationMs: Date.now() - startedAt\n });\n });\n\n recorder.start(300);\n\n return {\n stop: () => {\n if (recorder.state !== \"inactive\") {\n recorder.stop();\n }\n },\n cancel: () => {\n isCancelled = true;\n if (recorder.state !== \"inactive\") {\n recorder.stop();\n }\n teardown();\n rejectPromise(new BugReporterError(\"ABORTED\", \"Recording cancelled by user.\"));\n },\n promise\n };\n}\n"]}
@@ -0,0 +1,176 @@
1
+ import { BugReporterError } from './chunk-S2YRP4GT.js';
2
+
3
+ // src/core/screenshot.ts
4
+ function applyMasking(selectors) {
5
+ const masked = [];
6
+ for (const selector of selectors) {
7
+ document.querySelectorAll(selector).forEach((element) => {
8
+ masked.push({ element, previous: element.style.filter });
9
+ element.style.filter = "blur(12px)";
10
+ });
11
+ }
12
+ return masked;
13
+ }
14
+ function resetMasking(masked) {
15
+ masked.forEach(({ element, previous }) => {
16
+ element.style.filter = previous;
17
+ });
18
+ }
19
+ function scrubText(root, patterns) {
20
+ if (!patterns.length) {
21
+ return [];
22
+ }
23
+ const walkers = [];
24
+ const regexes = patterns.map((pattern) => typeof pattern === "string" ? new RegExp(pattern, "g") : pattern);
25
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
26
+ while (walker.nextNode()) {
27
+ const text = walker.currentNode;
28
+ let replaced = text.textContent ?? "";
29
+ for (const regex of regexes) {
30
+ replaced = replaced.replace(regex, "[redacted]");
31
+ }
32
+ if (replaced !== text.textContent) {
33
+ walkers.push({ node: text, previous: text.textContent ?? "" });
34
+ text.textContent = replaced;
35
+ }
36
+ }
37
+ return walkers;
38
+ }
39
+ function restoreText(changed) {
40
+ changed.forEach(({ node, previous }) => {
41
+ node.textContent = previous;
42
+ });
43
+ }
44
+ function createSelectionOverlay() {
45
+ return new Promise((resolve, reject) => {
46
+ const overlay = document.createElement("div");
47
+ overlay.setAttribute("data-bug-reporter-overlay", "true");
48
+ overlay.style.position = "fixed";
49
+ overlay.style.inset = "0";
50
+ overlay.style.background = "rgba(0,0,0,0.35)";
51
+ overlay.style.cursor = "crosshair";
52
+ overlay.style.zIndex = "2147483647";
53
+ const box = document.createElement("div");
54
+ box.style.position = "fixed";
55
+ box.style.border = "2px solid #ffffff";
56
+ box.style.background = "rgba(27, 116, 228, 0.2)";
57
+ box.style.pointerEvents = "none";
58
+ box.style.display = "none";
59
+ overlay.appendChild(box);
60
+ const cleanup = () => {
61
+ overlay.removeEventListener("mousedown", onMouseDown);
62
+ overlay.removeEventListener("mousemove", onMouseMove);
63
+ overlay.removeEventListener("mouseup", onMouseUp);
64
+ window.removeEventListener("keydown", onKeyDown);
65
+ overlay.remove();
66
+ };
67
+ let startX = 0;
68
+ let startY = 0;
69
+ let isDragging = false;
70
+ const onKeyDown = (event) => {
71
+ if (event.key === "Escape") {
72
+ cleanup();
73
+ reject(new BugReporterError("ABORTED", "Screenshot capture cancelled."));
74
+ }
75
+ };
76
+ const onMouseDown = (event) => {
77
+ isDragging = true;
78
+ startX = event.clientX;
79
+ startY = event.clientY;
80
+ box.style.display = "block";
81
+ box.style.left = `${startX}px`;
82
+ box.style.top = `${startY}px`;
83
+ box.style.width = "0px";
84
+ box.style.height = "0px";
85
+ };
86
+ const onMouseMove = (event) => {
87
+ if (!isDragging) {
88
+ return;
89
+ }
90
+ const left = Math.min(startX, event.clientX);
91
+ const top = Math.min(startY, event.clientY);
92
+ const width = Math.abs(startX - event.clientX);
93
+ const height = Math.abs(startY - event.clientY);
94
+ box.style.left = `${left}px`;
95
+ box.style.top = `${top}px`;
96
+ box.style.width = `${width}px`;
97
+ box.style.height = `${height}px`;
98
+ };
99
+ const onMouseUp = (event) => {
100
+ if (!isDragging) {
101
+ return;
102
+ }
103
+ isDragging = false;
104
+ const left = Math.min(startX, event.clientX);
105
+ const top = Math.min(startY, event.clientY);
106
+ const width = Math.abs(startX - event.clientX);
107
+ const height = Math.abs(startY - event.clientY);
108
+ cleanup();
109
+ if (width < 8 || height < 8) {
110
+ reject(new BugReporterError("CAPTURE_ERROR", "Selection area is too small."));
111
+ return;
112
+ }
113
+ resolve({ left, top, width, height });
114
+ };
115
+ overlay.addEventListener("mousedown", onMouseDown);
116
+ overlay.addEventListener("mousemove", onMouseMove);
117
+ overlay.addEventListener("mouseup", onMouseUp);
118
+ window.addEventListener("keydown", onKeyDown);
119
+ document.body.appendChild(overlay);
120
+ });
121
+ }
122
+ function canvasToBlob(canvas) {
123
+ return new Promise((resolve, reject) => {
124
+ canvas.toBlob((blob) => {
125
+ if (!blob) {
126
+ reject(new BugReporterError("CAPTURE_ERROR", "Failed to build screenshot blob."));
127
+ return;
128
+ }
129
+ resolve(blob);
130
+ }, "image/png");
131
+ });
132
+ }
133
+ async function captureScreenshotArea(options) {
134
+ const selection = await createSelectionOverlay();
135
+ const masked = applyMasking(options.maskSelectors);
136
+ const textChanges = scrubText(document.body, options.redactTextPatterns);
137
+ try {
138
+ const { default: html2canvas } = await import('html2canvas');
139
+ const baseCanvas = await html2canvas(document.documentElement, {
140
+ useCORS: true,
141
+ scrollX: -window.scrollX,
142
+ scrollY: -window.scrollY,
143
+ backgroundColor: null,
144
+ logging: false,
145
+ windowWidth: document.documentElement.scrollWidth,
146
+ windowHeight: document.documentElement.scrollHeight
147
+ });
148
+ const scaleX = baseCanvas.width / window.innerWidth;
149
+ const scaleY = baseCanvas.height / window.innerHeight;
150
+ const sx = Math.round(selection.left * scaleX);
151
+ const sy = Math.round(selection.top * scaleY);
152
+ const sw = Math.round(selection.width * scaleX);
153
+ const sh = Math.round(selection.height * scaleY);
154
+ const cropped = document.createElement("canvas");
155
+ cropped.width = sw;
156
+ cropped.height = sh;
157
+ const context = cropped.getContext("2d");
158
+ if (!context) {
159
+ throw new BugReporterError("CAPTURE_ERROR", "Canvas 2D context unavailable.");
160
+ }
161
+ context.drawImage(baseCanvas, sx, sy, sw, sh, 0, 0, sw, sh);
162
+ return await canvasToBlob(cropped);
163
+ } catch (error) {
164
+ if (error instanceof BugReporterError) {
165
+ throw error;
166
+ }
167
+ throw new BugReporterError("CAPTURE_ERROR", "Screenshot capture failed.", error);
168
+ } finally {
169
+ resetMasking(masked);
170
+ restoreText(textChanges);
171
+ }
172
+ }
173
+
174
+ export { captureScreenshotArea };
175
+ //# sourceMappingURL=screenshot-F4W72WRK.js.map
176
+ //# sourceMappingURL=screenshot-F4W72WRK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/screenshot.ts"],"names":[],"mappings":"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","file":"screenshot-F4W72WRK.js","sourcesContent":["import { BugReporterError } from \"../types\";\n\ntype CaptureOptions = {\n maskSelectors: string[];\n redactTextPatterns: Array<string | RegExp>;\n};\n\ntype SelectionRect = {\n left: number;\n top: number;\n width: number;\n height: number;\n};\n\nfunction applyMasking(selectors: string[]): Array<{ element: HTMLElement; previous: string }> {\n const masked: Array<{ element: HTMLElement; previous: string }> = [];\n for (const selector of selectors) {\n document.querySelectorAll<HTMLElement>(selector).forEach((element) => {\n masked.push({ element, previous: element.style.filter });\n element.style.filter = \"blur(12px)\";\n });\n }\n return masked;\n}\n\nfunction resetMasking(masked: Array<{ element: HTMLElement; previous: string }>): void {\n masked.forEach(({ element, previous }) => {\n element.style.filter = previous;\n });\n}\n\nfunction scrubText(root: HTMLElement, patterns: Array<string | RegExp>): Array<{ node: Text; previous: string }> {\n if (!patterns.length) {\n return [];\n }\n\n const walkers: Array<{ node: Text; previous: string }> = [];\n const regexes = patterns.map((pattern) => (typeof pattern === \"string\" ? new RegExp(pattern, \"g\") : pattern));\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const text = walker.currentNode as Text;\n let replaced = text.textContent ?? \"\";\n for (const regex of regexes) {\n replaced = replaced.replace(regex, \"[redacted]\");\n }\n if (replaced !== text.textContent) {\n walkers.push({ node: text, previous: text.textContent ?? \"\" });\n text.textContent = replaced;\n }\n }\n return walkers;\n}\n\nfunction restoreText(changed: Array<{ node: Text; previous: string }>): void {\n changed.forEach(({ node, previous }) => {\n node.textContent = previous;\n });\n}\n\nfunction createSelectionOverlay(): Promise<SelectionRect> {\n return new Promise((resolve, reject) => {\n const overlay = document.createElement(\"div\");\n overlay.setAttribute(\"data-bug-reporter-overlay\", \"true\");\n overlay.style.position = \"fixed\";\n overlay.style.inset = \"0\";\n overlay.style.background = \"rgba(0,0,0,0.35)\";\n overlay.style.cursor = \"crosshair\";\n overlay.style.zIndex = \"2147483647\";\n\n const box = document.createElement(\"div\");\n box.style.position = \"fixed\";\n box.style.border = \"2px solid #ffffff\";\n box.style.background = \"rgba(27, 116, 228, 0.2)\";\n box.style.pointerEvents = \"none\";\n box.style.display = \"none\";\n overlay.appendChild(box);\n\n const cleanup = () => {\n overlay.removeEventListener(\"mousedown\", onMouseDown);\n overlay.removeEventListener(\"mousemove\", onMouseMove);\n overlay.removeEventListener(\"mouseup\", onMouseUp);\n window.removeEventListener(\"keydown\", onKeyDown);\n overlay.remove();\n };\n\n let startX = 0;\n let startY = 0;\n let isDragging = false;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n cleanup();\n reject(new BugReporterError(\"ABORTED\", \"Screenshot capture cancelled.\"));\n }\n };\n\n const onMouseDown = (event: MouseEvent) => {\n isDragging = true;\n startX = event.clientX;\n startY = event.clientY;\n box.style.display = \"block\";\n box.style.left = `${startX}px`;\n box.style.top = `${startY}px`;\n box.style.width = \"0px\";\n box.style.height = \"0px\";\n };\n\n const onMouseMove = (event: MouseEvent) => {\n if (!isDragging) {\n return;\n }\n const left = Math.min(startX, event.clientX);\n const top = Math.min(startY, event.clientY);\n const width = Math.abs(startX - event.clientX);\n const height = Math.abs(startY - event.clientY);\n box.style.left = `${left}px`;\n box.style.top = `${top}px`;\n box.style.width = `${width}px`;\n box.style.height = `${height}px`;\n };\n\n const onMouseUp = (event: MouseEvent) => {\n if (!isDragging) {\n return;\n }\n isDragging = false;\n const left = Math.min(startX, event.clientX);\n const top = Math.min(startY, event.clientY);\n const width = Math.abs(startX - event.clientX);\n const height = Math.abs(startY - event.clientY);\n cleanup();\n if (width < 8 || height < 8) {\n reject(new BugReporterError(\"CAPTURE_ERROR\", \"Selection area is too small.\"));\n return;\n }\n resolve({ left, top, width, height });\n };\n\n overlay.addEventListener(\"mousedown\", onMouseDown);\n overlay.addEventListener(\"mousemove\", onMouseMove);\n overlay.addEventListener(\"mouseup\", onMouseUp);\n window.addEventListener(\"keydown\", onKeyDown);\n document.body.appendChild(overlay);\n });\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new BugReporterError(\"CAPTURE_ERROR\", \"Failed to build screenshot blob.\"));\n return;\n }\n resolve(blob);\n }, \"image/png\");\n });\n}\n\nexport async function captureScreenshotArea(options: CaptureOptions): Promise<Blob> {\n const selection = await createSelectionOverlay();\n const masked = applyMasking(options.maskSelectors);\n const textChanges = scrubText(document.body, options.redactTextPatterns);\n\n try {\n const { default: html2canvas } = await import(\"html2canvas\");\n const baseCanvas = await html2canvas(document.documentElement, {\n useCORS: true,\n scrollX: -window.scrollX,\n scrollY: -window.scrollY,\n backgroundColor: null,\n logging: false,\n windowWidth: document.documentElement.scrollWidth,\n windowHeight: document.documentElement.scrollHeight\n });\n\n const scaleX = baseCanvas.width / window.innerWidth;\n const scaleY = baseCanvas.height / window.innerHeight;\n\n const sx = Math.round(selection.left * scaleX);\n const sy = Math.round(selection.top * scaleY);\n const sw = Math.round(selection.width * scaleX);\n const sh = Math.round(selection.height * scaleY);\n\n const cropped = document.createElement(\"canvas\");\n cropped.width = sw;\n cropped.height = sh;\n\n const context = cropped.getContext(\"2d\");\n if (!context) {\n throw new BugReporterError(\"CAPTURE_ERROR\", \"Canvas 2D context unavailable.\");\n }\n\n context.drawImage(baseCanvas, sx, sy, sw, sh, 0, 0, sw, sh);\n return await canvasToBlob(cropped);\n } catch (error) {\n if (error instanceof BugReporterError) {\n throw error;\n }\n throw new BugReporterError(\"CAPTURE_ERROR\", \"Screenshot capture failed.\", error);\n } finally {\n resetMasking(masked);\n restoreText(textChanges);\n }\n}\n"]}
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ var chunk6TCI6T2U_cjs = require('./chunk-6TCI6T2U.cjs');
4
+
5
+ // src/core/screenshot.ts
6
+ function applyMasking(selectors) {
7
+ const masked = [];
8
+ for (const selector of selectors) {
9
+ document.querySelectorAll(selector).forEach((element) => {
10
+ masked.push({ element, previous: element.style.filter });
11
+ element.style.filter = "blur(12px)";
12
+ });
13
+ }
14
+ return masked;
15
+ }
16
+ function resetMasking(masked) {
17
+ masked.forEach(({ element, previous }) => {
18
+ element.style.filter = previous;
19
+ });
20
+ }
21
+ function scrubText(root, patterns) {
22
+ if (!patterns.length) {
23
+ return [];
24
+ }
25
+ const walkers = [];
26
+ const regexes = patterns.map((pattern) => typeof pattern === "string" ? new RegExp(pattern, "g") : pattern);
27
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
28
+ while (walker.nextNode()) {
29
+ const text = walker.currentNode;
30
+ let replaced = text.textContent ?? "";
31
+ for (const regex of regexes) {
32
+ replaced = replaced.replace(regex, "[redacted]");
33
+ }
34
+ if (replaced !== text.textContent) {
35
+ walkers.push({ node: text, previous: text.textContent ?? "" });
36
+ text.textContent = replaced;
37
+ }
38
+ }
39
+ return walkers;
40
+ }
41
+ function restoreText(changed) {
42
+ changed.forEach(({ node, previous }) => {
43
+ node.textContent = previous;
44
+ });
45
+ }
46
+ function createSelectionOverlay() {
47
+ return new Promise((resolve, reject) => {
48
+ const overlay = document.createElement("div");
49
+ overlay.setAttribute("data-bug-reporter-overlay", "true");
50
+ overlay.style.position = "fixed";
51
+ overlay.style.inset = "0";
52
+ overlay.style.background = "rgba(0,0,0,0.35)";
53
+ overlay.style.cursor = "crosshair";
54
+ overlay.style.zIndex = "2147483647";
55
+ const box = document.createElement("div");
56
+ box.style.position = "fixed";
57
+ box.style.border = "2px solid #ffffff";
58
+ box.style.background = "rgba(27, 116, 228, 0.2)";
59
+ box.style.pointerEvents = "none";
60
+ box.style.display = "none";
61
+ overlay.appendChild(box);
62
+ const cleanup = () => {
63
+ overlay.removeEventListener("mousedown", onMouseDown);
64
+ overlay.removeEventListener("mousemove", onMouseMove);
65
+ overlay.removeEventListener("mouseup", onMouseUp);
66
+ window.removeEventListener("keydown", onKeyDown);
67
+ overlay.remove();
68
+ };
69
+ let startX = 0;
70
+ let startY = 0;
71
+ let isDragging = false;
72
+ const onKeyDown = (event) => {
73
+ if (event.key === "Escape") {
74
+ cleanup();
75
+ reject(new chunk6TCI6T2U_cjs.BugReporterError("ABORTED", "Screenshot capture cancelled."));
76
+ }
77
+ };
78
+ const onMouseDown = (event) => {
79
+ isDragging = true;
80
+ startX = event.clientX;
81
+ startY = event.clientY;
82
+ box.style.display = "block";
83
+ box.style.left = `${startX}px`;
84
+ box.style.top = `${startY}px`;
85
+ box.style.width = "0px";
86
+ box.style.height = "0px";
87
+ };
88
+ const onMouseMove = (event) => {
89
+ if (!isDragging) {
90
+ return;
91
+ }
92
+ const left = Math.min(startX, event.clientX);
93
+ const top = Math.min(startY, event.clientY);
94
+ const width = Math.abs(startX - event.clientX);
95
+ const height = Math.abs(startY - event.clientY);
96
+ box.style.left = `${left}px`;
97
+ box.style.top = `${top}px`;
98
+ box.style.width = `${width}px`;
99
+ box.style.height = `${height}px`;
100
+ };
101
+ const onMouseUp = (event) => {
102
+ if (!isDragging) {
103
+ return;
104
+ }
105
+ isDragging = false;
106
+ const left = Math.min(startX, event.clientX);
107
+ const top = Math.min(startY, event.clientY);
108
+ const width = Math.abs(startX - event.clientX);
109
+ const height = Math.abs(startY - event.clientY);
110
+ cleanup();
111
+ if (width < 8 || height < 8) {
112
+ reject(new chunk6TCI6T2U_cjs.BugReporterError("CAPTURE_ERROR", "Selection area is too small."));
113
+ return;
114
+ }
115
+ resolve({ left, top, width, height });
116
+ };
117
+ overlay.addEventListener("mousedown", onMouseDown);
118
+ overlay.addEventListener("mousemove", onMouseMove);
119
+ overlay.addEventListener("mouseup", onMouseUp);
120
+ window.addEventListener("keydown", onKeyDown);
121
+ document.body.appendChild(overlay);
122
+ });
123
+ }
124
+ function canvasToBlob(canvas) {
125
+ return new Promise((resolve, reject) => {
126
+ canvas.toBlob((blob) => {
127
+ if (!blob) {
128
+ reject(new chunk6TCI6T2U_cjs.BugReporterError("CAPTURE_ERROR", "Failed to build screenshot blob."));
129
+ return;
130
+ }
131
+ resolve(blob);
132
+ }, "image/png");
133
+ });
134
+ }
135
+ async function captureScreenshotArea(options) {
136
+ const selection = await createSelectionOverlay();
137
+ const masked = applyMasking(options.maskSelectors);
138
+ const textChanges = scrubText(document.body, options.redactTextPatterns);
139
+ try {
140
+ const { default: html2canvas } = await import('html2canvas');
141
+ const baseCanvas = await html2canvas(document.documentElement, {
142
+ useCORS: true,
143
+ scrollX: -window.scrollX,
144
+ scrollY: -window.scrollY,
145
+ backgroundColor: null,
146
+ logging: false,
147
+ windowWidth: document.documentElement.scrollWidth,
148
+ windowHeight: document.documentElement.scrollHeight
149
+ });
150
+ const scaleX = baseCanvas.width / window.innerWidth;
151
+ const scaleY = baseCanvas.height / window.innerHeight;
152
+ const sx = Math.round(selection.left * scaleX);
153
+ const sy = Math.round(selection.top * scaleY);
154
+ const sw = Math.round(selection.width * scaleX);
155
+ const sh = Math.round(selection.height * scaleY);
156
+ const cropped = document.createElement("canvas");
157
+ cropped.width = sw;
158
+ cropped.height = sh;
159
+ const context = cropped.getContext("2d");
160
+ if (!context) {
161
+ throw new chunk6TCI6T2U_cjs.BugReporterError("CAPTURE_ERROR", "Canvas 2D context unavailable.");
162
+ }
163
+ context.drawImage(baseCanvas, sx, sy, sw, sh, 0, 0, sw, sh);
164
+ return await canvasToBlob(cropped);
165
+ } catch (error) {
166
+ if (error instanceof chunk6TCI6T2U_cjs.BugReporterError) {
167
+ throw error;
168
+ }
169
+ throw new chunk6TCI6T2U_cjs.BugReporterError("CAPTURE_ERROR", "Screenshot capture failed.", error);
170
+ } finally {
171
+ resetMasking(masked);
172
+ restoreText(textChanges);
173
+ }
174
+ }
175
+
176
+ exports.captureScreenshotArea = captureScreenshotArea;
177
+ //# sourceMappingURL=screenshot-FRAZAS6B.cjs.map
178
+ //# sourceMappingURL=screenshot-FRAZAS6B.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/screenshot.ts"],"names":["BugReporterError"],"mappings":"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","file":"screenshot-FRAZAS6B.cjs","sourcesContent":["import { BugReporterError } from \"../types\";\n\ntype CaptureOptions = {\n maskSelectors: string[];\n redactTextPatterns: Array<string | RegExp>;\n};\n\ntype SelectionRect = {\n left: number;\n top: number;\n width: number;\n height: number;\n};\n\nfunction applyMasking(selectors: string[]): Array<{ element: HTMLElement; previous: string }> {\n const masked: Array<{ element: HTMLElement; previous: string }> = [];\n for (const selector of selectors) {\n document.querySelectorAll<HTMLElement>(selector).forEach((element) => {\n masked.push({ element, previous: element.style.filter });\n element.style.filter = \"blur(12px)\";\n });\n }\n return masked;\n}\n\nfunction resetMasking(masked: Array<{ element: HTMLElement; previous: string }>): void {\n masked.forEach(({ element, previous }) => {\n element.style.filter = previous;\n });\n}\n\nfunction scrubText(root: HTMLElement, patterns: Array<string | RegExp>): Array<{ node: Text; previous: string }> {\n if (!patterns.length) {\n return [];\n }\n\n const walkers: Array<{ node: Text; previous: string }> = [];\n const regexes = patterns.map((pattern) => (typeof pattern === \"string\" ? new RegExp(pattern, \"g\") : pattern));\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const text = walker.currentNode as Text;\n let replaced = text.textContent ?? \"\";\n for (const regex of regexes) {\n replaced = replaced.replace(regex, \"[redacted]\");\n }\n if (replaced !== text.textContent) {\n walkers.push({ node: text, previous: text.textContent ?? \"\" });\n text.textContent = replaced;\n }\n }\n return walkers;\n}\n\nfunction restoreText(changed: Array<{ node: Text; previous: string }>): void {\n changed.forEach(({ node, previous }) => {\n node.textContent = previous;\n });\n}\n\nfunction createSelectionOverlay(): Promise<SelectionRect> {\n return new Promise((resolve, reject) => {\n const overlay = document.createElement(\"div\");\n overlay.setAttribute(\"data-bug-reporter-overlay\", \"true\");\n overlay.style.position = \"fixed\";\n overlay.style.inset = \"0\";\n overlay.style.background = \"rgba(0,0,0,0.35)\";\n overlay.style.cursor = \"crosshair\";\n overlay.style.zIndex = \"2147483647\";\n\n const box = document.createElement(\"div\");\n box.style.position = \"fixed\";\n box.style.border = \"2px solid #ffffff\";\n box.style.background = \"rgba(27, 116, 228, 0.2)\";\n box.style.pointerEvents = \"none\";\n box.style.display = \"none\";\n overlay.appendChild(box);\n\n const cleanup = () => {\n overlay.removeEventListener(\"mousedown\", onMouseDown);\n overlay.removeEventListener(\"mousemove\", onMouseMove);\n overlay.removeEventListener(\"mouseup\", onMouseUp);\n window.removeEventListener(\"keydown\", onKeyDown);\n overlay.remove();\n };\n\n let startX = 0;\n let startY = 0;\n let isDragging = false;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n cleanup();\n reject(new BugReporterError(\"ABORTED\", \"Screenshot capture cancelled.\"));\n }\n };\n\n const onMouseDown = (event: MouseEvent) => {\n isDragging = true;\n startX = event.clientX;\n startY = event.clientY;\n box.style.display = \"block\";\n box.style.left = `${startX}px`;\n box.style.top = `${startY}px`;\n box.style.width = \"0px\";\n box.style.height = \"0px\";\n };\n\n const onMouseMove = (event: MouseEvent) => {\n if (!isDragging) {\n return;\n }\n const left = Math.min(startX, event.clientX);\n const top = Math.min(startY, event.clientY);\n const width = Math.abs(startX - event.clientX);\n const height = Math.abs(startY - event.clientY);\n box.style.left = `${left}px`;\n box.style.top = `${top}px`;\n box.style.width = `${width}px`;\n box.style.height = `${height}px`;\n };\n\n const onMouseUp = (event: MouseEvent) => {\n if (!isDragging) {\n return;\n }\n isDragging = false;\n const left = Math.min(startX, event.clientX);\n const top = Math.min(startY, event.clientY);\n const width = Math.abs(startX - event.clientX);\n const height = Math.abs(startY - event.clientY);\n cleanup();\n if (width < 8 || height < 8) {\n reject(new BugReporterError(\"CAPTURE_ERROR\", \"Selection area is too small.\"));\n return;\n }\n resolve({ left, top, width, height });\n };\n\n overlay.addEventListener(\"mousedown\", onMouseDown);\n overlay.addEventListener(\"mousemove\", onMouseMove);\n overlay.addEventListener(\"mouseup\", onMouseUp);\n window.addEventListener(\"keydown\", onKeyDown);\n document.body.appendChild(overlay);\n });\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new BugReporterError(\"CAPTURE_ERROR\", \"Failed to build screenshot blob.\"));\n return;\n }\n resolve(blob);\n }, \"image/png\");\n });\n}\n\nexport async function captureScreenshotArea(options: CaptureOptions): Promise<Blob> {\n const selection = await createSelectionOverlay();\n const masked = applyMasking(options.maskSelectors);\n const textChanges = scrubText(document.body, options.redactTextPatterns);\n\n try {\n const { default: html2canvas } = await import(\"html2canvas\");\n const baseCanvas = await html2canvas(document.documentElement, {\n useCORS: true,\n scrollX: -window.scrollX,\n scrollY: -window.scrollY,\n backgroundColor: null,\n logging: false,\n windowWidth: document.documentElement.scrollWidth,\n windowHeight: document.documentElement.scrollHeight\n });\n\n const scaleX = baseCanvas.width / window.innerWidth;\n const scaleY = baseCanvas.height / window.innerHeight;\n\n const sx = Math.round(selection.left * scaleX);\n const sy = Math.round(selection.top * scaleY);\n const sw = Math.round(selection.width * scaleX);\n const sh = Math.round(selection.height * scaleY);\n\n const cropped = document.createElement(\"canvas\");\n cropped.width = sw;\n cropped.height = sh;\n\n const context = cropped.getContext(\"2d\");\n if (!context) {\n throw new BugReporterError(\"CAPTURE_ERROR\", \"Canvas 2D context unavailable.\");\n }\n\n context.drawImage(baseCanvas, sx, sy, sw, sh, 0, 0, sw, sh);\n return await canvasToBlob(cropped);\n } catch (error) {\n if (error instanceof BugReporterError) {\n throw error;\n }\n throw new BugReporterError(\"CAPTURE_ERROR\", \"Screenshot capture failed.\", error);\n } finally {\n resetMasking(masked);\n restoreText(textChanges);\n }\n}\n"]}