@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.mjs CHANGED
@@ -1,8 +1,7 @@
1
1
  import {
2
2
  captureScreenshot,
3
- clearScreenshot,
4
- loadHtml2Canvas
5
- } from "./chunk-CQ56DMFR.mjs";
3
+ clearScreenshot
4
+ } from "./chunk-6EVTRC5X.mjs";
6
5
  import {
7
6
  startHighlighting
8
7
  } from "./chunk-CD33QAA6.mjs";
@@ -111,6 +110,75 @@ function clearLogs() {
111
110
  errorsCapture = [];
112
111
  }
113
112
 
113
+ // src/network-interceptor.ts
114
+ var MAX_LOGS2 = 50;
115
+ var logs2 = [];
116
+ var installed2 = false;
117
+ function installNetworkInterceptor() {
118
+ if (installed2 || typeof window === "undefined") return;
119
+ installed2 = true;
120
+ const origFetch = window.fetch;
121
+ window.fetch = async function(...args) {
122
+ const start = Date.now();
123
+ const req = new Request(...args);
124
+ const entry = {
125
+ method: req.method,
126
+ url: req.url,
127
+ type: "fetch",
128
+ timestamp: start
129
+ };
130
+ try {
131
+ const res = await origFetch.apply(this, args);
132
+ entry.status = res.status;
133
+ entry.duration = Date.now() - start;
134
+ entry.response_type = res.headers.get("content-type") || void 0;
135
+ pushLog(entry);
136
+ return res;
137
+ } catch (err) {
138
+ entry.duration = Date.now() - start;
139
+ entry.error = err.message || "Network error";
140
+ pushLog(entry);
141
+ throw err;
142
+ }
143
+ };
144
+ const origOpen = XMLHttpRequest.prototype.open;
145
+ const origSend = XMLHttpRequest.prototype.send;
146
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
147
+ this.__cf_method = method;
148
+ this.__cf_url = String(url);
149
+ return origOpen.apply(this, [method, url, ...rest]);
150
+ };
151
+ XMLHttpRequest.prototype.send = function(...args) {
152
+ const start = Date.now();
153
+ const xhr = this;
154
+ xhr.addEventListener("loadend", () => {
155
+ const entry = {
156
+ method: xhr.__cf_method || "GET",
157
+ url: xhr.__cf_url || "",
158
+ status: xhr.status,
159
+ duration: Date.now() - start,
160
+ type: "xhr",
161
+ timestamp: start,
162
+ response_type: xhr.getResponseHeader("content-type") || void 0
163
+ };
164
+ if (xhr.status === 0) entry.error = "Request failed";
165
+ pushLog(entry);
166
+ });
167
+ return origSend.apply(this, args);
168
+ };
169
+ }
170
+ function pushLog(entry) {
171
+ if (entry.url.includes("/sdk/feedback")) return;
172
+ logs2.push(entry);
173
+ if (logs2.length > MAX_LOGS2) logs2.shift();
174
+ }
175
+ function getNetworkLogs() {
176
+ return [...logs2];
177
+ }
178
+ function clearNetworkLogs() {
179
+ logs2 = [];
180
+ }
181
+
114
182
  // src/widget.ts
