@qaiddev/thumbs-embed 1.6.2 → 1.8.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 (37) hide show
  1. package/README.md +32 -21
  2. package/dist/bootstrap.d.ts +6 -0
  3. package/dist/chunks/{annotate-NeITyYNe.js → annotate-D-o8xCV9.js} +24 -17
  4. package/dist/chunks/annotate-D-o8xCV9.js.map +1 -0
  5. package/dist/chunks/dom-DvdtcUS8.js +17 -0
  6. package/dist/chunks/dom-DvdtcUS8.js.map +1 -0
  7. package/dist/chunks/{loader-DnJ3MHkt.js → loader-DHtXHt1z.js} +187 -163
  8. package/dist/chunks/loader-DHtXHt1z.js.map +1 -0
  9. package/dist/chunks/modal-CTbqN-h9.js +213 -0
  10. package/dist/chunks/modal-CTbqN-h9.js.map +1 -0
  11. package/dist/chunks/{recording-DhRZ6-tW.js → recording-Dx8AX-BC.js} +104 -98
  12. package/dist/chunks/recording-Dx8AX-BC.js.map +1 -0
  13. package/dist/chunks/{screenshot-BkOCLuS2.js → screenshot-DYOKg2bS.js} +2 -2
  14. package/dist/chunks/{screenshot-BkOCLuS2.js.map → screenshot-DYOKg2bS.js.map} +1 -1
  15. package/dist/chunks/{targeting-BEQU2Ogo.js → targeting-Dz6z1BYl.js} +3 -2
  16. package/dist/chunks/{targeting-BEQU2Ogo.js.map → targeting-Dz6z1BYl.js.map} +1 -1
  17. package/dist/chunks/{video-BOYqf2Im.js → video-Bd_29-rK.js} +76 -71
  18. package/dist/chunks/video-Bd_29-rK.js.map +1 -0
  19. package/dist/embed.d.ts +11 -0
  20. package/dist/icons.d.ts +2 -0
  21. package/dist/index.js +597 -451
  22. package/dist/index.js.map +1 -1
  23. package/dist/loader.js +1 -1
  24. package/dist/modal.d.ts +19 -1
  25. package/dist/qaid.umd.cjs +14 -31
  26. package/dist/qaid.umd.cjs.map +1 -1
  27. package/dist/recording.d.ts +6 -0
  28. package/dist/screenshot.d.ts +1 -1
  29. package/dist/types.d.ts +33 -2
  30. package/dist/video-capture.d.ts +6 -0
  31. package/package.json +1 -1
  32. package/dist/chunks/annotate-NeITyYNe.js.map +0 -1
  33. package/dist/chunks/loader-DnJ3MHkt.js.map +0 -1
  34. package/dist/chunks/modal-C8Ca7E5j.js +0 -119
  35. package/dist/chunks/modal-C8Ca7E5j.js.map +0 -1
  36. package/dist/chunks/recording-DhRZ6-tW.js.map +0 -1
  37. package/dist/chunks/video-BOYqf2Im.js.map +0 -1
