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