@checkflow/sdk 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -134,6 +134,75 @@ function clearLogs() {
134
134
  errorsCapture = [];
135
135
  }
136
136
 
137
+ // src/network-interceptor.ts
138
+ var MAX_LOGS2 = 50;
139
+ var logs2 = [];
140
+ var installed2 = false;
141
+ function installNetworkInterceptor() {
142
+ if (installed2 || typeof window === "undefined") return;
143
+ installed2 = true;
144
+ const origFetch = window.fetch;
145
+ window.fetch = async function(...args) {
146
+ const start = Date.now();
147
+ const req = new Request(...args);
148
+ const entry = {
149
+ method: req.method,
150
+ url: req.url,
151
+ type: "fetch",
152
+ timestamp: start
153
+ };
154
+ try {
155
+ const res = await origFetch.apply(this, args);
156
+ entry.status = res.status;
157
+ entry.duration = Date.now() - start;
158
+ entry.response_type = res.headers.get("content-type") || void 0;
159
+ pushLog(entry);
160
+ return res;
161
+ } catch (err) {
162
+ entry.duration = Date.now() - start;
163
+ entry.error = err.message || "Network error";
164
+ pushLog(entry);
165
+ throw err;
166
+ }
167
+ };
168
+ const origOpen = XMLHttpRequest.prototype.open;
169
+ const origSend = XMLHttpRequest.prototype.send;
170
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
171
+ this.__cf_method = method;
172
+ this.__cf_url = String(url);
173
+ return origOpen.apply(this, [method, url, ...rest]);
174
+ };
175
+ XMLHttpRequest.prototype.send = function(...args) {
176
+ const start = Date.now();
177
+ const xhr = this;
178
+ xhr.addEventListener("loadend", () => {
179
+ const entry = {
180
+ method: xhr.__cf_method || "GET",
181
+ url: xhr.__cf_url || "",
182
+ status: xhr.status,
183
+ duration: Date.now() - start,
184
+ type: "xhr",
185
+ timestamp: start,
186
+ response_type: xhr.getResponseHeader("content-type") || void 0
187
+ };
188
+ if (xhr.status === 0) entry.error = "Request failed";
189
+ pushLog(entry);
190
+ });
191
+ return origSend.apply(this, args);
192
+ };
193
+ }
194
+ function pushLog(entry) {
195
+ if (entry.url.includes("/sdk/feedback")) return;
196
+ logs2.push(entry);
197
+ if (logs2.length > MAX_LOGS2) logs2.shift();
198
+ }
199
+ function getNetworkLogs() {
200
+ return [...logs2];
201
+ }
202
+ function clearNetworkLogs() {
203
+ logs2 = [];
204
+ }
205
+
137
206
  // src/widget.ts