115
183
  var DEFAULT_CONFIG = {
116
184
  position: "bottom-right",
@@ -122,78 +190,154 @@ var container = null;
122
190
  var onSubmitCallback = null;
123
191
  var screenshotDataUrl = null;
124
192
  var annotationsData = [];
193
+ var isExpanded = false;
125
194
  function mountWidget(config2 = {}, onSubmit, opts) {
126
195
  if (container) return;
127
196
  onSubmitCallback = onSubmit;
128
197
  const cfg = { ...DEFAULT_CONFIG, ...config2 };
129
198
  container = document.createElement("div");
130
199
  container.id = "checkflow-widget";
131
- container.innerHTML = getWidgetHTML(cfg);
200
+ injectStyles(cfg);
201
+ container.innerHTML = getTriggerHTML(cfg);
132
202
  document.body.appendChild(container);
133
203
  const btn = container.querySelector("#cf-trigger");
134
- const form = container.querySelector("#cf-form");
135
- const closeBtn = container.querySelector("#cf-close");
136
- const submitBtn = container.querySelector("#cf-submit");
137
- const screenshotBtn = container.querySelector("#cf-screenshot");
138
- const highlightBtn = container.querySelector("#cf-highlight");
139
- const screenshotPreview = container.querySelector("#cf-screenshot-preview");
140
- btn?.addEventListener("click", () => {
141
- form.style.display = form.style.display === "none" ? "flex" : "none";
142
- screenshotDataUrl = null;
143
- annotationsData = [];
144
- if (screenshotPreview) screenshotPreview.style.display = "none";
145
- });
146
- closeBtn?.addEventListener("click", () => {
147
- form.style.display = "none";
204
+ btn?.addEventListener("click", () => openModal(cfg, opts));
205
+ }
206
+ function openModal(cfg, opts) {
207
+ if (!container) return;
208
+ const existing = document.getElementById("cf-modal-backdrop");
209
+ if (existing) existing.remove();
210
+ isExpanded = false;
211
+ screenshotDataUrl = null;
212
+ annotationsData = [];
213
+ const backdrop = document.createElement("div");
214
+ backdrop.id = "cf-modal-backdrop";
215
+ backdrop.innerHTML = getModalHTML(cfg, false);
216
+ document.body.appendChild(backdrop);
217
+ backdrop.addEventListener("click", (e) => {
218
+ if (e.target === backdrop) closeModal();
148
219
  });
149
- screenshotBtn?.addEventListener("click", async () => {
220
+ bindModalEvents(cfg, opts);
221
+ }
222
+ function closeModal() {
223
+ const backdrop = document.getElementById("cf-modal-backdrop");
224
+ if (backdrop) backdrop.remove();
225
+ isExpanded = false;
226
+ screenshotDataUrl = null;
227
+ annotationsData = [];
228
+ }
229
+ function expandModal(cfg, opts) {
230
+ const backdrop = document.getElementById("cf-modal-backdrop");
231
+ if (!backdrop) return;
232
+ const name = backdrop.querySelector("#cf-name")?.value || "";
233
+ const email = backdrop.querySelector("#cf-email")?.value || "";
234
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
235
+ isExpanded = true;
236
+ backdrop.innerHTML = getModalHTML(cfg, true);
237
+ bindModalEvents(cfg, opts);
238
+ const nameEl = backdrop.querySelector("#cf-name");
239
+ const emailEl = backdrop.querySelector("#cf-email");
240
+ const descEl = backdrop.querySelector("#cf-desc");
241
+ if (nameEl) nameEl.value = name;
242
+ if (emailEl) emailEl.value = email;
243
+ if (descEl) descEl.value = desc;
244
+ }
245
+ function collapseModal(cfg, opts) {
246
+ const backdrop = document.getElementById("cf-modal-backdrop");
247
+ if (!backdrop) return;
248
+ const name = backdrop.querySelector("#cf-name")?.value || "";
249
+ const email = backdrop.querySelector("#cf-email")?.value || "";
250
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
251
+ isExpanded = false;
252
+ screenshotDataUrl = null;
253
+ annotationsData = [];
254
+ backdrop.innerHTML = getModalHTML(cfg, false);
255
+ bindModalEvents(cfg, opts);
256
+ const nameEl = backdrop.querySelector("#cf-name");
257
+ const emailEl = backdrop.querySelector("#cf-email");
258
+ const descEl = backdrop.querySelector("#cf-desc");
259
+ if (nameEl) nameEl.value = name;
260
+ if (emailEl) emailEl.value = email;
261
+ if (descEl) descEl.value = desc;
262
+ }
263
+ function bindModalEvents(cfg, opts) {
264
+ const backdrop = document.getElementById("cf-modal-backdrop");
265
+ if (!backdrop) return;
266
+ backdrop.querySelector("#cf-cancel")?.addEventListener("click", closeModal);
267
+ backdrop.querySelector("#cf-screenshot-btn")?.addEventListener("click", async () => {
150
268
  if (!opts?.onScreenshot) return;
151
- screenshotBtn.textContent = "Capturing...";
152
- form.style.display = "none";
269
+ backdrop.style.display = "none";
153
270
  try {
154
271
  const data = await opts.onScreenshot();
155
272
  if (data) {
156
273
  screenshotDataUrl = data;
157
- if (screenshotPreview) {
158
- screenshotPreview.querySelector("img").src = data;
159
- screenshotPreview.style.display = "block";
160
- }
274
+ backdrop.style.display = "flex";
275
+ expandModal(cfg, opts);
276
+ return;
161
277
  }
162
278
  } catch {
163
279
  }
164
- form.style.display = "flex";
165
- 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';
280
+ backdrop.style.display = "flex";
281
+ });
282
+ backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
283
+ collapseModal(cfg, opts);
166
284
  });
167
- highlightBtn?.addEventListener("click", async () => {
285
+ backdrop.querySelector("#cf-highlight-btn")?.addEventListener("click", async () => {
168
286
  if (!opts?.onHighlight) return;
169
- form.style.display = "none";
287
+ backdrop.style.display = "none";
170
288
  try {
171
289
  const annotations = await opts.onHighlight();
172
290
  if (annotations && annotations.length > 0) {
173
291
  annotationsData = annotations;
174
- 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`;
175
292
  }
176
293
  } catch {
177
294
  }
178
- form.style.display = "flex";
295
+ backdrop.style.display = "flex";
296
+ const hlBtn = backdrop.querySelector("#cf-highlight-btn");
297
+ if (hlBtn && annotationsData.length > 0) {
298
+ hlBtn.textContent = `Surligner (${annotationsData.length})`;
299
+ }
300
+ });
301
+ backdrop.querySelector("#cf-mask-btn")?.addEventListener("click", async () => {
302
+ if (!opts?.onHighlight) return;
303
+ backdrop.style.display = "none";
304
+ try {
305
+ const annotations = await opts.onHighlight();
306
+ if (annotations && annotations.length > 0) {
307
+ annotationsData = [...annotationsData, ...annotations];
308
+ }
309
+ } catch {
310
+ }
311
+ backdrop.style.display = "flex";
179
312
  });
180
- submitBtn?.addEventListener("click", () => {
181
- const title = container.querySelector("#cf-title")?.value;
182
- const desc = container.querySelector("#cf-desc")?.value;
183
- const type = container.querySelector("#cf-type")?.value;
184
- const priority = container.querySelector("#cf-priority")?.value;
185
- if (!title?.trim()) return;
186
- onSubmitCallback?.({ title, description: desc, type, priority, screenshot: screenshotDataUrl || void 0, annotations: annotationsData.length > 0 ? annotationsData : void 0 });
187
- container.querySelector("#cf-title").value = "";
188
- container.querySelector("#cf-desc").value = "";
189
- screenshotDataUrl = null;
190
- annotationsData = [];
191
- if (screenshotPreview) screenshotPreview.style.display = "none";
192
- form.style.display = "none";
193
- showToast("Feedback sent!");
313
+ backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
314
+ const desc = backdrop.querySelector("#cf-desc")?.value;
315
+ const name = backdrop.querySelector("#cf-name")?.value;
316
+ const email = backdrop.querySelector("#cf-email")?.value;
317
+ if (!desc?.trim()) {
318
+ const descEl = backdrop.querySelector("#cf-desc");
319
+ if (descEl) {
320
+ descEl.style.borderColor = "#e74c3c";
321
+ descEl.focus();
322
+ }
323
+ return;
324
+ }
325
+ onSubmitCallback?.({
326
+ title: desc.trim().slice(0, 100),
327
+ description: desc.trim(),
328
+ type: "BUG",
329
+ priority: "MEDIUM",
330
+ screenshot: screenshotDataUrl || void 0,
331
+ annotations: annotationsData.length > 0 ? annotationsData : void 0,
332
+ reporter_name: name?.trim() || void 0,
333
+ reporter_email: email?.trim() || void 0
334
+ });
335
+ closeModal();
336
+ showToast("Rapport envoy\xE9 avec succ\xE8s !");
194
337
  });
195
338
  }
196
339
  function unmountWidget() {
340
+ closeModal();
197
341
  if (container) {
198
342
  container.remove();
199
343
  container = null;
@@ -202,10 +346,252 @@ function unmountWidget() {
202
346
  function showToast(msg) {
203
347
  const toast = document.createElement("div");
204
348
  toast.textContent = msg;
205
- 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);";
349
+ 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;';
206
350
  document.body.appendChild(toast);
351
+ setTimeout(() => {
352
+ toast.style.opacity = "0";
353
+ toast.style.transition = "opacity .3s";
354
+ }, 2500);
207
355
  setTimeout(() => toast.remove(), 3e3);
208
356
  }
357
+ function injectStyles(cfg) {
358
+ const style = document.createElement("style");
359
+ style.id = "cf-widget-styles";
360
+ style.textContent = `
361
+ @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
362
+ @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
363
+
364
+ #cf-trigger {
365
+ position:fixed;
366
+ ${getPositionCSS(cfg.position)}
367
+ z-index:100000;
368
+ background:${cfg.color};
369
+ color:#fff;
370
+ border:none;
371
+ border-radius:50px;
372
+ padding:11px 20px;
373
+ font-size:14px;
374
+ font-weight:500;
375
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
376
+ cursor:pointer;
377
+ box-shadow:0 4px 16px rgba(0,0,0,.18);
378
+ display:flex;
379
+ align-items:center;
380
+ gap:8px;
381
+ transition:transform .15s,box-shadow .15s;
382
+ }
383
+ #cf-trigger:hover { transform:scale(1.04); box-shadow:0 6px 24px rgba(0,0,0,.22); }
384
+ #cf-trigger svg { width:18px; height:18px; }
385
+
386
+ #cf-modal-backdrop {
387
+ position:fixed;
388
+ top:0;left:0;right:0;bottom:0;
389
+ background:rgba(0,0,0,.45);
390
+ z-index:100001;
391
+ display:flex;
392
+ align-items:center;
393
+ justify-content:center;
394
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
395
+ }
396
+
397
+ .cf-modal {
398
+ background:#fff;
399
+ border-radius:16px;
400
+ box-shadow:0 24px 80px rgba(0,0,0,.25);
401
+ overflow:hidden;
402
+ animation:cf-modal-in .25s ease;
403
+ display:flex;
404
+ flex-direction:column;
405
+ max-height:90vh;
406
+ }
407
+ .cf-modal--compact { width:420px; }
408
+ .cf-modal--expanded { width:min(1100px,92vw); flex-direction:column; }
409
+
410
+ .cf-modal-header {
411
+ display:flex;
412
+ align-items:center;
413
+ justify-content:space-between;
414
+ padding:20px 24px 16px;
415
+ border-bottom:1px solid #f0f0f0;
416
+ }
417
+ .cf-modal-header h2 {
418
+ margin:0;
419
+ font-size:20px;
420
+ font-weight:700;
421
+ color:#1a1a2e;
422
+ }
423
+ .cf-modal-header .cf-logo {
424
+ width:28px;
425
+ height:28px;
426
+ color:${cfg.color};
427
+ }
428
+
429
+ .cf-modal-body { display:flex; flex:1; overflow:hidden; }
430
+
431
+ .cf-screenshot-panel {
432
+ flex:1;
433
+ min-width:0;
434
+ background:#1a1a2e;
435
+ display:flex;
436
+ flex-direction:column;
437
+ position:relative;
438
+ }
439
+ .cf-screenshot-panel img {
440
+ width:100%;
441
+ height:100%;
442
+ object-fit:contain;
443
+ max-height:60vh;
444
+ }
445
+ .cf-screenshot-tools {
446
+ position:absolute;
447
+ bottom:16px;
448
+ left:50%;
449
+ transform:translateX(-50%);
450
+ display:flex;
451
+ gap:8px;
452
+ }
453
+ .cf-screenshot-tools button {
454
+ padding:8px 18px;
455
+ border-radius:8px;
456
+ font-size:13px;
457
+ font-weight:500;
458
+ cursor:pointer;
459
+ font-family:inherit;
460
+ border:none;
461
+ transition:all .15s;
462
+ }
463
+ .cf-tool-highlight {
464
+ background:${cfg.color};
465
+ color:#fff;
466
+ }
467
+ .cf-tool-highlight:hover { opacity:.9; }
468
+ .cf-tool-mask {
469
+ background:#fff;
470
+ color:#1a1a2e;
471
+ border:1px solid #e0e0e0 !important;
472
+ }
473
+ .cf-tool-mask:hover { background:#f5f5f5; }
474
+
475
+ .cf-form-panel {
476
+ width:100%;
477
+ padding:20px 24px;
478
+ display:flex;
479
+ flex-direction:column;
480
+ gap:14px;
481
+ overflow-y:auto;
482
+ }
483
+ .cf-modal--expanded .cf-form-panel {
484
+ width:340px;
485
+ flex-shrink:0;
486
+ border-left:1px solid #f0f0f0;
487
+ }
488
+
489
+ .cf-field label {
490
+ display:block;
491
+ font-size:13px;
492
+ font-weight:500;
493
+ color:#1a1a2e;
494
+ margin-bottom:6px;
495
+ }
496
+ .cf-field label span { font-weight:400; color:#999; }
497
+ .cf-field input, .cf-field textarea {
498
+ width:100%;
499
+ padding:10px 14px;
500
+ border:1px solid #e0e0e0;
501
+ border-radius:10px;
502
+ font-size:14px;
503
+ font-family:inherit;
504
+ box-sizing:border-box;
505
+ outline:none;
506
+ transition:border-color .15s,box-shadow .15s;
507
+ color:#1a1a2e;
508
+ background:#fff;
509
+ }
510
+ .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
511
+ .cf-field input:focus, .cf-field textarea:focus {
512
+ border-color:${cfg.color};
513
+ box-shadow:0 0 0 3px ${cfg.color}18;
514
+ }
515
+ .cf-field textarea { resize:vertical; min-height:100px; }
516
+
517
+ .cf-screenshot-action {
518
+ width:100%;
519
+ padding:12px;
520
+ border:1px solid #e0e0e0;
521
+ border-radius:10px;
522
+ background:#fff;
523
+ font-size:14px;
524
+ color:#1a1a2e;
525
+ cursor:pointer;
526
+ font-family:inherit;
527
+ transition:all .15s;
528
+ text-align:center;
529
+ }
530
+ .cf-screenshot-action:hover { background:#f8f8f8; border-color:#ccc; }
531
+
532
+ .cf-remove-screenshot {
533
+ width:100%;
534
+ padding:10px;
535
+ border:1px solid #e0e0e0;
536
+ border-radius:10px;
537
+ background:#fff;
538
+ font-size:13px;
539
+ color:#666;
540
+ cursor:pointer;
541
+ font-family:inherit;
542
+ transition:all .15s;
543
+ text-align:center;
544
+ }
545
+ .cf-remove-screenshot:hover { background:#fff0f0; color:#e74c3c; border-color:#e74c3c; }
546
+
547
+ .cf-submit-btn {
548
+ width:100%;
549
+ padding:13px;
550
+ border:none;
551
+ border-radius:10px;
552
+ background:${cfg.color};
553
+ color:#fff;
554
+ font-size:15px;
555
+ font-weight:600;
556
+ cursor:pointer;
557
+ font-family:inherit;
558
+ transition:all .15s;
559
+ }
560
+ .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${cfg.color}40; }
561
+ .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
562
+
563
+ .cf-cancel-btn {
564
+ width:100%;
565
+ padding:11px;
566
+ border:1px solid #e0e0e0;
567
+ border-radius:10px;
568
+ background:#fff;
569
+ color:#1a1a2e;
570
+ font-size:14px;
571
+ font-weight:500;
572
+ cursor:pointer;
573
+ font-family:inherit;
574
+ transition:all .15s;
575
+ text-align:center;
576
+ }
577
+ .cf-cancel-btn:hover { background:#f8f8f8; }
578
+
579
+ .cf-footer {
580
+ text-align:center;
581
+ padding:12px;
582
+ border-top:1px solid #f0f0f0;
583
+ font-size:11px;
584
+ color:#bbb;
585
+ }
586
+ .cf-footer a {
587
+ color:#999;
588
+ text-decoration:none;
589
+ font-weight:500;
590
+ }
591
+ .cf-footer a:hover { color:${cfg.color}; }
592
+ `;
593
+ document.head.appendChild(style);
594
+ }
209
595
  function getPositionCSS(pos) {
210
596
  switch (pos) {
211
597
  case "bottom-left":
@@ -218,79 +604,74 @@ function getPositionCSS(pos) {
218
604
  return "bottom:20px;right:20px;";
219
605
  }
220
606
  }
221
- function getFormPositionCSS(pos) {
222
- switch (pos) {
223
- case "bottom-left":
224
- return "bottom:70px;left:20px;";
225
- case "top-right":
226
- return "top:70px;right:20px;";
227
- case "top-left":
228
- return "top:70px;left:20px;";
229
- default:
230
- return "bottom:70px;right:20px;";
231
- }
232
- }
233
- function getWidgetHTML(cfg) {
234
- const posBtn = getPositionCSS(cfg.position);
235
- const posForm = getFormPositionCSS(cfg.position);
607
+ function getTriggerHTML(cfg) {
236
608
  return `
237
- <style>
238
- #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}
239
- #cf-trigger:hover{transform:scale(1.05)}
240
- #cf-trigger svg{width:16px;height:16px}
241
- #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}
242
- #cf-form h3{margin:0;font-size:15px;font-weight:600;color:#111}
243
- #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}
244
- #cf-form input:focus,#cf-form textarea:focus,#cf-form select:focus{border-color:${cfg.color}}
245
- #cf-form textarea{resize:vertical;min-height:60px}
246
- .cf-row{display:flex;gap:8px}
247
- .cf-row select{flex:1}
248
- .cf-tools{display:flex;gap:6px}
249
- .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}
250
- .cf-tool-btn:hover{background:#f1f5f9;border-color:#cbd5e1}
251
- .cf-tool-btn svg{width:14px;height:14px}
252
- #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}
253
- #cf-submit:hover{opacity:.9}
254
- #cf-close{position:absolute;top:10px;right:12px;background:none;border:none;cursor:pointer;font-size:18px;color:#94a3b8;line-height:1}
255
- #cf-screenshot-preview{display:none;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;max-height:120px}
256
- #cf-screenshot-preview img{width:100%;height:auto;display:block}
257
- </style>
258
- <button id="cf-trigger">
259
- <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>
260
- ${cfg.text}
261
- </button>
262
- <div id="cf-form">
263
- <button id="cf-close">&times;</button>
264
- <h3>Report Feedback</h3>
265
- <input id="cf-title" type="text" placeholder="Title *" />
266
- <textarea id="cf-desc" placeholder="Description (optional)"></textarea>
267
- <div class="cf-row">
268
- <select id="cf-type">
269
- <option value="BUG">Bug</option>
270
- <option value="FEATURE">Feature</option>
271
- <option value="IMPROVEMENT">Improvement</option>
272
- <option value="QUESTION">Question</option>
273
- </select>
274
- <select id="cf-priority">
275
- <option value="LOW">Low</option>
276
- <option value="MEDIUM" selected>Medium</option>
277
- <option value="HIGH">High</option>
278
- <option value="CRITICAL">Critical</option>
279
- </select>
280
- </div>
281
- <div class="cf-tools">
282
- <button type="button" class="cf-tool-btn" id="cf-screenshot">
283
- <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>
284
- Screenshot
285
- </button>
286
- <button type="button" class="cf-tool-btn" id="cf-highlight">
287
- <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>
288
- Highlight
609
+ <button id="cf-trigger">
610
+ <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>
611
+ ${cfg.text}
289
612
  </button>
290
- </div>
291
- <div id="cf-screenshot-preview"><img src="" alt="screenshot" /></div>
292
- <button id="cf-submit">Send Feedback</button>
293
- </div>`;
613
+ `;
614
+ }
615
+ function getCheckflowLogo() {
616
+ 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>`;
617
+ }
618
+ function getModalHTML(cfg, expanded) {
619
+ const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
620
+ const formFields = `
621
+ <div class="cf-field">
622
+ <label>Nom <span>(obligatoire)</span></label>
623
+ <input id="cf-name" type="text" placeholder="Votre nom" />
624
+ </div>
625
+ <div class="cf-field">
626
+ <label>Email <span>(obligatoire)</span></label>
627
+ <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
628
+ </div>
629
+ <div class="cf-field">
630
+ <label>Description <span>(obligatoire)</span></label>
631
+ <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
632
+ </div>
633
+ `;
634
+ if (expanded && screenshotDataUrl) {
635
+ return `
636
+ <div class="${modalClass}">
637
+ <div class="cf-modal-header">
638
+ <h2>Envoyer le rapport</h2>
639
+ ${getCheckflowLogo()}
640
+ </div>
641
+ <div class="cf-modal-body">
642
+ <div class="cf-screenshot-panel">
643
+ <img src="${screenshotDataUrl}" alt="Capture d'\xE9cran" />
644
+ <div class="cf-screenshot-tools">
645
+ <button class="cf-tool-highlight" id="cf-highlight-btn">Surligner</button>
646
+ <button class="cf-tool-mask" id="cf-mask-btn">Masquer</button>
647
+ </div>
648
+ </div>
649
+ <div class="cf-form-panel">
650
+ ${formFields}
651
+ <button class="cf-remove-screenshot" id="cf-remove-screenshot">Supprimer la capture d'\xE9cran</button>
652
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
653
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
654
+ </div>
655
+ </div>
656
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
657
+ </div>
658
+ `;
659
+ }
660
+ return `
661
+ <div class="${modalClass}">
662
+ <div class="cf-modal-header">
663
+ <h2>Envoyer le rapport</h2>
664
+ ${getCheckflowLogo()}
665
+ </div>
666
+ <div class="cf-form-panel">
667
+ ${formFields}
668
+ <button class="cf-screenshot-action" id="cf-screenshot-btn">Ajouter une capture d'\xE9cran</button>
669
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
670
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
671
+ </div>
672
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
673
+ </div>
674
+ `;
294
675
  }
295
676
 
296
677
  // src/index.ts
@@ -306,10 +687,7 @@ function init(cfg) {
306
687
  };
307
688
  if (!config.enabled) return;
308
689
  installInterceptors();
309
- if (typeof window !== "undefined") {
310
- loadHtml2Canvas().catch(() => {
311
- });
312
- }
690
+ installNetworkInterceptor();
313
691
  if (typeof window !== "undefined" && config.widget?.showOnInit !== false) {
314
692
  mountWidget(
315
693
  config.widget,
@@ -320,7 +698,9 @@ function init(cfg) {
320
698
  type: data.type,
321
699
  priority: data.priority,
322
700
  screenshot_data: data.screenshot,
323
- annotations: data.annotations
701
+ annotations: data.annotations,
702
+ reporter_name: data.reporter_name,
703
+ reporter_email: data.reporter_email
324
704
  });
325
705
  },
326
706
  {
@@ -338,6 +718,7 @@ async function sendFeedback(data) {
338
718
  const perf = typeof window !== "undefined" ? collectPerformance() : void 0;
339
719
  const consoleLogs = getConsoleLogs();
340
720
  const jsErrors = getJavascriptErrors();
721
+ const networkLogs = getNetworkLogs();
341
722
  const payload = {
342
723
  ...data,
343
724
  ...context,
@@ -345,6 +726,7 @@ async function sendFeedback(data) {
345
726
  performance_metrics: perf,
346
727
  console_logs: consoleLogs.length > 0 ? consoleLogs : void 0,
347
728
  javascript_errors: jsErrors.length > 0 ? jsErrors : void 0,
729
+ network_logs: networkLogs.length > 0 ? networkLogs : void 0,
348
730
  sdk_version: SDK_VERSION
349
731
  };
350
732
  if (data.screenshot_data) {
@@ -353,6 +735,12 @@ async function sendFeedback(data) {
353
735
  if (data.annotations && data.annotations.length > 0) {
354
736
  payload.annotations = data.annotations;
355
737
  }
738
+ if (data.reporter_name) {
739
+ payload.reporter_name = data.reporter_name;
740
+ }
741
+ if (data.reporter_email) {
742
+ payload.reporter_email = data.reporter_email;
743
+ }
356
744
  try {
357
745
  const res = await fetch(`${config.endpoint}/sdk/feedback`, {
358
746
  method: "POST",
@@ -382,7 +770,9 @@ function showWidget() {
382
770
  type: data.type,
383
771
  priority: data.priority,
384
772
  screenshot_data: data.screenshot,
385
- annotations: data.annotations
773
+ annotations: data.annotations,
774
+ reporter_name: data.reporter_name,
775
+ reporter_email: data.reporter_email
386
776
  });
387
777
  },
388
778
  {
@@ -397,6 +787,7 @@ function hideWidget() {
397
787
  function destroy() {
398
788
  unmountWidget();
399
789
  clearLogs();
790
+ clearNetworkLogs();
400
791
  clearScreenshot();
401
792
  config = null;
402
793
  }