@triagly/sdk 0.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,11 @@
2
2
 
3
3
  > JavaScript SDK for Triagly - Turn user feedback into GitHub issues instantly.
4
4
 
5
- Lightweight (<10KB gzipped) browser widget that captures user feedback with screenshots and automatically creates GitHub issues.
5
+ Lightweight (<10KB gzipped) browser widget that captures user feedback and automatically creates issues in your tracker.
6
6
 
7
7
  ## Features
8
8
 
9
9
  - 🎨 **Beautiful UI Widget** - Clean, customizable feedback form
10
- - 📸 **Screenshot Capture** - Automatic page screenshots (with html2canvas)
11
10
  - 🐛 **Console Log Capture** - Automatically captures browser console errors and warnings for debugging
12
11
  - 🤖 **Bot Protection** - Cloudflare Turnstile integration to prevent spam
13
12
  - 🔒 **Secure Authentication** - Uses publishable keys with optional hardened mode
@@ -34,9 +33,6 @@ yarn add @triagly/sdk
34
33
  <!-- Include Cloudflare Turnstile for bot protection -->
35
34
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
36
35
 
37
- <!-- Include html2canvas for screenshot support (optional) -->
38
- <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
39
-
40
36
  <!-- Include Triagly SDK -->
41
37
  <script src="https://unpkg.com/@triagly/sdk/dist/index.min.js"></script>