138
207
  var DEFAULT_CONFIG = {
139
208
  position: "bottom-right",
@@ -145,78 +214,154 @@ var container = null;
145
214
  var onSubmitCallback = null;
146
215
  var screenshotDataUrl = null;
147
216
  var annotationsData = [];
217
+ var isExpanded = false;
148
218
  function mountWidget(config2 = {}, onSubmit, opts) {
149
219
  if (container) return;
150
220
  onSubmitCallback = onSubmit;
151
221
  const cfg = { ...DEFAULT_CONFIG, ...config2 };
152
222
  container = document.createElement("div");
153
223
  container.id = "checkflow-widget";
154
- container.innerHTML = getWidgetHTML(cfg);
224
+ injectStyles(cfg);
225
+ container.innerHTML = getTriggerHTML(cfg);
155
226
  document.body.appendChild(container);
156
227
  const btn = container.querySelector("#cf-trigger");
157
- const form = container.querySelector("#cf-form");
158
- const closeBtn = container.querySelector("#cf-close");
159
- const submitBtn = container.querySelector("#cf-submit");
160
- const screenshotBtn = container.querySelector("#cf-screenshot");
161
- const highlightBtn = container.querySelector("#cf-highlight");
162
- const screenshotPreview = container.querySelector("#cf-screenshot-preview");
163
- btn?.addEventListener("click", () => {
164
- form.style.display = form.style.display === "none" ? "flex" : "none";
165
- screenshotDataUrl = null;
166
- annotationsData = [];
167
- if (screenshotPreview) screenshotPreview.style.display = "none";
168
- });
169
- closeBtn?.addEventListener("click", () => {
170
- form.style.display = "none";
228
+ btn?.addEventListener("click", () => openModal(cfg, opts));
229
+ }
230
+ function openModal(cfg, opts) {
231
+ if (!container) return;
232
+ const existing = document.getElementById("cf-modal-backdrop");
233
+ if (existing) existing.remove();
234
+ isExpanded = false;
235
+ screenshotDataUrl = null;
236
+ annotationsData = [];
237
+ const backdrop = document.createElement("div");
238
+ backdrop.id = "cf-modal-backdrop";
239
+ backdrop.innerHTML = getModalHTML(cfg, false);
240
+ document.body.appendChild(backdrop);
241
+ backdrop.addEventListener("click", (e) => {
242
+ if (e.target === backdrop) closeModal();
171
243
  });
172
- screenshotBtn?.addEventListener("click", async () => {
244
+ bindModalEvents(cfg, opts);
245
+ }
246
+ function closeModal() {
247
+ const backdrop = document.getElementById("cf-modal-backdrop");
248
+ if (backdrop) backdrop.remove();
249
+ isExpanded = false;
250
+ screenshotDataUrl = null;
251
+ annotationsData = [];
252
+ }
253
+ function expandModal(cfg, opts) {
254
+ const backdrop = document.getElementById("cf-modal-backdrop");
255
+ if (!backdrop) return;
256
+ const name = backdrop.querySelector("#cf-name")?.value || "";
257
+ const email = backdrop.querySelector("#cf-email")?.value || "";
258
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
259
+ isExpanded = true;
260
+ backdrop.innerHTML = getModalHTML(cfg, true);
261
+ bindModalEvents(cfg, opts);
262
+ const nameEl = backdrop.querySelector("#cf-name");
263
+ const emailEl = backdrop.querySelector("#cf-email");
264
+ const descEl = backdrop.querySelector("#cf-desc");
265
+ if (nameEl) nameEl.value = name;
266
+ if (emailEl) emailEl.value = email;
267
+ if (descEl) descEl.value = desc;
268
+ }
269
+ function collapseModal(cfg, opts) {
270
+ const backdrop = document.getElementById("cf-modal-backdrop");
271
+ if (!backdrop) return;
272
+ const name = backdrop.querySelector("#cf-name")?.value || "";
273
+ const email = backdrop.querySelector("#cf-email")?.value || "";
274
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
275
+ isExpanded = false;
276
+ screenshotDataUrl = null;
277
+ annotationsData = [];
278
+ backdrop.innerHTML = getModalHTML(cfg, false);
279
+ bindModalEvents(cfg, opts);
280
+ const nameEl = backdrop.querySelector("#cf-name");
281
+ const emailEl = backdrop.querySelector("#cf-email");
282
+ const descEl = backdrop.querySelector("#cf-desc");
283
+ if (nameEl) nameEl.value = name;
284
+ if (emailEl) emailEl.value = email;
285
+ if (descEl) descEl.value = desc;
286
+ }
287
+ function bindModalEvents(cfg, opts) {
288
+ const backdrop = document.getElementById("cf-modal-backdrop");
289
+ if (!backdrop) return;
290
+ backdrop.querySelector("#cf-cancel")?.addEventListener("click", closeModal);
291
+ backdrop.querySelector("#cf-screenshot-btn")?.addEventListener("click", async () => {
173
292
  if (!opts?.onScreenshot) return;
174
- screenshotBtn.textContent = "Capturing...";
175
- form.style.display = "none";
293
+ backdrop.style.display = "none";
176
294
  try {
177
295
  const data = await opts.onScreenshot();
178
296
  if (data) {
179
297
  screenshotDataUrl = data;
180
- if (screenshotPreview) {
181
- screenshotPreview.querySelector("img").src = data;
182
- screenshotPreview.style.display = "block";
183
- }
298
+ backdrop.style.display = "flex";
299
+ expandModal(cfg, opts);
300
+ return;
184
301
  }
185
302
  } catch {
186
303
  }
187
- form.style.display = "flex";
188
- screenshotBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg> Screenshot';
304
+ backdrop.style.display = "flex";
305
+ });
306
+ backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
307
+ collapseModal(cfg, opts);
189
308
  });
190
- highlightBtn?.addEventListener("click", async () => {
309
+ backdrop.querySelector("#cf-highlight-btn")?.addEventListener("click", async () => {
191
310
  if (!opts?.onHighlight) return;
192
- form.style.display = "none";
311
+ backdrop.style.display = "none";
193
312
  try {
194
313
  const annotations = await opts.onHighlight();
195
314
  if (annotations && annotations.length > 0) {
196
315
  annotationsData = annotations;
197
- highlightBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg> ${annotations.length} highlighted`;
198
316
  }
199
317
  } catch {
200
318
  }
201
- form.style.display = "flex";
319
+ backdrop.style.display = "flex";
320
+ const hlBtn = backdrop.querySelector("#cf-highlight-btn");
321
+ if (hlBtn && annotationsData.length > 0) {
322
+ hlBtn.textContent = `Surligner (${annotationsData.length})`;
323
+ }
324
+ });
325
+ backdrop.querySelector("#cf-mask-btn")?.addEventListener("click", async () => {
326
+ if (!opts?.onHighlight) return;
327
+ backdrop.style.display = "none";
328
+ try {
329
+ const annotations = await opts.onHighlight();
330
+ if (annotations && annotations.length > 0) {
331
+ annotationsData = [...annotationsData, ...annotations];
332
+ }
333
+ } catch {
334
+ }
335
+ backdrop.style.display = "flex";
202
336
  });
203
- submitBtn?.addEventListener("click", () => {
204
- const title = container.querySelector("#cf-title")?.value;
205
- const desc = container.querySelector("#cf-desc")?.value;
206
- const type = container.querySelector("#cf-type")?.value;
207
- const priority = container.querySelector("#cf-priority")?.value;
208
- if (!title?.trim()) return;
209
- onSubmitCallback?.({ title, description: desc, type, priority, screenshot: screenshotDataUrl || void 0, annotations: annotationsData.length > 0 ? annotationsData : void 0 });
210
- container.querySelector("#cf-title").value = "";
211
- container.querySelector("#cf-desc").value = "";
212
- screenshotDataUrl = null;
213
- annotationsData = [];
214
- if (screenshotPreview) screenshotPreview.style.display = "none";
215
- form.style.display = "none";
216
- showToast("Feedback sent!");
337
+ backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
338
+ const desc = backdrop.querySelector("#cf-desc")?.value;
339
+ const name = backdrop.querySelector("#cf-name")?.value;
340
+ const email = backdrop.querySelector("#cf-email")?.value;
341
+ if (!desc?.trim()) {
342
+ const descEl = backdrop.querySelector("#cf-desc");
343
+ if (descEl) {
344
+ descEl.style.borderColor = "#e74c3c";
345
+ descEl.focus();
346
+ }
347
+ return;
348
+ }
349
+ onSubmitCallback?.({
350
+ title: desc.trim().slice(0, 100),
351
+ description: desc.trim(),
352
+ type: "BUG",
353
+ priority: "MEDIUM",
354
+ screenshot: screenshotDataUrl || void 0,
355
+ annotations: annotationsData.length > 0 ? annotationsData : void 0,
356
+ reporter_name: name?.trim() || void 0,
357
+ reporter_email: email?.trim() || void 0
358
+ });
359
+ closeModal();
360
+ showToast("Rapport envoy\xE9 avec succ\xE8s !");
217
361
  });
218
362
  }
219
363
  function unmountWidget() {
364
+ closeModal();
220
365
  if (container) {
221
366
  container.remove();
222
367
  container = null;
@@ -225,10 +370,252 @@ function unmountWidget() {
225
370
  function showToast(msg) {
226
371
  const toast = document.createElement("div");
227
372
  toast.textContent = msg;
228
- toast.style.cssText = "position:fixed;bottom:80px;right:20px;background:#10b981;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;z-index:100001;font-family:system-ui;box-shadow:0 2px 8px rgba(0,0,0,.15);";
373
+ toast.style.cssText = 'position:fixed;bottom:80px;right:20px;background:#10b981;color:#fff;padding:10px 20px;border-radius:10px;font-size:14px;z-index:100010;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-shadow:0 4px 16px rgba(0,0,0,.18);animation:cf-toast-in .3s ease;';
229
374
  document.body.appendChild(toast);
375
+ setTimeout(() => {
376
+ toast.style.opacity = "0";
377
+ toast.style.transition = "opacity .3s";
378
+ }, 2500);
230
379
  setTimeout(() => toast.remove(), 3e3);
231
380
  }
381
+ function injectStyles(cfg) {
382
+ const style = document.createElement("style");
383
+ style.id = "cf-widget-styles";
384
+ style.textContent = `
385
+ @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
386
+ @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
387
+
388
+ #cf-trigger {
389
+ position:fixed;
390
+ ${getPositionCSS(cfg.position)}
391
+ z-index:100000;
392
+ background:${cfg.color};
393
+ color:#fff;
394
+ border:none;
395
+ border-radius:50px;
396
+ padding:11px 20px;
397
+ font-size:14px;
398
+ font-weight:500;
399
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
400
+ cursor:pointer;
401
+ box-shadow:0 4px 16px rgba(0,0,0,.18);
402
+ display:flex;
403
+ align-items:center;
404
+ gap:8px;
405
+ transition:transform .15s,box-shadow .15s;
406
+ }
407
+ #cf-trigger:hover { transform:scale(1.04); box-shadow:0 6px 24px rgba(0,0,0,.22); }
408
+ #cf-trigger svg { width:18px; height:18px; }
409
+
410
+ #cf-modal-backdrop {
411
+ position:fixed;
412
+ top:0;left:0;right:0;bottom:0;
413
+ background:rgba(0,0,0,.45);
414
+ z-index:100001;
415
+ display:flex;
416
+ align-items:center;
417
+ justify-content:center;
418
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
419
+ }
420
+
421
+ .cf-modal {
422
+ background:#fff;
423
+ border-radius:16px;
424
+ box-shadow:0 24px 80px rgba(0,0,0,.25);
425
+ overflow:hidden;
426
+ animation:cf-modal-in .25s ease;
427
+ display:flex;
428
+ flex-direction:column;
429
+ max-height:90vh;
430
+ }
431
+ .cf-modal--compact { width:420px; }
432
+ .cf-modal--expanded { width:min(1100px,92vw); flex-direction:column; }
433
+
434
+ .cf-modal-header {
435
+ display:flex;
436
+ align-items:center;
437
+ justify-content:space-between;
438
+ padding:20px 24px 16px;
439
+ border-bottom:1px solid #f0f0f0;
440
+ }
441
+ .cf-modal-header h2 {
442
+ margin:0;
443
+ font-size:20px;
444
+ font-weight:700;
445
+ color:#1a1a2e;
446
+ }
447
+ .cf-modal-header .cf-logo {
448
+ width:28px;
449
+ height:28px;
450
+ color:${cfg.color};
451
+ }
452
+
453
+ .cf-modal-body { display:flex; flex:1; overflow:hidden; }
454
+
455
+ .cf-screenshot-panel {
456
+ flex:1;
457
+ min-width:0;
458
+ background:#1a1a2e;
459
+ display:flex;
460
+ flex-direction:column;
461
+ position:relative;
462
+ }
463
+ .cf-screenshot-panel img {
464
+ width:100%;
465
+ height:100%;
466
+ object-fit:contain;
467
+ max-height:60vh;
468
+ }
469
+ .cf-screenshot-tools {
470
+ position:absolute;
471
+ bottom:16px;
472
+ left:50%;
473
+ transform:translateX(-50%);
474
+ display:flex;
475
+ gap:8px;
476
+ }
477
+ .cf-screenshot-tools button {
478
+ padding:8px 18px;
479
+ border-radius:8px;
480
+ font-size:13px;
481
+ font-weight:500;
482
+ cursor:pointer;
483
+ font-family:inherit;
484
+ border:none;
485
+ transition:all .15s;
486
+ }
487
+ .cf-tool-highlight {
488
+ background:${cfg.color};
489
+ color:#fff;
490
+ }
491
+ .cf-tool-highlight:hover { opacity:.9; }
492
+ .cf-tool-mask {
493
+ background:#fff;
494
+ color:#1a1a2e;
495
+ border:1px solid #e0e0e0 !important;
496
+ }
497
+ .cf-tool-mask:hover { background:#f5f5f5; }
498
+
499
+ .cf-form-panel {
500
+ width:100%;
501
+ padding:20px 24px;
502
+ display:flex;
503
+ flex-direction:column;
504
+ gap:14px;
505
+ overflow-y:auto;
506
+ }
507
+ .cf-modal--expanded .cf-form-panel {
508
+ width:340px;
509
+ flex-shrink:0;
510
+ border-left:1px solid #f0f0f0;
511
+ }
512
+
513
+ .cf-field label {
514
+ display:block;
515
+ font-size:13px;
516
+ font-weight:500;
517
+ color:#1a1a2e;
518
+ margin-bottom:6px;
519
+ }
520
+ .cf-field label span { font-weight:400; color:#999; }
521
+ .cf-field input, .cf-field textarea {
522
+ width:100%;
523
+ padding:10px 14px;
524
+ border:1px solid #e0e0e0;
525
+ border-radius:10px;
526
+ font-size:14px;
527
+ font-family:inherit;
528
+ box-sizing:border-box;
529
+ outline:none;
530
+ transition:border-color .15s,box-shadow .15s;
531
+ color:#1a1a2e;
532
+ background:#fff;
533
+ }
534
+ .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
535
+ .cf-field input:focus, .cf-field textarea:focus {
536
+ border-color:${cfg.color};
537
+ box-shadow:0 0 0 3px ${cfg.color}18;
538
+ }
539
+ .cf-field textarea { resize:vertical; min-height:100px; }
540
+
541
+ .cf-screenshot-action {
542
+ width:100%;
543
+ padding:12px;
544
+ border:1px solid #e0e0e0;
545
+ border-radius:10px;
546
+ background:#fff;
547
+ font-size:14px;
548
+ color:#1a1a2e;
549
+ cursor:pointer;
550
+ font-family:inherit;
551
+ transition:all .15s;
552
+ text-align:center;
553
+ }
554
+ .cf-screenshot-action:hover { background:#f8f8f8; border-color:#ccc; }
555
+
556
+ .cf-remove-screenshot {
557
+ width:100%;
558
+ padding:10px;
559
+ border:1px solid #e0e0e0;
560
+ border-radius:10px;
561
+ background:#fff;
562
+ font-size:13px;
563
+ color:#666;
564
+ cursor:pointer;
565
+ font-family:inherit;
566
+ transition:all .15s;
567
+ text-align:center;
568
+ }
569
+ .cf-remove-screenshot:hover { background:#fff0f0; color:#e74c3c; border-color:#e74c3c; }
570
+
571
+ .cf-submit-btn {
572
+ width:100%;
573
+ padding:13px;
574
+ border:none;
575
+ border-radius:10px;
576
+ background:${cfg.color};
577
+ color:#fff;
578
+ font-size:15px;
579
+ font-weight:600;
580
+ cursor:pointer;
581
+ font-family:inherit;
582
+ transition:all .15s;
583
+ }
584
+ .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${cfg.color}40; }
585
+ .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
586
+
587
+ .cf-cancel-btn {
588
+ width:100%;
589
+ padding:11px;
590
+ border:1px solid #e0e0e0;
591
+ border-radius:10px;
592
+ background:#fff;
593
+ color:#1a1a2e;
594
+ font-size:14px;
595
+ font-weight:500;
596
+ cursor:pointer;
597
+ font-family:inherit;
598
+ transition:all .15s;
599
+ text-align:center;
600
+ }
601
+ .cf-cancel-btn:hover { background:#f8f8f8; }
602
+
603
+ .cf-footer {
604
+ text-align:center;
605
+ padding:12px;
606
+ border-top:1px solid #f0f0f0;
607
+ font-size:11px;
608
+ color:#bbb;
609
+ }
610
+ .cf-footer a {
611
+ color:#999;
612
+ text-decoration:none;
613
+ font-weight:500;
614
+ }
615
+ .cf-footer a:hover { color:${cfg.color}; }
616
+ `;
617
+ document.head.appendChild(style);
618
+ }
232
619
  function getPositionCSS(pos) {
233
620
  switch (pos) {
234
621
  case "bottom-left":
@@ -241,133 +628,118 @@ function getPositionCSS(pos) {
241
628
  return "bottom:20px;right:20px;";
242
629
  }
243
630
  }
244
- function getFormPositionCSS(pos) {
245
- switch (pos) {
246
- case "bottom-left":
247
- return "bottom:70px;left:20px;";
248
- case "top-right":
249
- return "top:70px;right:20px;";
250
- case "top-left":
251
- return "top:70px;left:20px;";
252
- default:
253
- return "bottom:70px;right:20px;";
254
- }
255
- }
256
- function getWidgetHTML(cfg) {
257
- const posBtn = getPositionCSS(cfg.position);
258
- const posForm = getFormPositionCSS(cfg.position);
631
+ function getTriggerHTML(cfg) {
259
632
  return `
260
- <style>
261
- #cf-trigger{position:fixed;${posBtn}z-index:100000;background:${cfg.color};color:#fff;border:none;border-radius:50px;padding:10px 18px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.15);display:flex;align-items:center;gap:6px;transition:transform .15s}
262
- #cf-trigger:hover{transform:scale(1.05)}
263
- #cf-trigger svg{width:16px;height:16px}
264
- #cf-form{position:fixed;${posForm}z-index:100000;background:#fff;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.12);width:360px;padding:16px;font-family:system-ui,-apple-system,sans-serif;display:none;flex-direction:column;gap:10px}
265
- #cf-form h3{margin:0;font-size:15px;font-weight:600;color:#111}
266
- #cf-form input,#cf-form textarea,#cf-form select{width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;font-family:inherit;box-sizing:border-box;outline:none;transition:border .15s}
267
- #cf-form input:focus,#cf-form textarea:focus,#cf-form select:focus{border-color:${cfg.color}}
268
- #cf-form textarea{resize:vertical;min-height:60px}
269
- .cf-row{display:flex;gap:8px}
270
- .cf-row select{flex:1}
271
- .cf-tools{display:flex;gap:6px}
272
- .cf-tool-btn{display:flex;align-items:center;gap:4px;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;background:#f8fafc;color:#475569;font-size:11px;cursor:pointer;font-family:inherit;transition:all .15s}
273
- .cf-tool-btn:hover{background:#f1f5f9;border-color:#cbd5e1}
274
- .cf-tool-btn svg{width:14px;height:14px}
275
- #cf-submit{background:${cfg.color};color:#fff;border:none;border-radius:8px;padding:9px;font-size:13px;font-weight:500;cursor:pointer;transition:opacity .15s}
276
- #cf-submit:hover{opacity:.9}
277
- #cf-close{position:absolute;top:10px;right:12px;background:none;border:none;cursor:pointer;font-size:18px;color:#94a3b8;line-height:1}
278
- #cf-screenshot-preview{display:none;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;max-height:120px}
279
- #cf-screenshot-preview img{width:100%;height:auto;display:block}
280
- </style>
281
- <button id="cf-trigger">
282
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
283
- ${cfg.text}
284
- </button>
285
- <div id="cf-form">
286
- <button id="cf-close">&times;</button>
287
- <h3>Report Feedback</h3>
288
- <input id="cf-title" type="text" placeholder="Title *" />
289
- <textarea id="cf-desc" placeholder="Description (optional)"></textarea>
290
- <div class="cf-row">
291
- <select id="cf-type">
292
- <option value="BUG">Bug</option>
293
- <option value="FEATURE">Feature</option>
294
- <option value="IMPROVEMENT">Improvement</option>
295
- <option value="QUESTION">Question</option>
296
- </select>
297
- <select id="cf-priority">
298
- <option value="LOW">Low</option>
299
- <option value="MEDIUM" selected>Medium</option>
300
- <option value="HIGH">High</option>
301
- <option value="CRITICAL">Critical</option>
302
- </select>
303
- </div>
304
- <div class="cf-tools">
305
- <button type="button" class="cf-tool-btn" id="cf-screenshot">
306
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
307
- Screenshot
308
- </button>
309
- <button type="button" class="cf-tool-btn" id="cf-highlight">
310
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
311
- Highlight
633
+ <button id="cf-trigger">
634
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
635
+ ${cfg.text}
312
636
  </button>
313
- </div>
314
- <div id="cf-screenshot-preview"><img src="" alt="screenshot" /></div>
315
- <button id="cf-submit">Send Feedback</button>
316
- </div>`;
637
+ `;
638
+ }
639
+ function getCheckflowLogo() {
640
+ return `<svg class="cf-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M8.65 22c.21-.66.45-1.32.57-2"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/><path d="M2 16h.01"/><path d="M21.8 16c.2-2 .131-5.354 0-6"/><path d="M9 6.8a6 6 0 0 1 9 5.2v2"/></svg>`;
641
+ }
642
+ function getModalHTML(cfg, expanded) {
643
+ const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
644
+ const formFields = `
645
+ <div class="cf-field">
646
+ <label>Nom <span>(obligatoire)</span></label>
647
+ <input id="cf-name" type="text" placeholder="Votre nom" />
648
+ </div>
649
+ <div class="cf-field">
650
+ <label>Email <span>(obligatoire)</span></label>
651
+ <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
652
+ </div>
653
+ <div class="cf-field">
654
+ <label>Description <span>(obligatoire)</span></label>
655
+ <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
656
+ </div>
657
+ `;
658
+ if (expanded && screenshotDataUrl) {
659
+ return `
660
+ <div class="${modalClass}">
661
+ <div class="cf-modal-header">
662
+ <h2>Envoyer le rapport</h2>
663
+ ${getCheckflowLogo()}
664
+ </div>
665
+ <div class="cf-modal-body">
666
+ <div class="cf-screenshot-panel">
667
+ <img src="${screenshotDataUrl}" alt="Capture d'\xE9cran" />
668
+ <div class="cf-screenshot-tools">
669
+ <button class="cf-tool-highlight" id="cf-highlight-btn">Surligner</button>
670
+ <button class="cf-tool-mask" id="cf-mask-btn">Masquer</button>
671
+ </div>
672
+ </div>
673
+ <div class="cf-form-panel">
674
+ ${formFields}
675
+ <button class="cf-remove-screenshot" id="cf-remove-screenshot">Supprimer la capture d'\xE9cran</button>
676
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
677
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
678
+ </div>
679
+ </div>
680
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
681
+ </div>
682
+ `;
683
+ }
684
+ return `
685
+ <div class="${modalClass}">
686
+ <div class="cf-modal-header">
687
+ <h2>Envoyer le rapport</h2>
688
+ ${getCheckflowLogo()}
689
+ </div>
690
+ <div class="cf-form-panel">
691
+ ${formFields}
692
+ <button class="cf-screenshot-action" id="cf-screenshot-btn">Ajouter une capture d'\xE9cran</button>
693
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
694
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
695
+ </div>
696
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
697
+ </div>
698
+ `;
317
699
  }
318
700
 
319
701
  // src/screenshot.ts
320
702
  var screenshotData = null;
321
703
  async function captureScreenshot() {
322
704
  try {
323
- if (typeof window.html2canvas === "function") {
324
- const canvas2 = await window.html2canvas(document.body, {
325
- useCORS: true,
326
- allowTaint: true,
327
- scale: Math.min(window.devicePixelRatio, 2),
328
- logging: false,
329
- width: window.innerWidth,
330
- height: window.innerHeight,
331
- x: window.scrollX,
332
- y: window.scrollY
705
+ const stream = await navigator.mediaDevices.getDisplayMedia({
706
+ video: { displaySurface: "browser" },
707
+ preferCurrentTab: true
708
+ });
709
+ const track = stream.getVideoTracks()[0];
710
+ const imageCapture = new window.ImageCapture(track);
711
+ await new Promise((r) => setTimeout(r, 300));
712
+ let bitmap;
713
+ try {
714
+ bitmap = await imageCapture.grabFrame();
715
+ } catch {
716
+ const video = document.createElement("video");
717
+ video.srcObject = stream;
718
+ video.autoplay = true;
719
+ await new Promise((resolve) => {
720
+ video.onloadedmetadata = () => {
721
+ video.play();
722
+ resolve();
723
+ };
333
724
  });
334
- screenshotData = canvas2.toDataURL("image/png", 0.8);
725
+ await new Promise((r) => setTimeout(r, 200));
726
+ const canvas2 = document.createElement("canvas");
727
+ canvas2.width = video.videoWidth;
728
+ canvas2.height = video.videoHeight;
729
+ const ctx2 = canvas2.getContext("2d");
730
+ ctx2.drawImage(video, 0, 0);
731
+ track.stop();
732
+ screenshotData = canvas2.toDataURL("image/png", 0.92);
335
733
  return screenshotData;
336
734
  }
337
735
  const canvas = document.createElement("canvas");
736
+ canvas.width = bitmap.width;
737
+ canvas.height = bitmap.height;
338
738
  const ctx = canvas.getContext("2d");
339
- if (!ctx) return null;
340
- canvas.width = window.innerWidth;
341
- canvas.height = window.innerHeight;
342
- ctx.fillStyle = "#ffffff";
343
- ctx.fillRect(0, 0, canvas.width, canvas.height);
344
- ctx.fillStyle = "#333333";
345
- ctx.font = "14px system-ui";
346
- ctx.fillText(`Page: ${window.location.href}`, 10, 30);
347
- ctx.fillText(`Viewport: ${window.innerWidth}x${window.innerHeight}`, 10, 50);
348
- ctx.fillText(`Screenshot captured at ${(/* @__PURE__ */ new Date()).toISOString()}`, 10, 70);
349
- const svgData = `
350
- <svg xmlns="http://www.w3.org/2000/svg" width="${window.innerWidth}" height="${window.innerHeight}">
351
- <foreignObject width="100%" height="100%">
352
- <div xmlns="http://www.w3.org/1999/xhtml">
353
- ${document.documentElement.outerHTML}
354
- </div>
355
- </foreignObject>
356
- </svg>`;
357
- try {
358
- const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
359
- const url = URL.createObjectURL(blob);
360
- const img = new Image();
361
- await new Promise((resolve, reject) => {
362
- img.onload = () => resolve();
363
- img.onerror = () => reject();
364
- img.src = url;
365
- });
366
- ctx.drawImage(img, 0, 0);
367
- URL.revokeObjectURL(url);
368
- } catch {
369
- }
370
- screenshotData = canvas.toDataURL("image/png", 0.8);
739
+ ctx.drawImage(bitmap, 0, 0);
740
+ bitmap.close();
741
+ track.stop();
742
+ screenshotData = canvas.toDataURL("image/png", 0.92);
371
743
  return screenshotData;
372
744
  } catch {
373
745
  return null;
@@ -376,19 +748,6 @@ async function captureScreenshot() {
376
748
  function clearScreenshot() {
377
749
  screenshotData = null;
378
750
  }
379
- function loadHtml2Canvas() {
380
- return new Promise((resolve, reject) => {
381
- if (typeof window.html2canvas === "function") {
382
- resolve();
383
- return;
384
- }
385
- const script = document.createElement("script");
386
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
387
- script.onload = () => resolve();
388
- script.onerror = () => reject(new Error("Failed to load html2canvas"));
389
- document.head.appendChild(script);
390
- });
391
- }
392
751
 
393
752
  // src/highlighter.ts
394
753
  var overlay = null;
@@ -527,10 +886,7 @@ function init(cfg) {
527
886
  };
528
887
  if (!config.enabled) return;
529
888
  installInterceptors();
530
- if (typeof window !== "undefined") {
531
- loadHtml2Canvas().catch(() => {
532
- });
533
- }
889
+ installNetworkInterceptor();
534
890
  if (typeof window !== "undefined" && config.widget?.showOnInit !== false) {
535
891
  mountWidget(
536
892
  config.widget,
@@ -541,7 +897,9 @@ function init(cfg) {
541
897
  type: data.type,
542
898
  priority: data.priority,
543
899
  screenshot_data: data.screenshot,
544
- annotations: data.annotations
900
+ annotations: data.annotations,
901
+ reporter_name: data.reporter_name,
902
+ reporter_email: data.reporter_email
545
903
  });
546
904
  },
547
905
  {
@@ -559,6 +917,7 @@ async function sendFeedback(data) {
559
917
  const perf = typeof window !== "undefined" ? collectPerformance() : void 0;
560
918
  const consoleLogs = getConsoleLogs();
561
919
  const jsErrors = getJavascriptErrors();
920
+ const networkLogs = getNetworkLogs();
562
921
  const payload = {
563
922
  ...data,
564
923
  ...context,
@@ -566,6 +925,7 @@ async function sendFeedback(data) {
566
925
  performance_metrics: perf,
567
926
  console_logs: consoleLogs.length > 0 ? consoleLogs : void 0,
568
927
  javascript_errors: jsErrors.length > 0 ? jsErrors : void 0,
928
+ network_logs: networkLogs.length > 0 ? networkLogs : void 0,
569
929
  sdk_version: SDK_VERSION
570
930
  };
571
931
  if (data.screenshot_data) {
@@ -574,6 +934,12 @@ async function sendFeedback(data) {
574
934
  if (data.annotations && data.annotations.length > 0) {
575
935
  payload.annotations = data.annotations;
576
936
  }
937
+ if (data.reporter_name) {
938
+ payload.reporter_name = data.reporter_name;
939
+ }
940
+ if (data.reporter_email) {
941
+ payload.reporter_email = data.reporter_email;
942
+ }
577
943
  try {
578
944
  const res = await fetch(`${config.endpoint}/sdk/feedback`, {
579
945
  method: "POST",
@@ -603,7 +969,9 @@ function showWidget() {
603
969
  type: data.type,
604
970
  priority: data.priority,
605
971
  screenshot_data: data.screenshot,
606
- annotations: data.annotations
972
+ annotations: data.annotations,
973
+ reporter_name: data.reporter_name,
974
+ reporter_email: data.reporter_email
607
975
  });
608
976
  },
609
977
  {
@@ -618,6 +986,7 @@ function hideWidget() {
618
986
  function destroy() {
619
987
  unmountWidget();
620
988
  clearLogs();
989
+ clearNetworkLogs();
621
990
  clearScreenshot();
622
991
  config = null;
623
992
  }