@guidelytech/guide-runtime 0.15.0 → 0.17.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 (46) hide show
  1. package/dist/bug-report.cjs +340 -0
  2. package/dist/bug-report.cjs.map +1 -0
  3. package/dist/bug-report.d.cts +38 -0
  4. package/dist/bug-report.d.ts +38 -0
  5. package/dist/bug-report.js +213 -0
  6. package/dist/bug-report.js.map +1 -0
  7. package/dist/capture.cjs +5 -0
  8. package/dist/capture.cjs.map +1 -1
  9. package/dist/capture.js +32 -420
  10. package/dist/capture.js.map +1 -1
  11. package/dist/{chunk-VJSQM23R.js → chunk-63M4VHQG.js} +6 -6
  12. package/dist/chunk-63M4VHQG.js.map +1 -0
  13. package/dist/{chunk-VQJ7MZ2K.js → chunk-EPR4LWA2.js} +2 -2
  14. package/dist/{chunk-H3CROHPR.js → chunk-QFKMXMM4.js} +2 -2
  15. package/dist/chunk-THGKUOHM.js +445 -0
  16. package/dist/chunk-THGKUOHM.js.map +1 -0
  17. package/dist/chunk-V6TY7KAL.js +8 -0
  18. package/dist/chunk-V6TY7KAL.js.map +1 -0
  19. package/dist/index.cjs +100 -24
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +11 -4
  22. package/dist/index.d.ts +11 -4
  23. package/dist/index.js +87 -15
  24. package/dist/index.js.map +1 -1
  25. package/dist/messenger.js +3 -2
  26. package/dist/messenger.js.map +1 -1
  27. package/package.json +9 -4
  28. package/dist/chunk-4FKYF6OB.js +0 -466
  29. package/dist/chunk-4FKYF6OB.js.map +0 -1
  30. package/dist/chunk-4UW6YF44.js +0 -464
  31. package/dist/chunk-4UW6YF44.js.map +0 -1
  32. package/dist/chunk-7DOGZKWD.js +0 -322
  33. package/dist/chunk-7DOGZKWD.js.map +0 -1
  34. package/dist/chunk-GUH2PQJ7.js +0 -462
  35. package/dist/chunk-GUH2PQJ7.js.map +0 -1
  36. package/dist/chunk-GUSKGNNR.js +0 -301
  37. package/dist/chunk-GUSKGNNR.js.map +0 -1
  38. package/dist/chunk-KJRXCITD.js +0 -515
  39. package/dist/chunk-KJRXCITD.js.map +0 -1
  40. package/dist/chunk-PG7CMBHD.js +0 -330
  41. package/dist/chunk-PG7CMBHD.js.map +0 -1
  42. package/dist/chunk-VJSQM23R.js.map +0 -1
  43. package/dist/chunk-VNJPNGZ4.js +0 -301
  44. package/dist/chunk-VNJPNGZ4.js.map +0 -1
  45. /package/dist/{chunk-VQJ7MZ2K.js.map → chunk-EPR4LWA2.js.map} +0 -0
  46. /package/dist/{chunk-H3CROHPR.js.map → chunk-QFKMXMM4.js.map} +0 -0
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/bug-report.ts
21
+ var bug_report_exports = {};
22
+ __export(bug_report_exports, {
23
+ readImageFile: () => readImageFile,
24
+ renderBugReport: () => renderBugReport
25
+ });
26
+ module.exports = __toCommonJS(bug_report_exports);
27
+ var import_runtime = require("@guidelytech/shared/runtime");
28
+
29
+ // src/capture-screen.ts
30
+ function canCaptureScreen() {
31
+ return typeof navigator !== "undefined" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === "function";
32
+ }
33
+ async function requestTabCapture(container, doc = document) {
34
+ if (!canCaptureScreen()) return { ok: false, reason: "unsupported" };
35
+ let stream;
36
+ try {
37
+ stream = await navigator.mediaDevices.getDisplayMedia({
38
+ video: { displaySurface: "browser", frameRate: 5 },
39
+ audio: false,
40
+ preferCurrentTab: true,
41
+ selfBrowserSurface: "include",
42
+ surfaceSwitching: "exclude"
43
+ });
44
+ } catch (err) {
45
+ return { ok: false, reason: err?.name === "NotAllowedError" ? "denied" : "error" };
46
+ }
47
+ const track = stream.getVideoTracks()[0];
48
+ if (!track) {
49
+ stream.getTracks().forEach((t) => t.stop());
50
+ return { ok: false, reason: "error" };
51
+ }
52
+ const surface = track.getSettings?.()?.displaySurface;
53
+ if (surface && surface !== "browser") {
54
+ stream.getTracks().forEach((t) => t.stop());
55
+ return { ok: false, reason: "wrong-surface" };
56
+ }
57
+ const video = doc.createElement("video");
58
+ video.muted = true;
59
+ video.playsInline = true;
60
+ video.setAttribute("aria-hidden", "true");
61
+ video.style.cssText = "position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;pointer-events:none";
62
+ try {
63
+ video.srcObject = stream;
64
+ container.appendChild(video);
65
+ } catch {
66
+ stream.getTracks().forEach((t) => t.stop());
67
+ return { ok: false, reason: "error" };
68
+ }
69
+ try {
70
+ await video.play();
71
+ if (!video.videoWidth) await new Promise((resolve) => {
72
+ const done = () => resolve();
73
+ video.addEventListener("loadeddata", done, { once: true });
74
+ setTimeout(done, 1500);
75
+ });
76
+ } catch {
77
+ }
78
+ let ended = false;
79
+ const listeners = [];
80
+ const fv = video;
81
+ const rvfc = typeof fv.requestVideoFrameCallback === "function";
82
+ let frameAt = 0;
83
+ let waiters = [];
84
+ const onFrame = (now) => {
85
+ frameAt = now;
86
+ waiters.splice(0).forEach((w) => w());
87
+ if (!ended) fv.requestVideoFrameCallback(onFrame);
88
+ };
89
+ if (rvfc) fv.requestVideoFrameCallback(onFrame);
90
+ const end = () => {
91
+ if (ended) return;
92
+ ended = true;
93
+ stream.getTracks().forEach((t) => t.stop());
94
+ video.srcObject = null;
95
+ video.remove();
96
+ listeners.splice(0).forEach((cb) => cb());
97
+ };
98
+ track.addEventListener("ended", end);
99
+ return {
100
+ ok: true,
101
+ screen: {
102
+ live: () => !ended && track.readyState === "live",
103
+ onEnded: (cb) => ended ? cb() : listeners.push(cb),
104
+ grab: () => ended ? null : grabFrame(video, doc),
105
+ freshSince: (changedAt) => rvfc ? frameAt > changedAt : performance.now() - changedAt > 250,
106
+ nextFrame: (timeoutMs = 500) => new Promise((resolve) => {
107
+ const t = setTimeout(done, rvfc ? timeoutMs : 250);
108
+ function done() {
109
+ clearTimeout(t);
110
+ waiters = waiters.filter((w) => w !== done);
111
+ resolve();
112
+ }
113
+ if (rvfc) waiters.push(done);
114
+ }),
115
+ stop: () => {
116
+ listeners.length = 0;
117
+ end();
118
+ }
119
+ }
120
+ };
121
+ }
122
+ function grabFrame(video, doc = document) {
123
+ const w = video.videoWidth;
124
+ const h = video.videoHeight;
125
+ if (!w || !h) return null;
126
+ const canvas = doc.createElement("canvas");
127
+ canvas.width = w;
128
+ canvas.height = h;
129
+ const ctx = canvas.getContext("2d");
130
+ if (!ctx) return null;
131
+ ctx.drawImage(video, 0, 0, w, h);
132
+ return canvas;
133
+ }
134
+
135
+ // src/bug-report.ts
136
+ var CSS = `
137
+ .g-br-bg{position:fixed;inset:0;z-index:var(--g-z,2147483000);background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:16px;font-family:var(--g-font,system-ui,sans-serif)}
138
+ .g-br{background:var(--g-bg,#fff);color:var(--g-text,#111827);border-radius:calc(var(--g-radius,8px) + 2px);box-shadow:0 20px 50px rgba(0,0,0,.25);width:min(440px,100%);max-height:calc(100vh - 32px);overflow:auto;padding:20px;box-sizing:border-box;font-size:14px;line-height:1.45}
139
+ .g-br h2{margin:0 0 4px;font-size:17px;font-weight:600}
140
+ .g-br p{margin:0;opacity:.75;font-size:13px}
141
+ .g-br label{display:block;margin:12px 0 4px;font-size:12px;font-weight:600}
142
+ .g-br input,.g-br textarea{width:100%;box-sizing:border-box;border:1px solid rgba(127,127,127,.4);border-radius:var(--g-radius,8px);padding:8px 10px;font:inherit;color:inherit;background:transparent}
143
+ .g-br textarea{min-height:110px;resize:vertical}
144
+ .g-br input:focus,.g-br textarea:focus,.g-br button:focus-visible{outline:2px solid var(--g-primary,#4f46e5);outline-offset:1px}
145
+ .g-br-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px}
146
+ .g-br-end{justify-content:flex-end;margin-top:16px}
147
+ .g-br button{font:inherit;font-size:13px;border-radius:var(--g-radius,8px);padding:7px 12px;cursor:pointer;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit}
148
+ .g-br button.g-br-primary{background:var(--g-primary,#4f46e5);border-color:var(--g-primary,#4f46e5);color:var(--g-primary-text,#fff)}
149
+ .g-br button:disabled{opacity:.6;cursor:default}
150
+ .g-br-shot{display:flex;gap:8px;align-items:center;margin-top:10px;font-size:12px}
151
+ .g-br-shot img{max-width:120px;max-height:72px;border-radius:4px;border:1px solid rgba(127,127,127,.3)}
152
+ .g-br-err{color:#dc2626;font-size:12px;margin-top:8px}
153
+ .g-br[data-theme=dark] .g-br-err,[data-theme=dark] .g-br-err{color:#f87171}
154
+ .g-br-ok{font-weight:600;margin:8px 0}
155
+ `;
156
+ var MAX_SIDE = 1920;
157
+ function el(doc, tag, attrs = {}, text) {
158
+ const e = doc.createElement(tag);
159
+ for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);
160
+ if (text !== void 0) e.textContent = text;
161
+ return e;
162
+ }
163
+ function canvasToScreenshot(canvas, doc) {
164
+ const scale = Math.min(1, MAX_SIDE / Math.max(canvas.width || 1, canvas.height || 1));
165
+ let src = canvas;
166
+ if (scale < 1) {
167
+ src = doc.createElement("canvas");
168
+ src.width = Math.max(1, Math.round(canvas.width * scale));
169
+ src.height = Math.max(1, Math.round(canvas.height * scale));
170
+ src.getContext("2d")?.drawImage(canvas, 0, 0, src.width, src.height);
171
+ }
172
+ for (const q of [0.85, 0.7, 0.5]) {
173
+ const url = src.toDataURL("image/jpeg", q);
174
+ const data = url.slice(url.indexOf(",") + 1);
175
+ if (data.length * 0.75 <= import_runtime.MAX_BUG_REPORT_SCREENSHOT_BYTES) return data;
176
+ }
177
+ return null;
178
+ }
179
+ async function readImageFile(doc, file) {
180
+ if (!/^image\/(png|jpeg|webp|gif)$/.test(file.type)) throw new Error("Attach a PNG, JPEG, WebP or GIF image.");
181
+ const asIs = await new Promise((resolve, reject) => {
182
+ const r = new FileReader();
183
+ r.onload = () => resolve(String(r.result));
184
+ r.onerror = () => reject(new Error("The image couldn't be read."));
185
+ r.readAsDataURL(file);
186
+ });
187
+ const data = asIs.slice(asIs.indexOf(",") + 1);
188
+ if (data.length * 0.75 <= import_runtime.MAX_BUG_REPORT_SCREENSHOT_BYTES) return { fileName: file.name || "screenshot", data };
189
+ const img = await new Promise((resolve, reject) => {
190
+ const i = new Image();
191
+ i.onload = () => resolve(i);
192
+ i.onerror = () => reject(new Error("The image couldn't be read."));
193
+ i.src = asIs;
194
+ });
195
+ const canvas = doc.createElement("canvas");
196
+ canvas.width = img.naturalWidth;
197
+ canvas.height = img.naturalHeight;
198
+ canvas.getContext("2d")?.drawImage(img, 0, 0);
199
+ const scaled = canvasToScreenshot(canvas, doc);
200
+ if (!scaled) throw new Error("That image is too large. Screenshots can be up to 4 MB.");
201
+ return { fileName: `${(file.name || "screenshot").replace(/\.[a-z0-9]+$/i, "")}.jpg`, data: scaled };
202
+ }
203
+ async function captureTab(doc, hide) {
204
+ hide(true);
205
+ try {
206
+ const res = await requestTabCapture(doc.body, doc);
207
+ if (!res.ok) throw new Error(res.reason === "denied" ? "Screen capture was cancelled." : res.reason === "wrong-surface" ? "Share this tab to capture it." : "This browser can't capture the page.");
208
+ try {
209
+ await res.screen.nextFrame(800);
210
+ await res.screen.nextFrame(800);
211
+ const canvas = res.screen.grab();
212
+ const data = canvas ? canvasToScreenshot(canvas, doc) : null;
213
+ if (!data) throw new Error("The page couldn't be captured.");
214
+ return { fileName: "screenshot.jpg", data };
215
+ } finally {
216
+ res.screen.stop();
217
+ }
218
+ } finally {
219
+ hide(false);
220
+ }
221
+ }
222
+ function renderBugReport(o) {
223
+ const doc = o.scope.ownerDocument;
224
+ const root = el(doc, "div");
225
+ root.appendChild(el(doc, "style", {}, CSS));
226
+ const bg = el(doc, "div", { class: "g-br-bg" });
227
+ const box = el(doc, "div", { class: "g-br", role: "dialog", "aria-modal": "true", "aria-labelledby": "g-br-title", "data-theme": o.scope.getAttribute("data-theme") ?? "light" });
228
+ box.appendChild(el(doc, "h2", { id: "g-br-title" }, o.title ?? "Report a bug"));
229
+ box.appendChild(el(doc, "p", {}, "Tell us what went wrong. The team sees the page you're on and what happened just before."));
230
+ const form = el(doc, "form", { novalidate: "" });
231
+ const field = (id, label, input) => {
232
+ form.appendChild(el(doc, "label", { for: id }, label));
233
+ input.id = id;
234
+ form.appendChild(input);
235
+ return input;
236
+ };
237
+ const message = field("g-br-message", "What happened?", el(doc, "textarea", { maxlength: String(import_runtime.MAX_BUG_REPORT_LENGTH), required: "" }));
238
+ message.value = o.message ?? "";
239
+ const name = field("g-br-name", "Name", el(doc, "input", { type: "text", maxlength: "200", autocomplete: "name" }));
240
+ name.value = o.name ?? "";
241
+ const email = field("g-br-email", "Email", el(doc, "input", { type: "email", maxlength: "254", autocomplete: "email" }));
242
+ email.value = o.email ?? "";
243
+ let screenshot = null;
244
+ const shotRow = el(doc, "div", { class: "g-br-row" });
245
+ const file = el(doc, "input", { type: "file", accept: "image/png,image/jpeg,image/webp,image/gif", hidden: "" });
246
+ const attach = el(doc, "button", { type: "button" }, "Attach an image");
247
+ shotRow.appendChild(attach);
248
+ const capture = canCaptureScreen() ? el(doc, "button", { type: "button" }, "Capture this page") : null;
249
+ if (capture) shotRow.appendChild(capture);
250
+ shotRow.appendChild(file);
251
+ form.appendChild(shotRow);
252
+ const preview = el(doc, "div", { class: "g-br-shot", hidden: "" });
253
+ form.appendChild(preview);
254
+ const error = el(doc, "div", { class: "g-br-err", role: "alert" });
255
+ form.appendChild(error);
256
+ const actions = el(doc, "div", { class: "g-br-row g-br-end" });
257
+ const cancel = el(doc, "button", { type: "button" }, "Cancel");
258
+ const send = el(doc, "button", { type: "submit", class: "g-br-primary" }, "Send report");
259
+ actions.append(cancel, send);
260
+ form.appendChild(actions);
261
+ box.appendChild(form);
262
+ bg.appendChild(box);
263
+ root.appendChild(bg);
264
+ o.scope.appendChild(root);
265
+ const previous = doc.activeElement;
266
+ setTimeout(() => message.focus(), 0);
267
+ const showShot = (s) => {
268
+ screenshot = s;
269
+ preview.replaceChildren();
270
+ preview.hidden = !s;
271
+ if (!s) return;
272
+ const img = el(doc, "img", { alt: "Screenshot preview" });
273
+ img.src = `data:image/${/\.png$/i.test(s.fileName) ? "png" : "jpeg"};base64,${s.data}`;
274
+ const remove = el(doc, "button", { type: "button" }, "Remove");
275
+ remove.addEventListener("click", () => showShot(null));
276
+ preview.append(img, el(doc, "span", {}, s.fileName), remove);
277
+ };
278
+ attach.addEventListener("click", () => file.click());
279
+ file.addEventListener("change", () => {
280
+ const f = file.files?.[0];
281
+ file.value = "";
282
+ if (!f) return;
283
+ error.textContent = "";
284
+ readImageFile(doc, f).then(showShot, (e) => error.textContent = e.message);
285
+ });
286
+ capture?.addEventListener("click", () => {
287
+ error.textContent = "";
288
+ captureTab(doc, (hidden) => bg.style.visibility = hidden ? "hidden" : "").then(showShot, (e) => error.textContent = e.message);
289
+ });
290
+ let closed = false;
291
+ const close = () => {
292
+ if (closed) return;
293
+ closed = true;
294
+ doc.removeEventListener("keydown", onKey, true);
295
+ root.remove();
296
+ previous?.focus?.();
297
+ o.onClose?.();
298
+ };
299
+ const onKey = (e) => {
300
+ if (e.key === "Escape") {
301
+ e.stopPropagation();
302
+ close();
303
+ }
304
+ };
305
+ doc.addEventListener("keydown", onKey, true);
306
+ cancel.addEventListener("click", close);
307
+ bg.addEventListener("click", (e) => e.target === bg && close());
308
+ form.addEventListener("submit", (e) => {
309
+ e.preventDefault();
310
+ error.textContent = "";
311
+ if (!message.value.trim()) {
312
+ error.textContent = "Describe what happened.";
313
+ message.focus();
314
+ return;
315
+ }
316
+ if (email.value.trim() && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.value.trim())) {
317
+ error.textContent = "Enter a valid email address, or leave it empty.";
318
+ email.focus();
319
+ return;
320
+ }
321
+ send.disabled = true;
322
+ o.onSubmit({ message: message.value.trim(), name: name.value.trim(), email: email.value.trim(), screenshot }).then(
323
+ () => {
324
+ form.replaceWith(el(doc, "p", { class: "g-br-ok", role: "status" }, "Thanks! Your report was sent to the team."));
325
+ setTimeout(close, 2500);
326
+ },
327
+ (err) => {
328
+ send.disabled = false;
329
+ error.textContent = err?.message || "Your report couldn't be sent. Please try again.";
330
+ }
331
+ );
332
+ });
333
+ return { close };
334
+ }
335
+ // Annotate the CommonJS export names for ESM import in node:
336
+ 0 && (module.exports = {
337
+ readImageFile,
338
+ renderBugReport
339
+ });
340
+ //# sourceMappingURL=bug-report.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bug-report.ts","../src/capture-screen.ts"],"sourcesContent":["import { MAX_BUG_REPORT_LENGTH, MAX_BUG_REPORT_SCREENSHOT_BYTES } from \"@guidelytech/shared/runtime\";\nimport { canCaptureScreen, requestTabCapture } from \"./capture-screen\";\n\n/**\n * \"Report a bug\": the dialog behind `guidely.showBugReport()` (its own on-demand chunk). The person\n * describes the problem and can add a screenshot, either an image file or a capture of this tab\n * (the browser's own share prompt; nothing is captured without it). Plain DOM in the SDK's shadow\n * scope; text is only ever set with textContent.\n */\n\nexport interface BugReportDialogInput {\n message: string;\n name: string;\n email: string;\n screenshot: { fileName: string; data: string } | null;\n}\n\nexport interface BugReportDialogOptions {\n /** The SDK's themed scope element (inside its shadow root). */\n scope: HTMLElement;\n /** Prefilled from identify() traits. */\n name?: string;\n email?: string;\n message?: string;\n title?: string;\n /** Sends the report; rejects with a message to show. */\n onSubmit: (input: BugReportDialogInput) => Promise<void>;\n onClose?: () => void;\n}\n\nexport interface BugReportDialog {\n close(): void;\n}\n\nconst CSS = `\n.g-br-bg{position:fixed;inset:0;z-index:var(--g-z,2147483000);background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:16px;font-family:var(--g-font,system-ui,sans-serif)}\n.g-br{background:var(--g-bg,#fff);color:var(--g-text,#111827);border-radius:calc(var(--g-radius,8px) + 2px);box-shadow:0 20px 50px rgba(0,0,0,.25);width:min(440px,100%);max-height:calc(100vh - 32px);overflow:auto;padding:20px;box-sizing:border-box;font-size:14px;line-height:1.45}\n.g-br h2{margin:0 0 4px;font-size:17px;font-weight:600}\n.g-br p{margin:0;opacity:.75;font-size:13px}\n.g-br label{display:block;margin:12px 0 4px;font-size:12px;font-weight:600}\n.g-br input,.g-br textarea{width:100%;box-sizing:border-box;border:1px solid rgba(127,127,127,.4);border-radius:var(--g-radius,8px);padding:8px 10px;font:inherit;color:inherit;background:transparent}\n.g-br textarea{min-height:110px;resize:vertical}\n.g-br input:focus,.g-br textarea:focus,.g-br button:focus-visible{outline:2px solid var(--g-primary,#4f46e5);outline-offset:1px}\n.g-br-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px}\n.g-br-end{justify-content:flex-end;margin-top:16px}\n.g-br button{font:inherit;font-size:13px;border-radius:var(--g-radius,8px);padding:7px 12px;cursor:pointer;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit}\n.g-br button.g-br-primary{background:var(--g-primary,#4f46e5);border-color:var(--g-primary,#4f46e5);color:var(--g-primary-text,#fff)}\n.g-br button:disabled{opacity:.6;cursor:default}\n.g-br-shot{display:flex;gap:8px;align-items:center;margin-top:10px;font-size:12px}\n.g-br-shot img{max-width:120px;max-height:72px;border-radius:4px;border:1px solid rgba(127,127,127,.3)}\n.g-br-err{color:#dc2626;font-size:12px;margin-top:8px}\n.g-br[data-theme=dark] .g-br-err,[data-theme=dark] .g-br-err{color:#f87171}\n.g-br-ok{font-weight:600;margin:8px 0}\n`;\n\nconst MAX_SIDE = 1920;\n\nfunction el<K extends keyof HTMLElementTagNameMap>(doc: Document, tag: K, attrs: Record<string, string> = {}, text?: string): HTMLElementTagNameMap[K] {\n const e = doc.createElement(tag);\n for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);\n if (text !== undefined) e.textContent = text;\n return e;\n}\n\n/** Base64 (no prefix) of a canvas as JPEG small enough to send; null when it can't fit. */\nfunction canvasToScreenshot(canvas: HTMLCanvasElement, doc: Document): string | null {\n const scale = Math.min(1, MAX_SIDE / Math.max(canvas.width || 1, canvas.height || 1));\n let src = canvas;\n if (scale < 1) {\n src = doc.createElement(\"canvas\");\n src.width = Math.max(1, Math.round(canvas.width * scale));\n src.height = Math.max(1, Math.round(canvas.height * scale));\n src.getContext(\"2d\")?.drawImage(canvas, 0, 0, src.width, src.height);\n }\n for (const q of [0.85, 0.7, 0.5]) {\n const url = src.toDataURL(\"image/jpeg\", q);\n const data = url.slice(url.indexOf(\",\") + 1);\n if (data.length * 0.75 <= MAX_BUG_REPORT_SCREENSHOT_BYTES) return data;\n }\n return null;\n}\n\n/** Reads a picked image, scaled down and re-encoded when it's too large. */\nexport async function readImageFile(doc: Document, file: File): Promise<{ fileName: string; data: string }> {\n if (!/^image\\/(png|jpeg|webp|gif)$/.test(file.type)) throw new Error(\"Attach a PNG, JPEG, WebP or GIF image.\");\n const asIs = await new Promise<string>((resolve, reject) => {\n const r = new FileReader();\n r.onload = () => resolve(String(r.result));\n r.onerror = () => reject(new Error(\"The image couldn't be read.\"));\n r.readAsDataURL(file);\n });\n const data = asIs.slice(asIs.indexOf(\",\") + 1);\n if (data.length * 0.75 <= MAX_BUG_REPORT_SCREENSHOT_BYTES) return { fileName: file.name || \"screenshot\", data };\n const img = await new Promise<HTMLImageElement>((resolve, reject) => {\n const i = new Image();\n i.onload = () => resolve(i);\n i.onerror = () => reject(new Error(\"The image couldn't be read.\"));\n i.src = asIs;\n });\n const canvas = doc.createElement(\"canvas\");\n canvas.width = img.naturalWidth;\n canvas.height = img.naturalHeight;\n canvas.getContext(\"2d\")?.drawImage(img, 0, 0);\n const scaled = canvasToScreenshot(canvas, doc);\n if (!scaled) throw new Error(\"That image is too large. Screenshots can be up to 4 MB.\");\n return { fileName: `${(file.name || \"screenshot\").replace(/\\.[a-z0-9]+$/i, \"\")}.jpg`, data: scaled };\n}\n\n/** One frame of this tab, after the browser's share prompt (the dialog hides while it's taken). */\nasync function captureTab(doc: Document, hide: (hidden: boolean) => void): Promise<{ fileName: string; data: string }> {\n hide(true);\n try {\n const res = await requestTabCapture(doc.body, doc);\n if (!res.ok) throw new Error(res.reason === \"denied\" ? \"Screen capture was cancelled.\" : res.reason === \"wrong-surface\" ? \"Share this tab to capture it.\" : \"This browser can't capture the page.\");\n try {\n await res.screen.nextFrame(800);\n await res.screen.nextFrame(800);\n const canvas = res.screen.grab();\n const data = canvas ? canvasToScreenshot(canvas, doc) : null;\n if (!data) throw new Error(\"The page couldn't be captured.\");\n return { fileName: \"screenshot.jpg\", data };\n } finally {\n res.screen.stop();\n }\n } finally {\n hide(false);\n }\n}\n\nexport function renderBugReport(o: BugReportDialogOptions): BugReportDialog {\n const doc = o.scope.ownerDocument;\n const root = el(doc, \"div\");\n root.appendChild(el(doc, \"style\", {}, CSS));\n const bg = el(doc, \"div\", { class: \"g-br-bg\" });\n const box = el(doc, \"div\", { class: \"g-br\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-labelledby\": \"g-br-title\", \"data-theme\": o.scope.getAttribute(\"data-theme\") ?? \"light\" });\n box.appendChild(el(doc, \"h2\", { id: \"g-br-title\" }, o.title ?? \"Report a bug\"));\n box.appendChild(el(doc, \"p\", {}, \"Tell us what went wrong. The team sees the page you're on and what happened just before.\"));\n const form = el(doc, \"form\", { novalidate: \"\" });\n const field = <T extends HTMLInputElement | HTMLTextAreaElement>(id: string, label: string, input: T) => {\n form.appendChild(el(doc, \"label\", { for: id }, label));\n input.id = id;\n form.appendChild(input);\n return input;\n };\n const message = field(\"g-br-message\", \"What happened?\", el(doc, \"textarea\", { maxlength: String(MAX_BUG_REPORT_LENGTH), required: \"\" }));\n message.value = o.message ?? \"\";\n const name = field(\"g-br-name\", \"Name\", el(doc, \"input\", { type: \"text\", maxlength: \"200\", autocomplete: \"name\" }));\n name.value = o.name ?? \"\";\n const email = field(\"g-br-email\", \"Email\", el(doc, \"input\", { type: \"email\", maxlength: \"254\", autocomplete: \"email\" }));\n email.value = o.email ?? \"\";\n\n let screenshot: { fileName: string; data: string } | null = null;\n const shotRow = el(doc, \"div\", { class: \"g-br-row\" });\n const file = el(doc, \"input\", { type: \"file\", accept: \"image/png,image/jpeg,image/webp,image/gif\", hidden: \"\" });\n const attach = el(doc, \"button\", { type: \"button\" }, \"Attach an image\");\n shotRow.appendChild(attach);\n const capture = canCaptureScreen() ? el(doc, \"button\", { type: \"button\" }, \"Capture this page\") : null;\n if (capture) shotRow.appendChild(capture);\n shotRow.appendChild(file);\n form.appendChild(shotRow);\n const preview = el(doc, \"div\", { class: \"g-br-shot\", hidden: \"\" });\n form.appendChild(preview);\n const error = el(doc, \"div\", { class: \"g-br-err\", role: \"alert\" });\n form.appendChild(error);\n const actions = el(doc, \"div\", { class: \"g-br-row g-br-end\" });\n const cancel = el(doc, \"button\", { type: \"button\" }, \"Cancel\");\n const send = el(doc, \"button\", { type: \"submit\", class: \"g-br-primary\" }, \"Send report\");\n actions.append(cancel, send);\n form.appendChild(actions);\n box.appendChild(form);\n bg.appendChild(box);\n root.appendChild(bg);\n o.scope.appendChild(root);\n const previous = doc.activeElement as HTMLElement | null;\n setTimeout(() => message.focus(), 0);\n\n const showShot = (s: { fileName: string; data: string } | null) => {\n screenshot = s;\n preview.replaceChildren();\n preview.hidden = !s;\n if (!s) return;\n const img = el(doc, \"img\", { alt: \"Screenshot preview\" });\n img.src = `data:image/${/\\.png$/i.test(s.fileName) ? \"png\" : \"jpeg\"};base64,${s.data}`;\n const remove = el(doc, \"button\", { type: \"button\" }, \"Remove\");\n remove.addEventListener(\"click\", () => showShot(null));\n preview.append(img, el(doc, \"span\", {}, s.fileName), remove);\n };\n attach.addEventListener(\"click\", () => file.click());\n file.addEventListener(\"change\", () => {\n const f = file.files?.[0];\n file.value = \"\";\n if (!f) return;\n error.textContent = \"\";\n readImageFile(doc, f).then(showShot, (e: Error) => (error.textContent = e.message));\n });\n capture?.addEventListener(\"click\", () => {\n error.textContent = \"\";\n captureTab(doc, (hidden) => (bg.style.visibility = hidden ? \"hidden\" : \"\")).then(showShot, (e: Error) => (error.textContent = e.message));\n });\n\n let closed = false;\n const close = () => {\n if (closed) return;\n closed = true;\n doc.removeEventListener(\"keydown\", onKey, true);\n root.remove();\n previous?.focus?.();\n o.onClose?.();\n };\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n close();\n }\n };\n doc.addEventListener(\"keydown\", onKey, true);\n cancel.addEventListener(\"click\", close);\n bg.addEventListener(\"click\", (e) => e.target === bg && close());\n form.addEventListener(\"submit\", (e) => {\n e.preventDefault();\n error.textContent = \"\";\n if (!message.value.trim()) {\n error.textContent = \"Describe what happened.\";\n message.focus();\n return;\n }\n if (email.value.trim() && !/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(email.value.trim())) {\n error.textContent = \"Enter a valid email address, or leave it empty.\";\n email.focus();\n return;\n }\n send.disabled = true;\n o.onSubmit({ message: message.value.trim(), name: name.value.trim(), email: email.value.trim(), screenshot }).then(\n () => {\n form.replaceWith(el(doc, \"p\", { class: \"g-br-ok\", role: \"status\" }, \"Thanks! Your report was sent to the team.\"));\n setTimeout(close, 2500);\n },\n (err: Error) => {\n send.disabled = false;\n error.textContent = err?.message || \"Your report couldn't be sent. Please try again.\";\n },\n );\n });\n return { close };\n}\n","import { toFrameRect, type Box } from \"./capture-dom\";\n\n/**\n * Screenshots for how-to capture: a tab-capture stream (`getDisplayMedia`) played into a hidden\n * video, frames grabbed synchronously into a canvas, sensitive regions blurred into the pixels and\n * the result encoded — all in the browser, so raw pixels of redacted regions never leave it.\n */\n\nexport type ScreenResult = { ok: true; screen: ScreenSource } | { ok: false; reason: \"unsupported\" | \"denied\" | \"wrong-surface\" | \"error\" };\n\nexport interface ScreenSource {\n /** True while the stream delivers frames. */\n live(): boolean;\n /** Called once when the user stops sharing (or the stream ends otherwise). */\n onEnded(cb: () => void): void;\n /** Draws the current frame into a new canvas (synchronously). Null when no frame is available. */\n grab(): HTMLCanvasElement | null;\n /** Does the current frame show the page as it is after `changedAt` (a `performance.now()` time)? */\n freshSince(changedAt: number): boolean;\n /** Resolves when the next frame arrives (or after `timeoutMs`). */\n nextFrame(timeoutMs?: number): Promise<void>;\n stop(): void;\n}\n\n/** Can this browser capture the tab at all? */\nexport function canCaptureScreen(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === \"function\";\n}\n\n/**\n * Asks the user to share this tab (must run from a user gesture) and plays the stream into a\n * hidden `<video>` appended to `container`. Rejects windows and screens: their frames do not\n * match the page's viewport, so highlights and redactions would land in the wrong place.\n */\nexport async function requestTabCapture(container: Node, doc: Document = document): Promise<ScreenResult> {\n if (!canCaptureScreen()) return { ok: false, reason: \"unsupported\" };\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n video: { displaySurface: \"browser\", frameRate: 5 },\n audio: false,\n preferCurrentTab: true,\n selfBrowserSurface: \"include\",\n surfaceSwitching: \"exclude\",\n } as DisplayMediaStreamOptions);\n } catch (err) {\n return { ok: false, reason: (err as { name?: string })?.name === \"NotAllowedError\" ? \"denied\" : \"error\" };\n }\n const track = stream.getVideoTracks()[0];\n if (!track) {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"error\" };\n }\n const surface = (track.getSettings?.() as { displaySurface?: string } | undefined)?.displaySurface;\n if (surface && surface !== \"browser\") {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"wrong-surface\" };\n }\n const video = doc.createElement(\"video\");\n video.muted = true;\n video.playsInline = true;\n video.setAttribute(\"aria-hidden\", \"true\");\n // Kept rendered (not display:none) so browsers keep decoding frames.\n video.style.cssText = \"position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;pointer-events:none\";\n try {\n video.srcObject = stream;\n container.appendChild(video);\n } catch {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"error\" };\n }\n try {\n await video.play();\n if (!video.videoWidth) await new Promise<void>((resolve) => {\n const done = () => resolve();\n video.addEventListener(\"loadeddata\", done, { once: true });\n setTimeout(done, 1500);\n });\n } catch {\n /* frames may still arrive; grab() returns null until they do */\n }\n let ended = false;\n const listeners: Array<() => void> = [];\n // Frame timing: `requestVideoFrameCallback` where supported, else assume one frame period (5 fps) of lag.\n type FrameVideo = HTMLVideoElement & { requestVideoFrameCallback?: (cb: (now: number) => void) => number };\n const fv = video as FrameVideo;\n const rvfc = typeof fv.requestVideoFrameCallback === \"function\";\n let frameAt = 0;\n let waiters: Array<() => void> = [];\n const onFrame = (now: number) => {\n frameAt = now;\n waiters.splice(0).forEach((w) => w());\n if (!ended) fv.requestVideoFrameCallback!(onFrame);\n };\n if (rvfc) fv.requestVideoFrameCallback!(onFrame);\n const end = () => {\n if (ended) return;\n ended = true;\n stream.getTracks().forEach((t) => t.stop());\n video.srcObject = null;\n video.remove();\n listeners.splice(0).forEach((cb) => cb());\n };\n track.addEventListener(\"ended\", end);\n return {\n ok: true,\n screen: {\n live: () => !ended && track.readyState === \"live\",\n onEnded: (cb) => (ended ? cb() : listeners.push(cb)),\n grab: () => (ended ? null : grabFrame(video, doc)),\n freshSince: (changedAt) => (rvfc ? frameAt > changedAt : performance.now() - changedAt > 250),\n nextFrame: (timeoutMs = 500) =>\n new Promise<void>((resolve) => {\n const t = setTimeout(done, rvfc ? timeoutMs : 250);\n function done() {\n clearTimeout(t);\n waiters = waiters.filter((w) => w !== done);\n resolve();\n }\n if (rvfc) waiters.push(done);\n }),\n stop: () => {\n listeners.length = 0;\n end();\n },\n },\n };\n}\n\n/** Copies the video's current frame into a new canvas. */\nexport function grabFrame(video: HTMLVideoElement, doc: Document = document): HTMLCanvasElement | null {\n const w = video.videoWidth;\n const h = video.videoHeight;\n if (!w || !h) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return null;\n ctx.drawImage(video, 0, 0, w, h);\n return canvas;\n}\n\n/**\n * Blurs viewport rects (CSS px) into the canvas by averaging each region down to a few samples per\n * line of text and scaling it back up, which leaves nothing legible to recover. Returns the number\n * of regions blurred.\n */\nexport function redactCanvas(canvas: HTMLCanvasElement, rects: Box[], viewport: { width: number; height: number }): number {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx || !rects.length || !viewport.width || !viewport.height) return 0;\n const sx = canvas.width / viewport.width;\n const sy = canvas.height / viewport.height;\n const tmp = canvas.ownerDocument.createElement(\"canvas\");\n const tctx = tmp.getContext(\"2d\");\n if (!tctx) return 0;\n let count = 0;\n for (const rect of rects) {\n const r = toFrameRect(rect, sx, sy, canvas, 4);\n if (!r) continue;\n // One sample per ~line height (at least 16 CSS px) in both directions.\n const block = Math.max(16 * sy, Math.min(r.height, 48 * sy));\n tmp.width = Math.max(1, Math.round(r.width / block));\n tmp.height = Math.max(1, Math.round(r.height / block));\n tctx.imageSmoothingEnabled = true;\n tctx.drawImage(canvas, r.x, r.y, r.width, r.height, 0, 0, tmp.width, tmp.height);\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(tmp, 0, 0, tmp.width, tmp.height, r.x, r.y, r.width, r.height);\n // A light tint so readers can tell the region was hidden on purpose.\n ctx.fillStyle = \"rgba(120,120,120,.28)\";\n ctx.fillRect(r.x, r.y, r.width, r.height);\n count++;\n }\n return count;\n}\n\nfunction scaled(canvas: HTMLCanvasElement, maxWidth: number): HTMLCanvasElement {\n if (canvas.width <= maxWidth) return canvas;\n const out = canvas.ownerDocument.createElement(\"canvas\");\n out.width = maxWidth;\n out.height = Math.round((canvas.height * maxWidth) / canvas.width);\n const ctx = out.getContext(\"2d\");\n if (!ctx) return canvas;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(canvas, 0, 0, out.width, out.height);\n return out;\n}\n\nconst toBlob = (canvas: HTMLCanvasElement, type: string, quality: number) =>\n new Promise<Blob | null>((resolve) => {\n try {\n canvas.toBlob((b) => resolve(b), type, quality);\n } catch {\n resolve(null);\n }\n });\n\n/** Encodes as WebP (JPEG where WebP encoding is unsupported), at most `maxWidth` wide and `maxBytes` big. */\nexport async function encodeScreenshot(canvas: HTMLCanvasElement, options: { maxWidth?: number; maxBytes?: number } = {}): Promise<Blob | null> {\n const src = scaled(canvas, options.maxWidth ?? 2560);\n const maxBytes = options.maxBytes ?? 8 * 1024 * 1024;\n let blob = await toBlob(src, \"image/webp\", 0.85);\n // Browsers without a WebP encoder silently return PNG.\n if (!blob || blob.type !== \"image/webp\") blob = await toBlob(src, \"image/jpeg\", 0.85);\n if (blob && blob.size > maxBytes) blob = await toBlob(src, \"image/jpeg\", 0.6);\n return blob && blob.size <= maxBytes ? blob : null;\n}\n\n/** Synchronous JPEG data URL (≤ 1600 px wide), for persisting an unsent screenshot while the page unloads. */\nexport function screenshotDataUrl(canvas: HTMLCanvasElement): string | null {\n try {\n return scaled(canvas, 1600).toDataURL(\"image/jpeg\", 0.7);\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * "Report a bug": the dialog behind `guidely.showBugReport()` (its own on-demand chunk). The person
3
+ * describes the problem and can add a screenshot, either an image file or a capture of this tab
4
+ * (the browser's own share prompt; nothing is captured without it). Plain DOM in the SDK's shadow
5
+ * scope; text is only ever set with textContent.
6
+ */
7
+ interface BugReportDialogInput {
8
+ message: string;
9
+ name: string;
10
+ email: string;
11
+ screenshot: {
12
+ fileName: string;
13
+ data: string;
14
+ } | null;
15
+ }
16
+ interface BugReportDialogOptions {
17
+ /** The SDK's themed scope element (inside its shadow root). */
18
+ scope: HTMLElement;
19
+ /** Prefilled from identify() traits. */
20
+ name?: string;
21
+ email?: string;
22
+ message?: string;
23
+ title?: string;
24
+ /** Sends the report; rejects with a message to show. */
25
+ onSubmit: (input: BugReportDialogInput) => Promise<void>;
26
+ onClose?: () => void;
27
+ }
28
+ interface BugReportDialog {
29
+ close(): void;
30
+ }
31
+ /** Reads a picked image, scaled down and re-encoded when it's too large. */
32
+ declare function readImageFile(doc: Document, file: File): Promise<{
33
+ fileName: string;
34
+ data: string;
35
+ }>;
36
+ declare function renderBugReport(o: BugReportDialogOptions): BugReportDialog;
37
+
38
+ export { type BugReportDialog, type BugReportDialogInput, type BugReportDialogOptions, readImageFile, renderBugReport };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * "Report a bug": the dialog behind `guidely.showBugReport()` (its own on-demand chunk). The person
3
+ * describes the problem and can add a screenshot, either an image file or a capture of this tab
4
+ * (the browser's own share prompt; nothing is captured without it). Plain DOM in the SDK's shadow
5
+ * scope; text is only ever set with textContent.
6
+ */
7
+ interface BugReportDialogInput {
8
+ message: string;
9
+ name: string;
10
+ email: string;
11
+ screenshot: {
12
+ fileName: string;
13
+ data: string;
14
+ } | null;
15
+ }
16
+ interface BugReportDialogOptions {
17
+ /** The SDK's themed scope element (inside its shadow root). */
18
+ scope: HTMLElement;
19
+ /** Prefilled from identify() traits. */
20
+ name?: string;
21
+ email?: string;
22
+ message?: string;
23
+ title?: string;
24
+ /** Sends the report; rejects with a message to show. */
25
+ onSubmit: (input: BugReportDialogInput) => Promise<void>;
26
+ onClose?: () => void;
27
+ }
28
+ interface BugReportDialog {
29
+ close(): void;
30
+ }
31
+ /** Reads a picked image, scaled down and re-encoded when it's too large. */
32
+ declare function readImageFile(doc: Document, file: File): Promise<{
33
+ fileName: string;
34
+ data: string;
35
+ }>;
36
+ declare function renderBugReport(o: BugReportDialogOptions): BugReportDialog;
37
+
38
+ export { type BugReportDialog, type BugReportDialogInput, type BugReportDialogOptions, readImageFile, renderBugReport };