42
38
  ```
@@ -419,25 +415,6 @@ try {
419
415
 
420
416
  See [Styling Guide](./docs/guides/STYLING.md) for all available variables and more examples.
421
417
 
422
- ## Screenshot Support
423
-
424
- To enable screenshot capture, include html2canvas:
425
-
426
- ```html
427
- <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
428
- ```
429
-
430
- Or install via NPM and import in your bundle:
431
-
432
- ```bash
433
- npm install html2canvas
434
- ```
435
-
436
- ```typescript
437
- import html2canvas from 'html2canvas';
438
- (window as any).html2canvas = html2canvas;
439
- ```
440
-
441
418
  ## Console Log Capture
442
419
 
443
420
  The SDK automatically captures browser console messages (errors and warnings by default) to help developers debug issues.
package/dist/api.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { FeedbackData, FeedbackMetadata, FeedbackResponse } from './types';
2
+ export type Environment = 'production' | 'staging';
2
3
  export declare class TriaglyAPI {
3
4
  private apiUrl;
4
5
  private publishableKey;
5
6
  private getToken?;
6
7
  private turnstileSiteKey;
7
8
  private static readonly DEFAULT_TURNSTILE_SITE_KEY;
8
- constructor(publishableKey: string, apiUrl?: string, getToken?: () => Promise<string>, turnstileSiteKey?: string);
9
+ constructor(publishableKey: string, environment?: Environment, apiUrl?: string, getToken?: () => Promise<string>, turnstileSiteKey?: string);
9
10
  /**
10
11
  * Get the Turnstile site key
11
12
  */
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAIjB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IAGjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAA8B;gBAG9E,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAS3B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC;CA2E7B"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAOjB,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAEnD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAAS;IAGjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAA8B;gBAG9E,cAAc,EAAE,MAAM,EACtB,WAAW,GAAE,WAA0B,EACvC,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,EAChC,gBAAgB,CAAC,EAAE,MAAM;IAU3B;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACG,cAAc,CAClB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC;CA0E7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,YAAY,EACb,MAAM,SAAS,CAAC;AAKjB,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAA8B;gBAEvC,MAAM,EAAE,aAAa;IAwDjC;;OAEG;IACH,OAAO,CAAC,IAAI;IAkBZ;;OAEG;YACW,YAAY;IAgE1B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxE;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB;AAGD,cAAc,SAAS,CAAC;AAexB,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,YAAY,EACb,MAAM,SAAS,CAAC;AAKjB,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAA8B;gBAEvC,MAAM,EAAE,aAAa;IAyDjC;;OAEG;IACH,OAAO,CAAC,IAAI;IAkBZ;;OAEG;YACW,YAAY;IAuD1B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBxE;;OAEG;IACH,OAAO,IAAI,IAAI;CAKhB;AAGD,cAAc,SAAS,CAAC;AAexB,eAAe,OAAO,CAAC"}
package/dist/index.esm.js CHANGED
@@ -45,26 +45,23 @@ class FeedbackWidget {
45
45
  // Button orientation
46
46
  const orientation = this.config.orientation || 'horizontal';
47
47
  button.classList.add(`triagly-orientation-${orientation}`);
48
+ // Speech bubble SVG icon
49
+ const speechBubbleIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
50
+ const largeIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
48
51
  // Handle button text based on shape
49
- const fullText = this.config.buttonText || '🐛 Feedback';
52
+ const fullText = this.config.buttonText || 'Feedback';
50
53
  if (shape === 'circular') {
51
- button.innerHTML = '🐛';
54
+ button.innerHTML = largeIcon;
52
55
  button.setAttribute('aria-label', fullText);
53
56
  }
54
57
  else if (shape === 'expandable') {
55
- // Expandable starts with emoji, expands to full text on hover
56
- button.innerHTML = '<span class="triagly-btn-icon">🐛</span><span class="triagly-btn-text"> Feedback</span>';
58
+ // Expandable starts with icon, expands to full text on hover
59
+ button.innerHTML = `<span class="triagly-btn-icon">${largeIcon}</span><span class="triagly-btn-text"> ${this.config.buttonText || 'Feedback'}</span>`;
57
60
  button.setAttribute('aria-label', fullText);
58
- // Store custom text if provided
59
- if (this.config.buttonText) {
60
- const textSpan = button.querySelector('.triagly-btn-text');
61
- if (textSpan) {
62
- textSpan.textContent = ' ' + this.config.buttonText.replace('🐛', '').trim();
63
- }
64
- }
65
61
  }
66
62
  else {
67
- button.innerHTML = fullText;
63
+ // Default: icon + text
64
+ button.innerHTML = `${speechBubbleIcon}<span class="triagly-btn-label">${fullText}</span>`;
68
65
  }
69
66
  button.onclick = () => this.toggle();
70
67
  // Position button
@@ -130,9 +127,9 @@ class FeedbackWidget {
130
127
  this.setupKeyboardEvents();
131
128
  // Set up focus trap
132
129
  this.setupFocusTrap();
133
- // Focus on title field
134
- const titleInput = this.container?.querySelector('input[type="text"]');
135
- titleInput?.focus();
130
+ // Focus on description field
131
+ const descInput = this.container?.querySelector('#triagly-description');
132
+ descInput?.focus();
136
133
  }, 0);
137
134
  }
138
135
  /**
@@ -177,7 +174,7 @@ class FeedbackWidget {
177
174
  overlay.className = 'triagly-overlay';
178
175
  overlay.setAttribute('role', 'dialog');
179
176
  overlay.setAttribute('aria-modal', 'true');
180
- overlay.setAttribute('aria-labelledby', 'triagly-modal-title');
177
+ overlay.setAttribute('aria-label', 'Send feedback');
181
178
  overlay.onclick = (e) => {
182
179
  if (e.target === overlay)
183
180
  this.close('overlay');
@@ -188,7 +185,6 @@ class FeedbackWidget {
188
185
  const header = document.createElement('div');
189
186
  header.className = 'triagly-header';
190
187
  header.innerHTML = `
191
- <h3 id="triagly-modal-title">Send Feedback</h3>
192
188
  <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>
193
189
  `;
194
190
  const closeBtn = header.querySelector('.triagly-close');
@@ -197,16 +193,7 @@ class FeedbackWidget {
197
193
  form.className = 'triagly-form';
198
194
  form.innerHTML = `
199
195
  <div class="triagly-field">
200
- <label for="triagly-title">Title (optional)</label>
201
- <input
202
- type="text"
203
- id="triagly-title"
204
- placeholder="Brief summary of your feedback"
205
- />
206
- </div>
207
-
208
- <div class="triagly-field">
209
- <label for="triagly-description">Description *</label>
196
+ <label for="triagly-description">What's on your mind?</label>
210
197
  <textarea
211
198
  id="triagly-description"
212
199
  required