@@ -1,81 +1,70 @@
1
- import { b as v } from "./annotate-NeITyYNe.js";
2
- const g = document;
3
- function r(n, t, ...o) {
4
- const e = g.createElement(n);
5
- if (t) {
6
- if (t.class && (e.className = t.class), t.style && (e.style.cssText = t.style), t.html != null && (e.innerHTML = t.html), t.text != null && (e.textContent = t.text), t.attrs)
7
- for (const i in t.attrs) e.setAttribute(i, t.attrs[i]);
8
- if (t.on)
9
- for (const i in t.on) e.addEventListener(i, t.on[i]);
10
- }
11
- for (const i of o)
12
- i != null && i !== !1 && e.append(i);
13
- return e;
14
- }
15
- const R = 20, u = 4096;
16
- function l(n) {
17
- const t = typeof n == "string" ? n : JSON.stringify(n);
1
+ import { h as r } from "./dom-DvdtcUS8.js";
2
+ import { b as f } from "./annotate-D-o8xCV9.js";
3
+ const g = 20, u = 4096;
4
+ function l(d) {
5
+ const t = typeof d == "string" ? d : JSON.stringify(d);
18
6
  return t.length > u ? t.slice(0, u) + "…[truncated]" : t;
19
7
  }
20
- function f(n, t) {
21
- n.length >= R && n.shift(), n.push(t);
8
+ function p(d, t) {
9
+ d.length >= g && d.shift(), d.push(t);
22
10
  }
23
- async function b(n) {
11
+ async function b(d) {
24
12
  try {
25
- const t = await n.clone().text();
13
+ const t = await d.clone().text();
26
14
  return l(t);
27
15
  } catch {
28
16
  return;
29
17
  }
30
18
  }
31
- function y() {
32
- const n = [], t = window.fetch;
33
- window.fetch = async function(i, s) {
34
- const d = typeof i == "string" ? i : i instanceof URL ? i.toString() : i.url, h = s?.method ?? (typeof i == "object" && "method" in i ? i.method : "GET");
35
- let c;
36
- s?.body && (c = l(s.body));
37
- const a = await t.apply(window, [i, s]);
38
- if (a.status >= 400) {
39
- const p = await b(a);
40
- f(n, {
41
- url: d,
42
- method: h.toUpperCase(),
43
- status: a.status,
44
- statusText: a.statusText,
45
- requestBody: c,
46
- responseBody: p,
19
+ function R() {
20
+ const d = [], t = window.fetch;
21
+ window.fetch = async function(o, s) {
22
+ const n = typeof o == "string" ? o : o instanceof URL ? o.toString() : o.url, c = s?.method ?? (typeof o == "object" && "method" in o ? o.method : "GET");
23
+ let a;
24
+ s?.body && (a = l(s.body));
25
+ const h = await t.apply(window, [o, s]);
26
+ if (h.status >= 400) {
27
+ const v = await b(h);
28
+ p(d, {
29
+ url: n,
30
+ method: c.toUpperCase(),
31
+ status: h.status,
32
+ statusText: h.statusText,
33
+ requestBody: a,
34
+ responseBody: v,
47
35
  timestamp: Date.now()
48
36
  });
49
37
  }
50
- return a;
38
+ return h;
51
39
  };
52
- const o = XMLHttpRequest.prototype.open, e = XMLHttpRequest.prototype.send;
53
- return XMLHttpRequest.prototype.open = function(i, s, ...d) {
54
- return this._qaid_method = i, this._qaid_url = typeof s == "string" ? s : s.toString(), o.apply(this, [i, s, ...d]);
55
- }, XMLHttpRequest.prototype.send = function(i) {
56
- const s = this, d = i ? l(i) : void 0;
40
+ const i = XMLHttpRequest.prototype.open, e = XMLHttpRequest.prototype.send;
41
+ return XMLHttpRequest.prototype.open = function(o, s, ...n) {
42
+ return this._qaid_method = o, this._qaid_url = typeof s == "string" ? s : s.toString(), i.apply(this, [o, s, ...n]);
43
+ }, XMLHttpRequest.prototype.send = function(o) {
44
+ const s = this, n = o ? l(o) : void 0;
57
45
  return s.addEventListener("load", function() {
58
- s.status >= 400 && f(n, {
46
+ s.status >= 400 && p(d, {
59
47
  url: s._qaid_url,
60
48
  method: s._qaid_method.toUpperCase(),
61
49
  status: s.status,
62
50
  statusText: s.statusText,
63
- requestBody: d,
51
+ requestBody: n,
64
52
  responseBody: l(s.responseText),
65
53
  timestamp: Date.now()
66
54
  });
67
- }), e.apply(this, [i]);
55
+ }), e.apply(this, [o]);
68
56
  }, {
69
- errors: n,
57
+ errors: d,
70
58
  restore: () => {
71
- window.fetch = t, XMLHttpRequest.prototype.open = o, XMLHttpRequest.prototype.send = e;
59
+ window.fetch = t, XMLHttpRequest.prototype.open = i, XMLHttpRequest.prototype.send = e;
72
60
  }
73
61
  };
74
62
  }
75
- class w {
63
+ class m {
76
64
  constructor(t) {
77
65
  this.host = t;
78
66
  }
67
+ host;
79
68
  videoRecorder = null;
80
69
  networkCapture = null;
81
70
  recordedBlob = null;
@@ -98,9 +87,9 @@ class w {
98
87
  async startRecording(t = []) {
99
88
  if (!(this.isRecording || this.host.state !== "IDLE"))
100
89
  try {
101
- this.networkCapture = y();
102
- const { createVideoRecorder: o } = await import("./video-BOYqf2Im.js");
103
- this.videoRecorder = o({
90
+ this.networkCapture = R();
91
+ const { createVideoRecorder: i } = await import("./video-Bd_29-rK.js");
92
+ this.videoRecorder = i({
104
93
  maxDuration: this.host.config.videoOptions.maxDuration,
105
94
  redactionElements: t
106
95
  }), this.videoRecorder.onTick((e) => {
@@ -108,8 +97,8 @@ class w {
108
97
  }), this.videoRecorder.onStop((e) => {
109
98
  this.isRecording && (this.recordedBlob = e, this.isRecording = !1, this.host.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.host.boundKeyDown), this.host.setButtonsDisabled(!1), e && e.size > 0 ? this.showRecordingPreview() : this.cleanupRecording());
110
99
  }), await this.videoRecorder.start(), this.isRecording = !0, this.host.announceMsg("Recording started"), this.host.setButtonsDisabled(!0), this.showRecordingIndicator(), document.addEventListener("keydown", this.host.boundKeyDown);
111
- } catch (o) {
112
- o instanceof Error && /current tab/i.test(o.message) && this.host.announceMsg(o.message), this.cleanupRecording();
100
+ } catch (i) {
101
+ i instanceof Error && /current tab|cannot blur/i.test(i.message) && this.host.announceMsg(i.message), this.cleanupRecording();
113
102
  }
114
103
  }
115
104
  async stopRecording() {
@@ -129,10 +118,10 @@ class w {
129
118
  startPicking() {
130
119
  if (this.isRecording || this.host.state !== "IDLE") return;
131
120
  this.host.setState("REDACT_PICKING"), this.redactPicks = [];
132
- const t = this.host.ensureOverlayHost(), o = r("div", { style: "position:absolute;inset:0;" }), e = r(
121
+ const t = this.host.ensureOverlayHost(), i = r("div", { style: "position:absolute;inset:0;" }), e = r(
133
122
  "div",
134
123
  { class: "qaid-redact-picker", style: "position:fixed;inset:0;pointer-events:none;" },
135
- o,
124
+ i,
136
125
  // Control bar — the only pointer-interactive part of the overlay.
137
126
  r(
138
127
  "div",
@@ -158,27 +147,27 @@ class w {
158
147
  )
159
148
  );
160
149
  t.appendChild(e), this.redactPickerRoot = e;
161
- const i = () => {
162
- this.renderRedactPicks(o), this.redactRafId = requestAnimationFrame(i);
150
+ const o = () => {
151
+ this.renderRedactPicks(i), this.redactRafId = requestAnimationFrame(o);
163
152
  };
164
- i(), document.addEventListener("click", this.boundRedactClick, !0), document.addEventListener("keydown", this.host.boundKeyDown), this.host.announceMsg("Click sensitive areas to blur, then start recording");
153
+ o(), document.addEventListener("click", this.boundRedactClick, !0), document.addEventListener("keydown", this.host.boundKeyDown), this.host.announceMsg("Click sensitive areas to blur, then start recording");
165
154
  }
166
155
  handleRedactPickClick(t) {
167
- const o = t.target instanceof Element ? t.target : null;
168
- if (!o || v(o) || this.redactPickerRoot?.contains(o)) return;
156
+ const i = t.target instanceof Element ? t.target : null;
157
+ if (!i || f(i) || this.redactPickerRoot?.contains(i)) return;
169
158
  t.preventDefault(), t.stopPropagation();
170
- const e = this.redactPicks.indexOf(o);
171
- e >= 0 ? this.redactPicks.splice(e, 1) : this.redactPicks.push(o);
172
- const i = this.redactPickerRoot?.querySelector("[data-qaid-redact-label]");
173
- if (i) {
159
+ const e = this.redactPicks.indexOf(i);
160
+ e >= 0 ? this.redactPicks.splice(e, 1) : this.redactPicks.push(i);
161
+ const o = this.redactPickerRoot?.querySelector("[data-qaid-redact-label]");
162
+ if (o) {
174
163
  const s = this.redactPicks.length;
175
- i.textContent = s === 0 ? "Click sensitive areas to blur" : `${s} area${s === 1 ? "" : "s"} will be blurred`;
164
+ o.textContent = s === 0 ? "Click sensitive areas to blur" : `${s} area${s === 1 ? "" : "s"} will be blurred`;
176
165
  }
177
166
  }
178
167
  renderRedactPicks(t) {
179
168
  t.textContent = "";
180
- for (const o of this.redactPicks) {
181
- const e = o.getBoundingClientRect();
169
+ for (const i of this.redactPicks) {
170
+ const e = i.getBoundingClientRect();
182
171
  e.width <= 0 || e.height <= 0 || t.appendChild(
183
172
  r("div", {
184
173
  style: `position:fixed;left:${e.left}px;top:${e.top}px;width:${e.width}px;height:${e.height}px;border:2px solid #ff6b6b;border-radius:4px;background:rgba(255,107,107,.18);pointer-events:none;`
@@ -188,8 +177,8 @@ class w {
188
177
  }
189
178
  finishRedactionPicking(t) {
190
179
  this.redactRafId && cancelAnimationFrame(this.redactRafId), this.redactRafId = 0, document.removeEventListener("click", this.boundRedactClick, !0), document.removeEventListener("keydown", this.host.boundKeyDown), this.redactPickerRoot && (this.redactPickerRoot.remove(), this.redactPickerRoot = null);
191
- const o = this.redactPicks;
192
- this.redactPicks = [], this.host.setState("IDLE"), t && this.startRecording(o);
180
+ const i = this.redactPicks;
181
+ this.redactPicks = [], this.host.setState("IDLE"), t && this.startRecording(i);
193
182
  }
194
183
  showRecordingIndicator() {
195
184
  const t = this.host.ensureOverlayHost();
@@ -211,38 +200,38 @@ class w {
211
200
  }
212
201
  updateRecordingTimer(t) {
213
202
  if (!this.recordingIndicator) return;
214
- const o = this.recordingIndicator.querySelector(".qaid-recording-time");
215
- if (o) {
203
+ const i = this.recordingIndicator.querySelector(".qaid-recording-time");
204
+ if (i) {
216
205
  const e = Math.max(0, this.host.config.videoOptions.maxDuration - t);
217
- o.textContent = this.formatTime(e), e > 0 && e <= 5 && this.host.announceMsg(`${e} second${e === 1 ? "" : "s"} remaining`);
206
+ i.textContent = this.formatTime(e), e > 0 && e <= 5 && this.host.announceMsg(`${e} second${e === 1 ? "" : "s"} remaining`);
218
207
  }
219
208
  }
220
209
  formatTime(t) {
221
- const o = Math.floor(t / 60), e = t % 60;
222
- return `${o}:${e.toString().padStart(2, "0")}`;
210
+ const i = Math.floor(t / 60), e = t % 60;
211
+ return `${i}:${e.toString().padStart(2, "0")}`;
223
212
  }
224
213
  removeRecordingIndicator() {
225
214
  this.recordingIndicator && (this.recordingIndicator.remove(), this.recordingIndicator = null);
226
215
  }
227
216
  showRecordingPreview() {
228
- const t = this.host.ensureOverlayHost(), o = URL.createObjectURL(this.recordedBlob), e = `qaid-video-title-${this.host.uid}`, i = r("h3", { text: "Review your recording", attrs: { id: e } }), s = r("video");
229
- s.src = o, s.controls = !0, s.autoplay = !0, s.muted = !0;
230
- const d = r("textarea", {
217
+ const t = this.host.ensureOverlayHost(), i = URL.createObjectURL(this.recordedBlob), e = `qaid-video-title-${this.host.uid}`, o = r("h3", { text: "Review your recording", attrs: { id: e } }), s = r("video");
218
+ s.src = i, s.controls = !0, s.autoplay = !0, s.muted = !0;
219
+ const n = r("textarea", {
231
220
  attrs: {
232
221
  placeholder: "Optional: Describe the issue you recorded...",
233
222
  "aria-label": "Describe the issue you recorded"
234
223
  }
235
- }), h = r("button", {
224
+ }), c = r("button", {
236
225
  class: "qaid-video-btn qaid-video-btn-send",
237
226
  text: "Send",
238
227
  attrs: { type: "button" },
239
- on: { click: () => this.submitVideoFeedback(d.value.trim() || null, h) }
240
- }), c = r(
228
+ on: { click: () => this.submitVideoFeedback(n.value.trim() || null, c) }
229
+ }), a = r(
241
230
  "div",
242
231
  { class: "qaid-video-preview-box" },
243
- i,
232
+ o,
244
233
  s,
245
- d,
234
+ n,
246
235
  r(
247
236
  "div",
248
237
  { class: "qaid-video-preview-actions" },
@@ -262,14 +251,14 @@ class w {
262
251
  }
263
252
  }
264
253
  }),
265
- h
254
+ c
266
255
  )
267
256
  );
268
257
  this.videoPreview = r(
269
258
  "div",
270
259
  { class: "qaid-video-preview", style: `z-index:${this.host.config.zIndex + 100}` },
271
- c
272
- ), this.host.applyVars(this.videoPreview), this.host.overlayShadowHost && (this.host.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.videoPreview), this.host.openDialogA11y(c, { labelledbyId: i.id }), document.addEventListener("keydown", this.host.boundKeyDown);
260
+ a
261
+ ), this.host.applyVars(this.videoPreview), this.host.overlayShadowHost && (this.host.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.videoPreview), this.host.openDialogA11y(a, { labelledbyId: o.id }), document.addEventListener("keydown", this.host.boundKeyDown);
273
262
  }
274
263
  cancelRecordingPreview() {
275
264
  this.removeVideoPreview(), this.cleanupRecording();
@@ -281,35 +270,52 @@ class w {
281
270
  }
282
271
  document.removeEventListener("keydown", this.host.boundKeyDown);
283
272
  }
284
- async submitVideoFeedback(t, o) {
273
+ async submitVideoFeedback(t, i) {
285
274
  if (!this.recordedBlob || this.isSendingVideo) return;
286
- this.isSendingVideo = !0, o.disabled = !0, o.textContent = "Sending...";
275
+ this.isSendingVideo = !0, i.disabled = !0, i.textContent = "Sending...";
287
276
  const e = new FormData();
288
277
  e.append("video", this.recordedBlob, `recording.${this.recordedBlob.type.includes("mp4") ? "mp4" : "webm"}`), e.append("pageUrl", window.location.href), e.append("visitorId", this.host.visitorId), this.host.config.apiKey && e.append("apiKey", this.host.config.apiKey), t && e.append("message", t), this.host.consoleCapture && e.append("consoleErrors", JSON.stringify(this.host.consoleCapture.errors)), this.networkCapture && e.append("networkErrors", JSON.stringify(this.networkCapture.errors));
289
- let i = null;
278
+ let o = null, s = null;
290
279
  try {
291
- const s = await fetch(`${this.host.config.endpoint}/video`, {
280
+ const n = await fetch(`${this.host.config.endpoint}/video`, {
292
281
  method: "POST",
293
282
  body: e
294
283
  });
295
- if (s.ok) {
284
+ if (n.ok) {
296
285
  this.host.announceMsg("Recording sent");
297
286
  try {
298
- i = (await s.json())?.id ?? null;
287
+ o = (await n.json())?.id ?? null;
299
288
  } catch {
300
289
  }
301
290
  } else
302
- console.error("Failed to submit video feedback:", await s.text()), this.host.announceMsg("Failed to send recording", !0);
303
- } catch (s) {
304
- console.error("Failed to submit video feedback:", s), this.host.announceMsg("Failed to send recording", !0);
291
+ console.error("Failed to submit video feedback:", await n.text()), s = n.status === 413 ? "This recording is too large to send. Try a shorter one." : "Your recording could not be sent.";
292
+ } catch (n) {
293
+ console.error("Failed to submit video feedback:", n), s = "Your recording could not be sent. Check your connection and try again.";
294
+ }
295
+ if (this.isSendingVideo = !1, s) {
296
+ this.showSendError(s, i);
297
+ return;
298
+ }
299
+ this.removeVideoPreview(), this.cleanupRecording(), await this.host.tryLaunchQuest("video", o);
300
+ }
301
+ /**
302
+ * Keep the preview open and say the upload failed. Closing it, as a success
303
+ * does, told the visitor their recording had arrived when it had not. Send
304
+ * is re-enabled for another try; Cancel and Re-record still work.
305
+ */
306
+ showSendError(t, i) {
307
+ this.host.announceMsg(`Failed to send recording. ${t}`, !0), i.disabled = !1, i.textContent = "Send";
308
+ const e = this.videoPreview?.querySelector(".qaid-video-preview-actions");
309
+ if (e) {
310
+ let o = this.videoPreview.querySelector(".qaid-video-error");
311
+ o || (o = r("p", { class: "qaid-video-error" }), e.before(o)), o.textContent = t;
305
312
  }
306
- this.isSendingVideo = !1, this.removeVideoPreview(), this.cleanupRecording(), await this.host.tryLaunchQuest("video", i);
307
313
  }
308
314
  cleanupRecording() {
309
315
  this.isRecording = !1, this.removeRecordingIndicator(), this.videoRecorder && (this.videoRecorder.destroy(), this.videoRecorder = null), this.networkCapture && (this.networkCapture.restore(), this.networkCapture = null), this.recordedBlob && (this.recordedBlob = null), this.host.setButtonsDisabled(!1), this.host.overlayShadowHost && this.host.state === "IDLE" && (this.host.overlayShadowHost.style.pointerEvents = "none");
310
316
  }
311
317
  }
312
318
  export {
313
- w as RecordingController
319
+ m as RecordingController
314
320
  };
315
- //# sourceMappingURL=recording-DhRZ6-tW.js.map
321
+ //# sourceMappingURL=recording-Dx8AX-BC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-Dx8AX-BC.js","sources":["../../src/network-capture.ts","../../src/recording.ts"],"sourcesContent":["/**\n * Network capture utilities\n * Intercepts fetch() and XMLHttpRequest to capture 4xx/5xx errors\n */\n\nimport type { NetworkError } from \"./types\";\n\nconst MAX_ENTRIES = 20;\nconst MAX_BODY_SIZE = 4096; // 4KB\n\nexport interface NetworkCapture {\n errors: NetworkError[];\n restore: () => void;\n}\n\n/**\n * Truncate a body to max size. Callers must guard against null/undefined.\n */\nexport function truncateBody(body: unknown): string {\n const str = typeof body === \"string\" ? body : JSON.stringify(body);\n if (str.length > MAX_BODY_SIZE) {\n return str.slice(0, MAX_BODY_SIZE) + \"…[truncated]\";\n }\n return str;\n}\n\n/**\n * Add an error to the buffer, evicting oldest if at capacity\n */\nexport function addEntry(errors: NetworkError[], entry: NetworkError): void {\n if (errors.length >= MAX_ENTRIES) {\n errors.shift();\n }\n errors.push(entry);\n}\n\n/**\n * Read response body as text, with size limit\n */\nasync function safeReadBody(response: Response): Promise<string | undefined> {\n try {\n const text = await response.clone().text();\n return truncateBody(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Intercept fetch() and XMLHttpRequest to capture network errors (4xx/5xx)\n */\nexport function captureNetworkErrors(): NetworkCapture {\n const errors: NetworkError[] = [];\n const originalFetch = window.fetch;\n\n // Monkey-patch fetch\n window.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.toString() : input.url;\n const method = init?.method ?? (typeof input === \"object\" && \"method\" in input ? input.method : \"GET\");\n let requestBody: string | undefined;\n if (init?.body) {\n requestBody = truncateBody(init.body);\n }\n\n const response = await originalFetch.apply(window, [input, init]);\n\n if (response.status >= 400) {\n const responseBody = await safeReadBody(response);\n addEntry(errors, {\n url,\n method: method.toUpperCase(),\n status: response.status,\n statusText: response.statusText,\n requestBody,\n responseBody,\n timestamp: Date.now(),\n });\n }\n\n return response;\n };\n\n // Monkey-patch XMLHttpRequest\n const originalXHROpen = XMLHttpRequest.prototype.open;\n const originalXHRSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL, ...rest: unknown[]) {\n (this as XMLHttpRequest & { _qaid_method: string; _qaid_url: string })._qaid_method = method;\n (this as XMLHttpRequest & { _qaid_url: string })._qaid_url = typeof url === \"string\" ? url : url.toString();\n return originalXHROpen.apply(this, [method, url, ...rest] as Parameters<typeof originalXHROpen>);\n };\n\n XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {\n const xhr = this as XMLHttpRequest & { _qaid_method: string; _qaid_url: string };\n const requestBody = body ? truncateBody(body) : undefined;\n\n xhr.addEventListener(\"load\", function () {\n if (xhr.status >= 400) {\n addEntry(errors, {\n url: xhr._qaid_url,\n method: xhr._qaid_method.toUpperCase(),\n status: xhr.status,\n statusText: xhr.statusText,\n requestBody,\n responseBody: truncateBody(xhr.responseText),\n timestamp: Date.now(),\n });\n }\n });\n\n return originalXHRSend.apply(this, [body]);\n };\n\n return {\n errors,\n restore: () => {\n window.fetch = originalFetch;\n XMLHttpRequest.prototype.open = originalXHROpen;\n XMLHttpRequest.prototype.send = originalXHRSend;\n },\n };\n}\n","/**\n * Video-recording subsystem — extracted from embed.ts so it lives in its own\n * lazily-loaded chunk. A thumbs-only visitor never downloads any of this; the\n * embed dynamically imports it the first time the record button is used (and\n * warms it on hover — see embed.ts prewarmVideo).\n *\n * The controller owns all recording state and UI (recorder, redaction picker,\n * indicator, preview, submit). It talks back to the embed through the narrow\n * `RecordingHost` interface, so embed internals stay private and the coupling\n * is explicit.\n */\n\nimport { h } from \"./dom\";\nimport { isEmbedElement } from \"./dom-utils\";\nimport { captureNetworkErrors, type NetworkCapture } from \"./network-capture\";\nimport type { VideoRecorder } from \"./video-capture\";\nimport type { ResolvedFeedbackConfig, EmbedState } from \"./types\";\nimport type { ConsoleCapture } from \"./console-capture\";\n\n/** The slice of the embed the recording subsystem needs. */\nexport interface RecordingHost {\n readonly config: ResolvedFeedbackConfig;\n readonly visitorId: string;\n readonly uid: string;\n readonly overlayShadowHost: HTMLDivElement | null;\n readonly consoleCapture: ConsoleCapture | null;\n readonly boundKeyDown: (e: KeyboardEvent) => void;\n readonly state: EmbedState;\n setState(state: EmbedState): void;\n ensureOverlayHost(): ShadowRoot;\n applyVars(el: HTMLElement): void;\n announceMsg(message: string, assertive?: boolean): void;\n openDialogA11y(\n container: HTMLElement,\n opts: { labelledbyId?: string; describedbyId?: string; label?: string }\n ): void;\n closeDialogA11y(): void;\n setButtonsDisabled(disabled: boolean): void;\n tryLaunchQuest(\n type: \"up\" | \"down\" | \"video\",\n feedbackId: number | string | null\n ): Promise<boolean>;\n}\n\nexport class RecordingController {\n private videoRecorder: VideoRecorder | null = null;\n private networkCapture: NetworkCapture | null = null;\n private recordedBlob: Blob | null = null;\n private recordingIndicator: HTMLDivElement | null = null;\n private videoPreview: HTMLDivElement | null = null;\n private isRecording = false;\n private isSendingVideo = false;\n\n // Pre-recording redaction picking.\n private redactPicks: Element[] = [];\n private redactPickerRoot: HTMLDivElement | null = null;\n private redactRafId = 0;\n private boundRedactClick = (e: MouseEvent): void => this.handleRedactPickClick(e);\n\n constructor(private host: RecordingHost) {}\n\n /** Escape-key handler; returns true when it consumed the key. */\n handleEscape(): boolean {\n if (this.isRecording) {\n void this.stopRecording();\n return true;\n }\n if (this.videoPreview) {\n this.cancelRecordingPreview();\n return true;\n }\n if (this.redactPickerRoot) {\n this.finishRedactionPicking(false);\n return true;\n }\n return false;\n }\n\n destroy(): void {\n if (this.redactRafId) cancelAnimationFrame(this.redactRafId);\n this.redactRafId = 0;\n document.removeEventListener(\"click\", this.boundRedactClick, true);\n if (this.redactPickerRoot) {\n this.redactPickerRoot.remove();\n this.redactPickerRoot = null;\n }\n this.redactPicks = [];\n this.cleanupRecording();\n this.removeVideoPreview();\n }\n\n async startRecording(redactionElements: Element[] = []): Promise<void> {\n // Don't start if already recording or in a targeting/modal flow.\n if (this.isRecording || this.host.state !== \"IDLE\") return;\n\n try {\n this.networkCapture = captureNetworkErrors();\n\n const { createVideoRecorder } = await import(\"./video-capture\");\n this.videoRecorder = createVideoRecorder({\n maxDuration: this.host.config.videoOptions.maxDuration,\n redactionElements,\n });\n\n this.videoRecorder.onTick((elapsed) => {\n this.updateRecordingTimer(elapsed);\n });\n\n // Handle unexpected stops (browser stop button, max duration).\n this.videoRecorder.onStop((blob) => {\n if (this.isRecording) {\n this.recordedBlob = blob;\n this.isRecording = false;\n this.host.announceMsg(\"Recording stopped\");\n this.removeRecordingIndicator();\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n this.host.setButtonsDisabled(false);\n\n if (blob && blob.size > 0) {\n this.showRecordingPreview();\n } else {\n this.cleanupRecording();\n }\n }\n });\n\n await this.videoRecorder.start();\n this.isRecording = true;\n this.host.announceMsg(\"Recording started\");\n this.host.setButtonsDisabled(true);\n this.showRecordingIndicator();\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n } catch (error) {\n // User denied screen share, shared a disallowed surface (window / whole\n // screen), the browser cannot blur the picked areas, or another error\n // occurred. Surface the tab-only and no-blur messages so neither is a\n // silent no-op. (Matched by text: video-capture is a separate lazy\n // chunk, so importing its constant here would merge the two.)\n if (error instanceof Error && /current tab|cannot blur/i.test(error.message)) {\n this.host.announceMsg(error.message);\n }\n this.cleanupRecording();\n }\n }\n\n private async stopRecording(): Promise<void> {\n try {\n this.recordedBlob = await this.videoRecorder!.stop();\n } catch {\n this.recordedBlob = null;\n }\n\n this.isRecording = false;\n this.host.announceMsg(\"Recording stopped\");\n this.removeRecordingIndicator();\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n\n if (this.recordedBlob && this.recordedBlob.size > 0) {\n this.showRecordingPreview();\n } else {\n this.cleanupRecording();\n }\n }\n\n /**\n * Pre-recording redaction picker. The user clicks page areas to blur; each\n * gets an outline that tracks its position, then \"Start recording\" hands the\n * picks to the recorder, which blurs their live bounding boxes so the\n * redaction follows the content as the page scrolls. \"Cancel\"/Escape aborts.\n */\n startPicking(): void {\n if (this.isRecording || this.host.state !== \"IDLE\") return;\n this.host.setState(\"REDACT_PICKING\");\n this.redactPicks = [];\n\n const root = this.host.ensureOverlayHost();\n\n // Layer holding the per-pick outlines (repositioned every frame).\n const boxHost = h(\"div\", { style: \"position:absolute;inset:0;\" });\n\n const container = h(\n \"div\",\n { class: \"qaid-redact-picker\", style: \"position:fixed;inset:0;pointer-events:none;\" },\n boxHost,\n // Control bar — the only pointer-interactive part of the overlay.\n h(\n \"div\",\n {\n style:\n \"position:fixed;left:50%;bottom:24px;transform:translateX(-50%);\" +\n \"display:flex;gap:12px;align-items:center;pointer-events:auto;\" +\n \"background:#0b1220;color:#fff;border:1px solid #ff6b6b;border-radius:9999px;\" +\n \"padding:10px 16px;font:600 13px system-ui,sans-serif;box-shadow:0 8px 30px rgba(0,0,0,.5);\",\n },\n h(\"span\", {\n text: \"Click sensitive areas to blur\",\n attrs: { \"data-qaid-redact-label\": \"\" },\n }),\n h(\"button\", {\n text: \"Start recording\",\n attrs: { type: \"button\" },\n style:\n \"cursor:pointer;border:0;border-radius:9999px;padding:8px 14px;background:#ff6b6b;color:#0b1220;font:inherit;\",\n on: { click: () => this.finishRedactionPicking(true) },\n }),\n h(\"button\", {\n text: \"Cancel\",\n attrs: { type: \"button\" },\n style: \"cursor:pointer;border:0;background:transparent;color:#9fb3c8;font:inherit;\",\n on: { click: () => this.finishRedactionPicking(false) },\n }),\n ),\n );\n\n root.appendChild(container);\n this.redactPickerRoot = container;\n\n // Keep the outlines glued to their elements while the user scrolls/picks.\n const tick = (): void => {\n this.renderRedactPicks(boxHost);\n this.redactRafId = requestAnimationFrame(tick);\n };\n tick();\n\n document.addEventListener(\"click\", this.boundRedactClick, true);\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n this.host.announceMsg(\"Click sensitive areas to blur, then start recording\");\n }\n\n private handleRedactPickClick(e: MouseEvent): void {\n const target = e.target instanceof Element ? e.target : null;\n // Ignore clicks on the embed's own UI. isEmbedElement catches the retargeted\n // shadow host (real browsers compose click events to it); the contains()\n // check catches the picker's own control-bar buttons when the event isn't\n // retargeted, so their handlers still fire instead of being swallowed here.\n if (!target || isEmbedElement(target) || this.redactPickerRoot?.contains(target)) return;\n e.preventDefault();\n e.stopPropagation();\n\n const idx = this.redactPicks.indexOf(target);\n if (idx >= 0) {\n this.redactPicks.splice(idx, 1);\n } else {\n this.redactPicks.push(target);\n }\n\n const label = this.redactPickerRoot?.querySelector(\"[data-qaid-redact-label]\");\n if (label) {\n const n = this.redactPicks.length;\n label.textContent =\n n === 0 ? \"Click sensitive areas to blur\" : `${n} area${n === 1 ? \"\" : \"s\"} will be blurred`;\n }\n }\n\n private renderRedactPicks(layer: HTMLElement): void {\n // Rebuild the outline boxes to match current picks and their live positions.\n layer.textContent = \"\";\n for (const el of this.redactPicks) {\n const r = el.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0) continue;\n layer.appendChild(\n h(\"div\", {\n style:\n `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;` +\n \"border:2px solid #ff6b6b;border-radius:4px;background:rgba(255,107,107,.18);pointer-events:none;\",\n }),\n );\n }\n }\n\n private finishRedactionPicking(record: boolean): void {\n if (this.redactRafId) cancelAnimationFrame(this.redactRafId);\n this.redactRafId = 0;\n document.removeEventListener(\"click\", this.boundRedactClick, true);\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n if (this.redactPickerRoot) {\n this.redactPickerRoot.remove();\n this.redactPickerRoot = null;\n }\n const picks = this.redactPicks;\n this.redactPicks = [];\n this.host.setState(\"IDLE\");\n if (record) {\n void this.startRecording(picks);\n }\n }\n\n private showRecordingIndicator(): void {\n const root = this.host.ensureOverlayHost();\n\n this.recordingIndicator = h(\n \"div\",\n { class: \"qaid-recording-indicator\", style: `z-index:${this.host.config.zIndex + 100}` },\n h(\"div\", { class: \"qaid-recording-dot\" }),\n h(\"span\", {\n class: \"qaid-recording-time\",\n text: this.formatTime(this.host.config.videoOptions.maxDuration),\n }),\n h(\"button\", {\n class: \"qaid-recording-stop\",\n text: \"Stop\",\n attrs: { type: \"button\" },\n on: { click: () => this.stopRecording() },\n }),\n );\n\n this.host.applyVars(this.recordingIndicator);\n\n // Enable pointer events on the overlay host for the recording indicator.\n if (this.host.overlayShadowHost) {\n this.host.overlayShadowHost.style.pointerEvents = \"auto\";\n }\n\n root.appendChild(this.recordingIndicator);\n }\n\n private updateRecordingTimer(elapsed: number): void {\n if (!this.recordingIndicator) return;\n const timer = this.recordingIndicator.querySelector<HTMLSpanElement>(\".qaid-recording-time\");\n if (timer) {\n const remaining = Math.max(0, this.host.config.videoOptions.maxDuration - elapsed);\n timer.textContent = this.formatTime(remaining);\n // Announce the final countdown so it is perceivable non-visually.\n if (remaining > 0 && remaining <= 5) {\n this.host.announceMsg(`${remaining} second${remaining === 1 ? \"\" : \"s\"} remaining`);\n }\n }\n }\n\n private formatTime(seconds: number): string {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n }\n\n private removeRecordingIndicator(): void {\n if (this.recordingIndicator) {\n this.recordingIndicator.remove();\n this.recordingIndicator = null;\n }\n }\n\n private showRecordingPreview(): void {\n const root = this.host.ensureOverlayHost();\n\n const videoUrl = URL.createObjectURL(this.recordedBlob!);\n const titleId = `qaid-video-title-${this.host.uid}`;\n\n const title = h(\"h3\", { text: \"Review your recording\", attrs: { id: titleId } });\n\n // Boolean media props must be set as properties (the `muted` attribute is\n // unreliable for autoplay), so build the element then assign them.\n const videoEl = h(\"video\");\n videoEl.src = videoUrl;\n videoEl.controls = true;\n videoEl.autoplay = true;\n videoEl.muted = true;\n\n const textarea = h(\"textarea\", {\n attrs: {\n placeholder: \"Optional: Describe the issue you recorded...\",\n \"aria-label\": \"Describe the issue you recorded\",\n },\n });\n\n const sendBtn = h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-send\",\n text: \"Send\",\n attrs: { type: \"button\" },\n on: { click: () => this.submitVideoFeedback(textarea.value.trim() || null, sendBtn) },\n });\n\n const box = h(\n \"div\",\n { class: \"qaid-video-preview-box\" },\n title,\n videoEl,\n textarea,\n h(\n \"div\",\n { class: \"qaid-video-preview-actions\" },\n h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-cancel\",\n text: \"Cancel\",\n attrs: { type: \"button\" },\n on: { click: () => this.cancelRecordingPreview() },\n }),\n h(\"button\", {\n class: \"qaid-video-btn qaid-video-btn-rerecord\",\n text: \"Re-record\",\n attrs: { type: \"button\" },\n on: {\n click: () => {\n this.cancelRecordingPreview();\n this.startRecording();\n },\n },\n }),\n sendBtn,\n ),\n );\n\n this.videoPreview = h(\n \"div\",\n { class: \"qaid-video-preview\", style: `z-index:${this.host.config.zIndex + 100}` },\n box,\n );\n\n this.host.applyVars(this.videoPreview);\n\n // Enable pointer events on the overlay host for the video preview.\n if (this.host.overlayShadowHost) {\n this.host.overlayShadowHost.style.pointerEvents = \"auto\";\n }\n\n root.appendChild(this.videoPreview);\n\n // Dialog semantics + focus trap + background inert.\n this.host.openDialogA11y(box, { labelledbyId: title.id });\n\n // Listen for the escape key.\n document.addEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private cancelRecordingPreview(): void {\n this.removeVideoPreview();\n this.cleanupRecording();\n }\n\n private removeVideoPreview(): void {\n // Release focus trap / inert and restore focus before removing the DOM.\n this.host.closeDialogA11y();\n if (this.videoPreview) {\n const videoEl = this.videoPreview.querySelector<HTMLVideoElement>(\"video\");\n if (videoEl?.src) {\n URL.revokeObjectURL(videoEl.src);\n }\n this.videoPreview.remove();\n this.videoPreview = null;\n }\n document.removeEventListener(\"keydown\", this.host.boundKeyDown);\n }\n\n private async submitVideoFeedback(message: string | null, sendBtn: HTMLButtonElement): Promise<void> {\n if (!this.recordedBlob || this.isSendingVideo) return;\n\n this.isSendingVideo = true;\n sendBtn.disabled = true;\n sendBtn.textContent = \"Sending...\";\n\n const formData = new FormData();\n formData.append(\"video\", this.recordedBlob, `recording.${this.recordedBlob.type.includes(\"mp4\") ? \"mp4\" : \"webm\"}`);\n formData.append(\"pageUrl\", window.location.href);\n formData.append(\"visitorId\", this.host.visitorId);\n\n if (this.host.config.apiKey) {\n formData.append(\"apiKey\", this.host.config.apiKey);\n }\n if (message) {\n formData.append(\"message\", message);\n }\n if (this.host.consoleCapture) {\n formData.append(\"consoleErrors\", JSON.stringify(this.host.consoleCapture.errors));\n }\n if (this.networkCapture) {\n formData.append(\"networkErrors\", JSON.stringify(this.networkCapture.errors));\n }\n\n let videoFeedbackId: string | number | null = null;\n // Set when the upload failed: the reason shown to the visitor.\n let failure: string | null = null;\n try {\n const response = await fetch(`${this.host.config.endpoint}/video`, {\n method: \"POST\",\n body: formData,\n });\n\n if (response.ok) {\n this.host.announceMsg(\"Recording sent\");\n try {\n const data = (await response.json()) as { id?: string | number };\n videoFeedbackId = data?.id ?? null;\n } catch {\n // Non-JSON / no id — the quest can still launch, just unlinked.\n }\n } else {\n // A refusal (403 on a plan without video, 400, 413 too large, 429…)\n // used to close the preview exactly like a success.\n console.error(\"Failed to submit video feedback:\", await response.text());\n failure =\n response.status === 413\n ? \"This recording is too large to send. Try a shorter one.\"\n : \"Your recording could not be sent.\";\n }\n } catch (error) {\n console.error(\"Failed to submit video feedback:\", error);\n failure = \"Your recording could not be sent. Check your connection and try again.\";\n }\n\n this.isSendingVideo = false;\n\n if (failure) {\n this.showSendError(failure, sendBtn);\n return;\n }\n\n this.removeVideoPreview();\n this.cleanupRecording();\n\n // Once the recording is safely sent, launch the linked quest (if any),\n // passing the new feedback record id so its response is joinable.\n await this.host.tryLaunchQuest(\"video\", videoFeedbackId);\n }\n\n /**\n * Keep the preview open and say the upload failed. Closing it, as a success\n * does, told the visitor their recording had arrived when it had not. Send\n * is re-enabled for another try; Cancel and Re-record still work.\n */\n private showSendError(message: string, sendBtn: HTMLButtonElement): void {\n this.host.announceMsg(`Failed to send recording. ${message}`, true);\n sendBtn.disabled = false;\n sendBtn.textContent = \"Send\";\n\n const actions = this.videoPreview?.querySelector(\".qaid-video-preview-actions\");\n if (actions) {\n let note = this.videoPreview!.querySelector<HTMLElement>(\".qaid-video-error\");\n if (!note) {\n note = h(\"p\", { class: \"qaid-video-error\" });\n actions.before(note);\n }\n note.textContent = message;\n }\n }\n\n private cleanupRecording(): void {\n this.isRecording = false;\n this.removeRecordingIndicator();\n\n if (this.videoRecorder) {\n this.videoRecorder.destroy();\n this.videoRecorder = null;\n }\n if (this.networkCapture) {\n this.networkCapture.restore();\n this.networkCapture = null;\n }\n if (this.recordedBlob) {\n this.recordedBlob = null;\n }\n\n this.host.setButtonsDisabled(false);\n\n // Reset overlay host pointer events if nothing else needs them.\n if (this.host.overlayShadowHost && this.host.state === \"IDLE\") {\n this.host.overlayShadowHost.style.pointerEvents = \"none\";\n }\n }\n}\n"],"names":["MAX_ENTRIES","MAX_BODY_SIZE","truncateBody","body","str","addEntry","errors","entry","safeReadBody","response","text","captureNetworkErrors","originalFetch","input","init","url","method","requestBody","responseBody","originalXHROpen","originalXHRSend","rest","xhr","RecordingController","host","e","redactionElements","createVideoRecorder","elapsed","blob","error","root","boxHost","h","container","tick","target","isEmbedElement","idx","label","n","layer","el","r","record","picks","timer","remaining","seconds","m","s","videoUrl","titleId","title","videoEl","textarea","sendBtn","box","message","formData","videoFeedbackId","failure","actions","note"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  async function v(m = {}) {
2
- const { quality: g = 1, maxWidth: w = 1280, maxHeight: p = 800 } = m;
2
+ const { quality: g = 0.8, maxWidth: w = 1280, maxHeight: p = 800 } = m;
3
3
  try {
4
4
  if (!navigator.mediaDevices?.getDisplayMedia)
5
5
  return console.warn("Screen Capture API not available"), null;
@@ -30,4 +30,4 @@ async function v(m = {}) {
30
30
  export {
31
31
  v as captureScreenshot
32
32
  };
33
- //# sourceMappingURL=screenshot-BkOCLuS2.js.map
33
+ //# sourceMappingURL=screenshot-DYOKg2bS.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot-BkOCLuS2.js","sources":["../../src/screenshot.ts"],"sourcesContent":["/**\n * Screenshot capture utility using native Screen Capture API\n */\n\nexport interface ScreenshotOptions {\n /** Quality of WebP compression (0-1). Default: 1.0 */\n quality?: number;\n /** Max width of the screenshot. Default: 1280 */\n maxWidth?: number;\n /** Max height of the screenshot. Default: 800 */\n maxHeight?: number;\n}\n\n/**\n * Captures a screenshot using the native Screen Capture API\n * Requires user permission but produces accurate results\n * Returns a base64-encoded WebP data URL\n */\nexport async function captureScreenshot(\n options: ScreenshotOptions = {}\n): Promise<string | null> {\n const { quality = 1.0, maxWidth = 1280, maxHeight = 800 } = options;\n\n try {\n // Check if API is available\n if (!navigator.mediaDevices?.getDisplayMedia) {\n console.warn('Screen Capture API not available');\n return null;\n }\n\n // Request screen capture - preferCurrentTab auto-selects current tab in supported browsers\n const stream = await navigator.mediaDevices.getDisplayMedia({\n preferCurrentTab: true,\n video: {\n displaySurface: 'browser',\n },\n } as DisplayMediaStreamOptions);\n\n // Get video track\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n\n // Create video element\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n\n await new Promise<void>((resolve) => {\n video.onloadedmetadata = () => {\n video.play();\n resolve();\n };\n });\n\n // Wait for video to have actual frame data\n await new Promise<void>((resolve) => {\n const checkFrame = () => {\n if (video.readyState >= 2) {\n resolve();\n } else {\n requestAnimationFrame(checkFrame);\n }\n };\n checkFrame();\n });\n\n // Extra delay to ensure frame is rendered\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Calculate dimensions\n const srcWidth = settings.width || video.videoWidth;\n const srcHeight = settings.height || video.videoHeight;\n const scale = Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);\n const width = Math.round(srcWidth * scale);\n const height = Math.round(srcHeight * scale);\n\n // Draw to canvas\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n track.stop();\n return null;\n }\n\n ctx.drawImage(video, 0, 0, width, height);\n\n // Stop the stream\n track.stop();\n\n return canvas.toDataURL('image/webp', quality);\n } catch (error) {\n console.warn('Screenshot capture failed:', error);\n return null;\n }\n}\n"],"names":["captureScreenshot","options","quality","maxWidth","maxHeight","stream","track","settings","video","resolve","checkFrame","srcWidth","srcHeight","scale","width","height","canvas","ctx","error"],"mappings":"AAkBA,eAAsBA,EACpBC,IAA6B,IACL;AACxB,QAAM,EAAE,SAAAC,IAAU,GAAK,UAAAC,IAAW,MAAM,WAAAC,IAAY,QAAQH;AAE5D,MAAI;AAEF,QAAI,CAAC,UAAU,cAAc;AAC3B,qBAAQ,KAAK,kCAAkC,GACxC;AAIT,UAAMI,IAAS,MAAM,UAAU,aAAa,gBAAgB;AAAA,MAC1D,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,gBAAgB;AAAA,MAAA;AAAA,IAClB,CAC4B,GAGxBC,IAAQD,EAAO,eAAA,EAAiB,CAAC,GACjCE,IAAWD,EAAM,YAAA,GAGjBE,IAAQ,SAAS,cAAc,OAAO;AAC5C,IAAAA,EAAM,YAAYH,GAClBG,EAAM,QAAQ,IAEd,MAAM,IAAI,QAAc,CAACC,MAAY;AACnC,MAAAD,EAAM,mBAAmB,MAAM;AAC7B,QAAAA,EAAM,KAAA,GACNC,EAAA;AAAA,MACF;AAAA,IACF,CAAC,GAGD,MAAM,IAAI,QAAc,CAACA,MAAY;AACnC,YAAMC,IAAa,MAAM;AACvB,QAAIF,EAAM,cAAc,IACtBC,EAAA,IAEA,sBAAsBC,CAAU;AAAA,MAEpC;AACA,MAAAA,EAAA;AAAA,IACF,CAAC,GAGD,MAAM,IAAI,QAAQ,CAACD,MAAY,WAAWA,GAAS,GAAG,CAAC;AAGvD,UAAME,IAAWJ,EAAS,SAASC,EAAM,YACnCI,IAAYL,EAAS,UAAUC,EAAM,aACrCK,IAAQ,KAAK,IAAIV,IAAWQ,GAAUP,IAAYQ,GAAW,CAAC,GAC9DE,IAAQ,KAAK,MAAMH,IAAWE,CAAK,GACnCE,IAAS,KAAK,MAAMH,IAAYC,CAAK,GAGrCG,IAAS,SAAS,cAAc,QAAQ;AAC9C,IAAAA,EAAO,QAAQF,GACfE,EAAO,SAASD;AAEhB,UAAME,IAAMD,EAAO,WAAW,IAAI;AAClC,WAAKC,KAKLA,EAAI,UAAUT,GAAO,GAAG,GAAGM,GAAOC,CAAM,GAGxCT,EAAM,KAAA,GAECU,EAAO,UAAU,cAAcd,CAAO,MAT3CI,EAAM,KAAA,GACC;AAAA,EASX,SAASY,GAAO;AACd,mBAAQ,KAAK,8BAA8BA,CAAK,GACzC;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"screenshot-DYOKg2bS.js","sources":["../../src/screenshot.ts"],"sourcesContent":["/**\n * Screenshot capture utility using native Screen Capture API\n */\n\nexport interface ScreenshotOptions {\n /** Quality of WebP compression (0-1). Default: 0.8 */\n quality?: number;\n /** Max width of the screenshot. Default: 1280 */\n maxWidth?: number;\n /** Max height of the screenshot. Default: 800 */\n maxHeight?: number;\n}\n\n/**\n * Captures a screenshot using the native Screen Capture API\n * Requires user permission but produces accurate results\n * Returns a base64-encoded WebP data URL\n */\nexport async function captureScreenshot(\n options: ScreenshotOptions = {}\n): Promise<string | null> {\n const { quality = 0.8, maxWidth = 1280, maxHeight = 800 } = options;\n\n try {\n // Check if API is available\n if (!navigator.mediaDevices?.getDisplayMedia) {\n console.warn('Screen Capture API not available');\n return null;\n }\n\n // Request screen capture - preferCurrentTab auto-selects current tab in supported browsers\n const stream = await navigator.mediaDevices.getDisplayMedia({\n preferCurrentTab: true,\n video: {\n displaySurface: 'browser',\n },\n } as DisplayMediaStreamOptions);\n\n // Get video track\n const track = stream.getVideoTracks()[0];\n const settings = track.getSettings();\n\n // Create video element\n const video = document.createElement('video');\n video.srcObject = stream;\n video.muted = true;\n\n await new Promise<void>((resolve) => {\n video.onloadedmetadata = () => {\n video.play();\n resolve();\n };\n });\n\n // Wait for video to have actual frame data\n await new Promise<void>((resolve) => {\n const checkFrame = () => {\n if (video.readyState >= 2) {\n resolve();\n } else {\n requestAnimationFrame(checkFrame);\n }\n };\n checkFrame();\n });\n\n // Extra delay to ensure frame is rendered\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // Calculate dimensions\n const srcWidth = settings.width || video.videoWidth;\n const srcHeight = settings.height || video.videoHeight;\n const scale = Math.min(maxWidth / srcWidth, maxHeight / srcHeight, 1);\n const width = Math.round(srcWidth * scale);\n const height = Math.round(srcHeight * scale);\n\n // Draw to canvas\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n track.stop();\n return null;\n }\n\n ctx.drawImage(video, 0, 0, width, height);\n\n // Stop the stream\n track.stop();\n\n return canvas.toDataURL('image/webp', quality);\n } catch (error) {\n console.warn('Screenshot capture failed:', error);\n return null;\n }\n}\n"],"names":["captureScreenshot","options","quality","maxWidth","maxHeight","stream","track","settings","video","resolve","checkFrame","srcWidth","srcHeight","scale","width","height","canvas","ctx","error"],"mappings":"AAkBA,eAAsBA,EACpBC,IAA6B,IACL;AACxB,QAAM,EAAE,SAAAC,IAAU,KAAK,UAAAC,IAAW,MAAM,WAAAC,IAAY,QAAQH;AAE5D,MAAI;AAEF,QAAI,CAAC,UAAU,cAAc;AAC3B,qBAAQ,KAAK,kCAAkC,GACxC;AAIT,UAAMI,IAAS,MAAM,UAAU,aAAa,gBAAgB;AAAA,MAC1D,kBAAkB;AAAA,MAClB,OAAO;AAAA,QACL,gBAAgB;AAAA,MAAA;AAAA,IAClB,CAC4B,GAGxBC,IAAQD,EAAO,eAAA,EAAiB,CAAC,GACjCE,IAAWD,EAAM,YAAA,GAGjBE,IAAQ,SAAS,cAAc,OAAO;AAC5C,IAAAA,EAAM,YAAYH,GAClBG,EAAM,QAAQ,IAEd,MAAM,IAAI,QAAc,CAACC,MAAY;AACnC,MAAAD,EAAM,mBAAmB,MAAM;AAC7B,QAAAA,EAAM,KAAA,GACNC,EAAA;AAAA,MACF;AAAA,IACF,CAAC,GAGD,MAAM,IAAI,QAAc,CAACA,MAAY;AACnC,YAAMC,IAAa,MAAM;AACvB,QAAIF,EAAM,cAAc,IACtBC,EAAA,IAEA,sBAAsBC,CAAU;AAAA,MAEpC;AACA,MAAAA,EAAA;AAAA,IACF,CAAC,GAGD,MAAM,IAAI,QAAQ,CAACD,MAAY,WAAWA,GAAS,GAAG,CAAC;AAGvD,UAAME,IAAWJ,EAAS,SAASC,EAAM,YACnCI,IAAYL,EAAS,UAAUC,EAAM,aACrCK,IAAQ,KAAK,IAAIV,IAAWQ,GAAUP,IAAYQ,GAAW,CAAC,GAC9DE,IAAQ,KAAK,MAAMH,IAAWE,CAAK,GACnCE,IAAS,KAAK,MAAMH,IAAYC,CAAK,GAGrCG,IAAS,SAAS,cAAc,QAAQ;AAC9C,IAAAA,EAAO,QAAQF,GACfE,EAAO,SAASD;AAEhB,UAAME,IAAMD,EAAO,WAAW,IAAI;AAClC,WAAKC,KAKLA,EAAI,UAAUT,GAAO,GAAG,GAAGM,GAAOC,CAAM,GAGxCT,EAAM,KAAA,GAECU,EAAO,UAAU,cAAcd,CAAO,MAT3CI,EAAM,KAAA,GACC;AAAA,EASX,SAASY,GAAO;AACd,mBAAQ,KAAK,8BAA8BA,CAAK,GACzC;AAAA,EACT;AACF;"}
@@ -1,4 +1,4 @@
1
- import { g as k, c as T, b as m } from "./annotate-NeITyYNe.js";
1
+ import { g as k, c as T, b as m } from "./annotate-D-o8xCV9.js";
2
2
  const E = ["data-comp", "data-qa", "data-testid", "data-id"];
3
3
  function C(s, t = document.body) {
4
4
  let e = s;
@@ -175,6 +175,7 @@ class O {
175
175
  constructor(t) {
176
176
  this.host = t;
177
177
  }
178
+ host;
178
179
  mousePos = { x: 0, y: 0 };
179
180
  touchStartPos = null;
180
181
  overlayContainer = null;
@@ -292,4 +293,4 @@ class O {
292
293
  export {
293
294
  O as TargetingController
294
295
  };
295
- //# sourceMappingURL=targeting-BEQU2Ogo.js.map
296
+ //# sourceMappingURL=targeting-Dz6z1BYl.js.map