@@ -215,6 +202,15 @@ class FeedbackWidget {
215
202
  ></textarea>
216
203
  </div>
217
204
 
205
+ <div class="triagly-field">
206
+ <label for="triagly-name">Name (optional)</label>
207
+ <input
208
+ type="text"
209
+ id="triagly-name"
210
+ placeholder="Your name"
211
+ />
212
+ </div>
213
+
218
214
  <div class="triagly-field">
219
215
  <label for="triagly-email">Email (optional)</label>
220
216
  <input
@@ -224,13 +220,6 @@ class FeedbackWidget {
224
220
  />
225
221
  </div>
226
222
 
227
- <div class="triagly-field triagly-checkbox">
228
- <label>
229
- <input type="checkbox" id="triagly-screenshot" checked />
230
- <span>Include screenshot</span>
231
- </label>
232
- </div>
233
-
234
223
  ${this.config.turnstileSiteKey ? `
235
224
  <div class="triagly-field triagly-turnstile">
236
225
  <div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>
@@ -254,8 +243,16 @@ class FeedbackWidget {
254
243
  e.preventDefault();
255
244
  this.handleSubmit(form);
256
245
  };
246
+ const footer = document.createElement('div');
247
+ footer.className = 'triagly-footer';
248
+ footer.innerHTML = `
249
+ <a href="https://triagly.com" target="_blank" rel="noopener noreferrer" class="triagly-branding">
250
+ Powered by <strong>Triagly</strong>
251
+ </a>
252
+ `;
257
253
  modal.appendChild(header);
258
254
  modal.appendChild(form);
255
+ modal.appendChild(footer);
259
256
  overlay.appendChild(modal);
260
257
  // Render Turnstile widget if available
261
258
  if (this.config.turnstileSiteKey) {
@@ -297,17 +294,16 @@ class FeedbackWidget {
297
294
  turnstileContainer.setAttribute('data-widget-id', widgetId);
298
295
  }
299
296
  catch (error) {
300
- console.error('Triagly: Failed to render Turnstile widget:', error);
297
+ console.error('Triagly: Failed to render Turnstile widget:', error instanceof Error ? error.message : 'Unknown error');
301
298
  }
302
299
  }
303
300
  /**
304
301
  * Handle form submission
305
302
  */
306
303
  async handleSubmit(form) {
307
- const titleInput = form.querySelector('#triagly-title');
308
304
  const descInput = form.querySelector('#triagly-description');
305
+ const nameInput = form.querySelector('#triagly-name');
309
306
  const emailInput = form.querySelector('#triagly-email');
310
- const screenshotCheckbox = form.querySelector('#triagly-screenshot');
311
307
  const statusDiv = form.querySelector('#triagly-status');
312
308
  const submitBtn = form.querySelector('button[type="submit"]');
313
309
  const turnstileContainer = form.querySelector('.cf-turnstile');
@@ -321,10 +317,9 @@ class FeedbackWidget {
321
317
  turnstileToken = turnstileContainer.getAttribute('data-turnstile-response') || undefined;
322
318
  }
323
319
  const data = {
324
- title: titleInput.value.trim() || undefined,
325
320
  description: descInput.value.trim(),
321
+ reporterName: nameInput.value.trim() || undefined,
326
322
  reporterEmail: emailInput.value.trim() || undefined,
327
- includeScreenshot: screenshotCheckbox.checked,
328
323
  turnstileToken,
329
324
  };
330
325
  // Create a promise that waits for actual submission result
@@ -388,21 +383,32 @@ class FeedbackWidget {
388
383
  position: fixed;
389
384
  z-index: 999999;
390
385
  padding: 12px 20px;
391
- background: var(--triagly-button-bg, #6366f1);
386
+ background: var(--triagly-button-bg, #18181b);
392
387
  color: var(--triagly-button-text, #ffffff);
393
388
  border: none;
394
389
  border-radius: var(--triagly-button-radius, 8px);
395
390
  font-size: 14px;
396
391
  font-weight: 500;
397
392
  cursor: pointer;
398
- box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(99, 102, 241, 0.3));
393
+ box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));
399
394
  transition: all 0.2s;
395
+ display: inline-flex;
396
+ align-items: center;
397
+ gap: 8px;
400
398
  }
401
399
 
402
400
  .triagly-button:hover {
403
- background: var(--triagly-button-bg-hover, #4f46e5);
401
+ background: var(--triagly-button-bg-hover, #27272a);
404
402
  transform: translateY(-2px);
405
- box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(99, 102, 241, 0.4));
403
+ box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));
404
+ }
405
+
406
+ .triagly-button .triagly-icon {
407
+ flex-shrink: 0;
408
+ }
409
+
410
+ .triagly-button .triagly-btn-label {
411
+ line-height: 1;
406
412
  }
407
413
 
408
414
  /* Prevent expandable buttons from shifting on hover */
@@ -496,8 +502,8 @@ class FeedbackWidget {
496
502
  min-width: auto;
497
503
  padding: 12px 20px;
498
504
  border-radius: 30px;
499
- background: var(--triagly-button-bg-hover, #4f46e5);
500
- box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(99, 102, 241, 0.4));
505
+ background: var(--triagly-button-bg-hover, #27272a);
506
+ box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));
501
507
  }
502
508
  .triagly-shape-expandable:hover .triagly-btn-text {
503
509
  width: auto;
@@ -556,18 +562,10 @@ class FeedbackWidget {
556
562
 
557
563
  .triagly-header {
558
564
  display: flex;
559
- justify-content: space-between;
565
+ justify-content: flex-end;
560
566
  align-items: center;
561
- padding: 20px 24px;
567
+ padding: 8px 12px 0;
562
568
  background: var(--triagly-header-bg, #ffffff);
563
- border-bottom: 1px solid var(--triagly-header-border, #e5e7eb);
564
- }
565
-
566
- .triagly-header h3 {
567
- margin: 0;
568
- font-size: 18px;
569
- font-weight: 600;
570
- color: var(--triagly-header-text, #111827);
571
569
  }
572
570
 
573
571
  .triagly-close {
@@ -625,46 +623,21 @@ class FeedbackWidget {
625
623
  .triagly-field input:focus,
626
624
  .triagly-field textarea:focus {
627
625
  outline: none;
628
- border-color: var(--triagly-input-border-focus, #6366f1);
629
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
630
- }
631
-
632
- .triagly-checkbox label {
633
- display: flex;
634
- align-items: center;
635
- gap: 8px;
636
- cursor: pointer;
637
- font-weight: 400;
638
- }
639
-
640
- .triagly-checkbox label span {
641
- user-select: none;
642
- }
643
-
644
- .triagly-checkbox input {
645
- width: 16px;
646
- height: 16px;
647
- margin: 0;
648
- cursor: pointer;
626
+ border-color: var(--triagly-input-border-focus, #a1a1aa);
627
+ box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);
649
628
  }
650
629
 
651
630
  /* Focus visible styles for accessibility */
652
631
  .triagly-button:focus-visible,
653
632
  .triagly-field input:focus-visible,
654
633
  .triagly-field textarea:focus-visible,
655
- .triagly-checkbox input:focus-visible,
656
634
  .triagly-btn-primary:focus-visible,
657
635
  .triagly-btn-secondary:focus-visible,
658
636
  .triagly-close:focus-visible {
659
- outline: 2px solid #6366f1;
637
+ outline: 2px solid #a1a1aa;
660
638
  outline-offset: 2px;
661
639
  }
662
640
 
663
- /* Checkbox label gets visual indicator when checkbox is focused */
664
- .triagly-checkbox input:focus-visible + span {
665
- text-decoration: underline;
666
- }
667
-
668
641
  .triagly-turnstile {
669
642
  display: flex;
670
643
  justify-content: center;
@@ -690,12 +663,12 @@ class FeedbackWidget {
690
663
  }
691
664
 
692
665
  .triagly-btn-primary {
693
- background: var(--triagly-btn-primary-bg, #6366f1);
666
+ background: var(--triagly-btn-primary-bg, #18181b);
694
667
  color: var(--triagly-btn-primary-text, #ffffff);
695
668
  }
696
669
 
697
670
  .triagly-btn-primary:hover:not(:disabled) {
698
- background: var(--triagly-btn-primary-bg-hover, #4f46e5);
671
+ background: var(--triagly-btn-primary-bg-hover, #27272a);
699
672
  }
700
673
 
701
674
  .triagly-btn-primary:disabled {
@@ -731,6 +704,29 @@ class FeedbackWidget {
731
704
  background: var(--triagly-error-bg, #fee2e2);
732
705
  color: var(--triagly-error-text, #991b1b);
733
706
  }
707
+
708
+ .triagly-footer {
709
+ padding: 12px 24px 16px;
710
+ text-align: right;
711
+ border-top: 1px solid var(--triagly-footer-border, #e5e7eb);
712
+ background: var(--triagly-footer-bg, #f9fafb);
713
+ border-radius: 0 0 var(--triagly-modal-radius, 12px) var(--triagly-modal-radius, 12px);
714
+ }
715
+
716
+ .triagly-branding {
717
+ font-size: 12px;
718
+ color: var(--triagly-footer-text, #6b7280);
719
+ text-decoration: none;
720
+ transition: color 0.2s;
721
+ }
722
+
723
+ .triagly-branding:hover {
724
+ color: var(--triagly-footer-text-hover, #18181b);
725
+ }
726
+
727
+ .triagly-branding strong {
728
+ font-weight: 600;
729
+ }
734
730
  `;
735
731
  document.head.appendChild(style);
736
732
  }
@@ -818,10 +814,14 @@ class FeedbackWidget {
818
814
  }
819
815
 
820
816
  // API Client
821
- const DEFAULT_API_URL = 'https://bssghvinezdawvupcyci.supabase.co/functions/v1';
817
+ const API_URLS = {
818
+ production: 'https://iipkklhhafrjesryscjh.supabase.co/functions/v1',
819
+ staging: 'https://bssghvinezdawvupcyci.supabase.co/functions/v1',
820
+ };
822
821
  class TriaglyAPI {
823
- constructor(publishableKey, apiUrl, getToken, turnstileSiteKey) {
824
- this.apiUrl = (apiUrl || DEFAULT_API_URL).replace(/\/$/, ''); // Remove trailing slash
822
+ constructor(publishableKey, environment = 'production', apiUrl, getToken, turnstileSiteKey) {
823
+ // apiUrl override takes precedence, then environment-based URL
824
+ this.apiUrl = (apiUrl || API_URLS[environment]).replace(/\/$/, ''); // Remove trailing slash
825
825
  this.publishableKey = publishableKey;
826
826
  this.getToken = getToken;
827
827
  // Always use Triagly's Turnstile site key (can be overridden for testing)
@@ -859,7 +859,7 @@ class TriaglyAPI {
859
859
  }
860
860
  }
861
861
  catch (error) {
862
- console.warn('Failed to get Turnstile token:', error);
862
+ console.warn('Failed to get Turnstile token:', error instanceof Error ? error.message : 'Unknown error');
863
863
  }
864
864
  }
865
865
  return null;
@@ -883,7 +883,7 @@ class TriaglyAPI {
883
883
  hardenedToken = await this.getToken();
884
884
  }
885
885
  catch (error) {
886
- console.error('Failed to get hardened token:', error);
886
+ console.error('Failed to get hardened token:', error instanceof Error ? error.message : 'Unknown error');
887
887
  throw new Error('Failed to authenticate. Please try again.');
888
888
  }
889
889
  }
@@ -896,7 +896,6 @@ class TriaglyAPI {
896
896
  consoleLogs: data.consoleLogs,
897
897
  },
898
898
  tags: data.tags,
899
- screenshot: data.screenshot,
900
899
  reporterEmail: data.reporterEmail,
901
900
  reporterName: data.reporterName,
902
901
  turnstileToken,
@@ -983,34 +982,6 @@ function detectBrowser() {
983
982
  }
984
983
  return browser;
985
984
  }
986
- /**
987
- * Capture screenshot of current page
988
- */
989
- async function captureScreenshot() {
990
- try {
991
- // Use html2canvas library if available
992
- if (typeof window.html2canvas !== 'undefined') {
993
- const canvas = await window.html2canvas(document.body, {
994
- logging: false,
995
- useCORS: true,
996
- allowTaint: true,
997
- });
998
- return canvas.toDataURL('image/png');
999
- }
1000
- // Fallback to native screenshot API if supported (limited browser support)
1001
- if ('mediaDevices' in navigator && 'getDisplayMedia' in navigator.mediaDevices) {
1002
- // This requires user interaction and shows a permission dialog
1003
- // Not ideal for automatic screenshots
1004
- console.warn('Screenshot capture requires html2canvas library');
1005
- return null;
1006
- }
1007
- return null;
1008
- }
1009
- catch (error) {
1010
- console.error('Screenshot capture failed:', error);
1011
- return null;
1012
- }
1013
- }
1014
985
  /**
1015
986
  * Simple rate limiter using localStorage
1016
987
  */
@@ -1063,7 +1034,7 @@ class RateLimiter {
1063
1034
  localStorage.setItem(this.key, JSON.stringify(data));
1064
1035
  }
1065
1036
  catch (error) {
1066
- console.error('Failed to store rate limit data:', error);
1037
+ console.error('Failed to store rate limit data:', error instanceof Error ? error.message : 'Unknown error');
1067
1038
  }
1068
1039
  }
1069
1040
  }
@@ -1168,7 +1139,7 @@ class ConsoleLogger {
1168
1139
  }
1169
1140
  catch (error) {
1170
1141
  // Don't let logging break the app
1171
- this.originalConsole.error('Failed to capture log:', error);
1142
+ this.originalConsole.error('Failed to capture log:', error instanceof Error ? error.message : 'Unknown error');
1172
1143
  }
1173
1144
  }
1174
1145
  /**
@@ -1231,7 +1202,7 @@ class Triagly {
1231
1202
  theme: 'auto',
1232
1203
  position: 'bottom-right',
1233
1204
  buttonShape: 'rounded',
1234
- buttonText: '🐛 Feedback',
1205
+ buttonText: 'Feedback',
1235
1206
  placeholderText: 'Describe what happened...',
1236
1207
  successMessage: 'Feedback sent successfully!',
1237
1208
  errorMessage: 'Failed to send feedback. Please try again.',
@@ -1242,7 +1213,7 @@ class Triagly {
1242
1213
  apiKey,
1243
1214
  publishableKey: apiKey, // Keep for backward compatibility
1244
1215
  };
1245
- this.api = new TriaglyAPI(apiKey, this.config.apiUrl, this.config.getToken, this.config.turnstileSiteKey);
1216
+ this.api = new TriaglyAPI(apiKey, this.config.environment || 'production', this.config.apiUrl, this.config.getToken, this.config.turnstileSiteKey);
1246
1217
  // Always pass Turnstile site key to widget (from API which has default)
1247
1218
  this.config.turnstileSiteKey = this.api.getTurnstileSiteKey();
1248
1219
  this.widget = new FeedbackWidget(this.config);
@@ -1286,17 +1257,11 @@ class Triagly {
1286
1257
  }
1287
1258
  // Collect metadata
1288
1259
  const metadata = collectMetadata(this.config.metadata);
1289
- // Capture screenshot if requested
1290
- let screenshot = null;
1291
- if (data.includeScreenshot) {
1292
- screenshot = await captureScreenshot();
1293
- }
1294
1260
  // Prepare feedback data
1295
1261
  const feedbackData = {
1296
1262
  title: data.title,
1297
1263
  description: data.description,
1298
1264
  reporterEmail: data.reporterEmail,
1299
- screenshot: screenshot || undefined,
1300
1265
  consoleLogs: this.consoleLogger?.getLogs(),
1301
1266
  };
1302
1267
  // Submit to API with Turnstile token if provided
@@ -1311,10 +1276,9 @@ class Triagly {
1311
1276
  if (this.config.onSuccess) {
1312
1277
  this.config.onSuccess(response.id);
1313
1278
  }
1314
- console.log('Feedback submitted successfully:', response.id);
1315
1279
  }
1316
1280
  catch (error) {
1317
- console.error('Failed to submit feedback:', error);
1281
+ console.error('Failed to submit feedback:', error instanceof Error ? error.message : 'Unknown error');
1318
1282
  // Dispatch error event for UI layer
1319
1283
  document.dispatchEvent(new CustomEvent('triagly:error', {
1320
1284
  detail: error