featurely-site-manager 1.1.11 → 1.1.14

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.d.mts CHANGED
@@ -90,6 +90,8 @@ interface SiteManagerConfig {
90
90
  onUpdateAvailable?: (versionInfo: VersionCheckResponse) => void;
91
91
  onUpdateRequired?: (versionInfo: VersionCheckResponse) => void;
92
92
  onError?: (error: Error) => void;
93
+ debugMode?: boolean;
94
+ environment?: string;
93
95
  }
94
96
  declare class SiteManager {
95
97
  private config;
@@ -109,6 +111,20 @@ declare class SiteManager {
109
111
  private pageEntryTime;
110
112
  private static readonly MAX_CONSECUTIVE_FAILURES;
111
113
  private lastVersionCheck;
114
+ private debugOverlayEl;
115
+ private debugRefreshId;
116
+ private debugLogs;
117
+ private networkLog;
118
+ private analyticsEventsSent;
119
+ private debugMinimized;
120
+ private debugTab;
121
+ private recentAnalyticsEvents;
122
+ private testFlagKey;
123
+ private testFeedback;
124
+ private errorCount;
125
+ private consoleIntercepted;
126
+ private originalConsoleError;
127
+ private originalConsoleWarn;
112
128
  constructor(config: SiteManagerConfig);
113
129
  init(): Promise<void>;
114
130
  destroy(): void;
@@ -127,6 +143,13 @@ declare class SiteManager {
127
143
  private startAnalyticsFlushing;
128
144
  private stopAnalyticsFlushing;
129
145
  private flushAnalytics;
146
+ private debugLog;
147
+ private debugNetwork;
148
+ private stopDebugOverlay;
149
+ private setupGlobalErrorCapture;
150
+ private setupDebugOverlay;
151
+ private renderDebugOverlay;
152
+ private handleTestAction;
130
153
  private setupPageTracking;
131
154
  private trackPageView;
132
155
  private trackPageExit;
package/dist/index.d.ts CHANGED
@@ -90,6 +90,8 @@ interface SiteManagerConfig {
90
90
  onUpdateAvailable?: (versionInfo: VersionCheckResponse) => void;
91
91
  onUpdateRequired?: (versionInfo: VersionCheckResponse) => void;
92
92
  onError?: (error: Error) => void;
93
+ debugMode?: boolean;
94
+ environment?: string;
93
95
  }
94
96
  declare class SiteManager {
95
97
  private config;
@@ -109,6 +111,20 @@ declare class SiteManager {
109
111
  private pageEntryTime;
110
112
  private static readonly MAX_CONSECUTIVE_FAILURES;
111
113
  private lastVersionCheck;
114
+ private debugOverlayEl;
115
+ private debugRefreshId;
116
+ private debugLogs;
117
+ private networkLog;
118
+ private analyticsEventsSent;
119
+ private debugMinimized;
120
+ private debugTab;
121
+ private recentAnalyticsEvents;
122
+ private testFlagKey;
123
+ private testFeedback;
124
+ private errorCount;
125
+ private consoleIntercepted;
126
+ private originalConsoleError;
127
+ private originalConsoleWarn;
112
128
  constructor(config: SiteManagerConfig);
113
129
  init(): Promise<void>;
114
130
  destroy(): void;
@@ -127,6 +143,13 @@ declare class SiteManager {
127
143
  private startAnalyticsFlushing;
128
144
  private stopAnalyticsFlushing;
129
145
  private flushAnalytics;
146
+ private debugLog;
147
+ private debugNetwork;
148
+ private stopDebugOverlay;
149
+ private setupGlobalErrorCapture;
150
+ private setupDebugOverlay;
151
+ private renderDebugOverlay;
152
+ private handleTestAction;
130
153
  private setupPageTracking;
131
154
  private trackPageView;
132
155
  private trackPageExit;
package/dist/index.js CHANGED
@@ -53,7 +53,23 @@ var _SiteManager = class _SiteManager {
53
53
  this.currentPageSearch = "";
54
54
  this.pageEntryTime = 0;
55
55
  this.lastVersionCheck = null;
56
- var _a, _b, _c, _d, _e;
56
+ this.debugOverlayEl = null;
57
+ this.debugRefreshId = null;
58
+ this.debugLogs = [];
59
+ this.networkLog = [];
60
+ this.analyticsEventsSent = 0;
61
+ this.debugMinimized = false;
62
+ this.debugTab = "sdk";
63
+ this.recentAnalyticsEvents = [];
64
+ this.testFlagKey = "";
65
+ this.testFeedback = [];
66
+ this.errorCount = 0;
67
+ this.consoleIntercepted = false;
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ this.originalConsoleError = null;
70
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ this.originalConsoleWarn = null;
72
+ var _a, _b, _c, _d, _e, _f;
57
73
  if (!config.apiKey) {
58
74
  throw new Error("Featurely Site Manager: apiKey is required");
59
75
  }
@@ -81,7 +97,9 @@ var _SiteManager = class _SiteManager {
81
97
  versionCheckInterval: (_e = config.versionCheckInterval) != null ? _e : 36e5,
82
98
  // 1 hour
83
99
  onUpdateAvailable: config.onUpdateAvailable,
84
- onUpdateRequired: config.onUpdateRequired
100
+ onUpdateRequired: config.onUpdateRequired,
101
+ debugMode: (_f = config.debugMode) != null ? _f : false,
102
+ environment: config.environment
85
103
  };
86
104
  this.loadDismissedMessages();
87
105
  }
@@ -89,23 +107,34 @@ var _SiteManager = class _SiteManager {
89
107
  * Initialize and start the site manager
90
108
  */
91
109
  async init() {
110
+ var _a;
92
111
  if (typeof window === "undefined" || typeof document === "undefined") {
93
112
  console.warn(
94
113
  "Featurely Site Manager: Can only be initialized in a browser environment"
95
114
  );
96
115
  return;
97
116
  }
98
- await this.fetchConfig();
99
- this.startPolling();
117
+ if (this.config.debugMode) {
118
+ this.setupDebugOverlay();
119
+ }
120
+ this.debugLog("info", `[init] v1.1.14 | project: ${this.config.projectId} | env: ${(_a = this.config.environment) != null ? _a : "\u2014"}`);
121
+ this.debugLog("info", `[init] analytics: ${this.config.enableAnalytics ? "on" : "off"} | poll: ${this.config.pollInterval}ms | debug: ${this.config.debugMode ? "on" : "off"}`);
100
122
  if (this.config.enableAnalytics) {
123
+ this.debugLog("info", "[init] starting analytics + page tracking");
101
124
  this.startAnalyticsFlushing();
102
125
  this.setupPageTracking();
103
126
  }
127
+ this.debugLog("info", "[init] fetching initial config\u2026");
128
+ await this.fetchConfig();
129
+ this.startPolling();
130
+ this.debugLog("info", `[init] polling every ${this.config.pollInterval / 1e3}s`);
104
131
  if (this.config.enableVersionCheck && this.config.appVersion) {
132
+ this.debugLog("info", `[init] checking version (current: ${this.config.appVersion})`);
105
133
  await this.checkVersion();
106
134
  this.startVersionChecking();
107
135
  }
108
136
  this.injectStyles();
137
+ this.debugLog("info", "[init] \u2713 ready");
109
138
  }
110
139
  /**
111
140
  * Stop the site manager and clean up
@@ -115,6 +144,7 @@ var _SiteManager = class _SiteManager {
115
144
  this.stopVersionChecking();
116
145
  this.stopAnalyticsFlushing();
117
146
  this.flushAnalytics();
147
+ this.stopDebugOverlay();
118
148
  this.clearMessages();
119
149
  }
120
150
  /**
@@ -150,6 +180,7 @@ var _SiteManager = class _SiteManager {
150
180
  return false;
151
181
  }
152
182
  const isEnabled = this.evaluateFeatureFlag(flag);
183
+ this.debugLog("info", `[flag] "${flagKey}" \u2192 ${isEnabled ? "ENABLED" : "DISABLED"}`);
153
184
  const trackKey = `feature_flag_tracked_${flagKey}`;
154
185
  if (typeof sessionStorage !== "undefined" && !sessionStorage.getItem(trackKey)) {
155
186
  this.trackEvent("feature_flag_evaluated", {
@@ -273,6 +304,9 @@ var _SiteManager = class _SiteManager {
273
304
  properties,
274
305
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
275
306
  });
307
+ this.recentAnalyticsEvents.unshift({ name: eventName, ts: (/* @__PURE__ */ new Date()).toLocaleTimeString() });
308
+ if (this.recentAnalyticsEvents.length > 50) this.recentAnalyticsEvents.length = 50;
309
+ this.debugLog("info", `trackEvent: ${eventName}`);
276
310
  if (this.analyticsQueue.length >= 10) {
277
311
  this.flushAnalytics();
278
312
  }
@@ -281,8 +315,9 @@ var _SiteManager = class _SiteManager {
281
315
  // Configuration Fetching
282
316
  // ============================================================================
283
317
  async fetchConfig() {
284
- var _a, _b;
318
+ var _a, _b, _c, _d, _e, _f, _g, _h;
285
319
  try {
320
+ const fetchStartMs = Date.now();
286
321
  const response = await fetch(
287
322
  `${this.config.apiUrl}/api/public/v1/site-config?projectId=${this.config.projectId}`,
288
323
  {
@@ -291,6 +326,7 @@ var _SiteManager = class _SiteManager {
291
326
  }
292
327
  }
293
328
  );
329
+ this.debugNetwork(`/api/public/v1/site-config`, response.status, Date.now() - fetchStartMs);
294
330
  if (!response.ok) {
295
331
  const errorData = await response.json().catch(() => ({}));
296
332
  const message = errorData.error || response.statusText;
@@ -313,9 +349,30 @@ var _SiteManager = class _SiteManager {
313
349
  const configChanged = JSON.stringify(newConfig) !== JSON.stringify(this.siteConfig);
314
350
  this.consecutiveFetchFailures = 0;
315
351
  if (configChanged) {
316
- const wasMaintenanceEnabled = (_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled;
317
- const oldFeatureFlags = ((_b = this.siteConfig) == null ? void 0 : _b.featureFlags) || [];
352
+ const prevMaintenance = (_b = (_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled) != null ? _b : false;
353
+ const prevFlagCount = (_d = (_c = this.siteConfig) == null ? void 0 : _c.featureFlags.length) != null ? _d : 0;
354
+ const prevMsgCount = (_f = (_e = this.siteConfig) == null ? void 0 : _e.messages.length) != null ? _f : 0;
355
+ const delta = [];
356
+ if (newConfig.maintenance.enabled !== prevMaintenance)
357
+ delta.push(`maintenance ${newConfig.maintenance.enabled ? "ON" : "OFF"}`);
358
+ if (newConfig.featureFlags.length !== prevFlagCount)
359
+ delta.push(`flags: ${prevFlagCount}\u2192${newConfig.featureFlags.length}`);
360
+ if (newConfig.messages.length !== prevMsgCount)
361
+ delta.push(`messages: ${prevMsgCount}\u2192${newConfig.messages.length}`);
362
+ this.debugLog("info", `[config] updated${delta.length ? ": " + delta.join(", ") : " (structure changed)"}`);
363
+ const wasMaintenanceEnabled = (_g = this.siteConfig) == null ? void 0 : _g.maintenance.enabled;
364
+ const oldFeatureFlags = ((_h = this.siteConfig) == null ? void 0 : _h.featureFlags) || [];
318
365
  this.siteConfig = newConfig;
366
+ if (newConfig.debugMode === true && !this.debugOverlayEl) {
367
+ this.setupDebugOverlay();
368
+ }
369
+ if (!this.debugOverlayEl && this.config.environment) {
370
+ const envs = newConfig.environments;
371
+ const env = envs == null ? void 0 : envs.find((e) => e.slug === this.config.environment || e.name === this.config.environment);
372
+ if ((env == null ? void 0 : env.debugEnabled) === true) {
373
+ this.setupDebugOverlay();
374
+ }
375
+ }
319
376
  if (newConfig.maintenance.enabled && !wasMaintenanceEnabled) {
320
377
  this.enableMaintenanceMode();
321
378
  } else if (!newConfig.maintenance.enabled && wasMaintenanceEnabled) {
@@ -329,17 +386,22 @@ var _SiteManager = class _SiteManager {
329
386
  }
330
387
  }
331
388
  this.updateMessages();
389
+ } else {
390
+ this.debugLog("info", "[config] polled \u2014 no changes");
332
391
  }
333
392
  } catch (error) {
334
393
  this.consecutiveFetchFailures++;
394
+ const errMsg = error instanceof Error ? error.message : String(error);
395
+ this.debugLog("error", `[config] fetch failed (attempt ${this.consecutiveFetchFailures}): ${errMsg}`);
396
+ this.errorCount++;
335
397
  if (this.consecutiveFetchFailures <= _SiteManager.MAX_CONSECUTIVE_FAILURES) {
336
398
  const isNetworkError = error instanceof TypeError && (error.message.includes("NetworkError") || error.message.includes("Failed to fetch") || error.message.includes("fetch"));
337
399
  if (isNetworkError) {
338
- console.error(
339
- `[Featurely] Network error \u2014 request to Featurely was blocked.
400
+ const cspMsg = `[Featurely] Network error \u2014 request to Featurely was blocked.
340
401
  \u2192 If your site has a Content-Security-Policy, add 'https://www.featurely.no' to the connect-src directive.
341
- Example: connect-src 'self' https://www.featurely.no ...`
342
- );
402
+ Example: connect-src 'self' https://www.featurely.no ...`;
403
+ console.error(cspMsg);
404
+ this.debugLog("error", "[config] possible CSP block \u2014 check connect-src");
343
405
  } else {
344
406
  console.error("Featurely Site Manager: Failed to fetch configuration", error);
345
407
  }
@@ -347,9 +409,9 @@ var _SiteManager = class _SiteManager {
347
409
  this.config.onError(error);
348
410
  }
349
411
  } else if (this.consecutiveFetchFailures === _SiteManager.MAX_CONSECUTIVE_FAILURES + 1) {
350
- console.error(
351
- `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`
352
- );
412
+ const silenceMsg = `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`;
413
+ console.error(silenceMsg);
414
+ this.debugLog("error", `[config] silencing errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} consecutive failures`);
353
415
  }
354
416
  }
355
417
  }
@@ -387,6 +449,8 @@ var _SiteManager = class _SiteManager {
387
449
  const url = `${this.config.apiUrl}/api/projects/${this.config.projectId}/analytics/events`;
388
450
  const eventsToSend = [...this.analyticsQueue];
389
451
  this.analyticsQueue = [];
452
+ this.analyticsEventsSent += eventsToSend.length;
453
+ this.debugLog("info", `[analytics] flushing ${eventsToSend.length} event(s) (total sent: ${this.analyticsEventsSent})`);
390
454
  for (const event of eventsToSend) {
391
455
  const payload = JSON.stringify({
392
456
  eventName: event.eventName,
@@ -403,25 +467,364 @@ var _SiteManager = class _SiteManager {
403
467
  }
404
468
  } else {
405
469
  try {
470
+ const t0 = Date.now();
406
471
  const res = await fetch(url, {
407
472
  method: "POST",
408
473
  headers: { "Content-Type": "application/json" },
409
474
  body: payload,
410
475
  keepalive: true
411
476
  });
477
+ this.debugNetwork(`/api/projects/${this.config.projectId}/analytics/events`, res.status, Date.now() - t0);
412
478
  if (!res.ok) {
413
479
  const body = await res.text().catch(() => "(unreadable)");
414
- console.error(`[Featurely] Analytics event "${event.eventName}" rejected: ${res.status} ${res.statusText} \u2014 ${body}`);
480
+ const errDetail = `[analytics] event "${event.eventName}" rejected: ${res.status} ${res.statusText} \u2014 ${body}`;
481
+ console.error(`[Featurely] ${errDetail}`);
482
+ this.debugLog("error", errDetail);
483
+ this.errorCount++;
415
484
  }
416
485
  } catch (fetchError) {
417
486
  const sentViaBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, payload);
418
487
  if (!sentViaBeacon) {
488
+ const errMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
419
489
  console.error(`[Featurely] Failed to send analytics event "${event.eventName}":`, fetchError);
490
+ this.debugLog("error", `[analytics] send failed for "${event.eventName}": ${errMsg}`);
491
+ this.errorCount++;
492
+ } else {
493
+ this.debugLog("warn", `[analytics] fetch failed for "${event.eventName}", delivered via sendBeacon`);
420
494
  }
421
495
  }
422
496
  }
423
497
  }
424
498
  }
499
+ /** Log a message to the debug panel (max 100 entries). */
500
+ debugLog(level, msg) {
501
+ if (!this.config.debugMode && !this.debugOverlayEl) return;
502
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
503
+ this.debugLogs.unshift({ ts, level, msg });
504
+ if (this.debugLogs.length > 100) this.debugLogs.length = 100;
505
+ }
506
+ /** Add a network call entry. */
507
+ debugNetwork(url, status, ms) {
508
+ if (!this.config.debugMode && !this.debugOverlayEl) return;
509
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
510
+ const shortUrl = url.replace(/^https?:\/\/[^/]+/, "");
511
+ this.networkLog.unshift({ url: shortUrl, status, ms, ts });
512
+ if (this.networkLog.length > 50) this.networkLog.length = 50;
513
+ }
514
+ stopDebugOverlay() {
515
+ var _a;
516
+ if (this.debugRefreshId) {
517
+ clearInterval(this.debugRefreshId);
518
+ this.debugRefreshId = null;
519
+ }
520
+ if ((_a = this.debugOverlayEl) == null ? void 0 : _a.parentNode) {
521
+ this.debugOverlayEl.parentNode.removeChild(this.debugOverlayEl);
522
+ }
523
+ this.debugOverlayEl = null;
524
+ if (this.originalConsoleError) {
525
+ console.error = this.originalConsoleError;
526
+ this.originalConsoleError = null;
527
+ }
528
+ if (this.originalConsoleWarn) {
529
+ console.warn = this.originalConsoleWarn;
530
+ this.originalConsoleWarn = null;
531
+ }
532
+ this.consoleIntercepted = false;
533
+ }
534
+ setupGlobalErrorCapture() {
535
+ if (this.consoleIntercepted) return;
536
+ this.consoleIntercepted = true;
537
+ this.originalConsoleError = console.error.bind(console);
538
+ console.error = (...args) => {
539
+ this.originalConsoleError(...args);
540
+ const msg = args.map((a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)).join(" ");
541
+ this.errorCount++;
542
+ this.debugLog("error", `[console.error] ${msg.slice(0, 300)}`);
543
+ };
544
+ this.originalConsoleWarn = console.warn.bind(console);
545
+ console.warn = (...args) => {
546
+ this.originalConsoleWarn(...args);
547
+ const msg = args.map((a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)).join(" ");
548
+ this.debugLog("warn", `[console.warn] ${msg.slice(0, 300)}`);
549
+ };
550
+ window.addEventListener("error", (e) => {
551
+ this.errorCount++;
552
+ const src = e.filename ? `${e.filename.replace(/.*\//, "")}:${e.lineno}:${e.colno}` : "";
553
+ this.debugLog("error", `[uncaught error] ${e.message}${src ? ` @ ${src}` : ""}`);
554
+ });
555
+ window.addEventListener("unhandledrejection", (e) => {
556
+ this.errorCount++;
557
+ const reason = e.reason instanceof Error ? e.reason.message : typeof e.reason === "string" ? e.reason : JSON.stringify(e.reason);
558
+ this.debugLog("error", `[unhandled promise] ${reason.slice(0, 300)}`);
559
+ });
560
+ }
561
+ setupDebugOverlay() {
562
+ if (typeof document === "undefined" || this.debugOverlayEl) return;
563
+ const el = document.createElement("div");
564
+ el.id = "__featurely_debug__";
565
+ el.setAttribute("data-featurely-debug", "true");
566
+ const BG = "rgba(15,15,20,0.97)";
567
+ const BORDER = "rgba(255,255,255,0.12)";
568
+ const TEXT = "#e5e7eb";
569
+ const W = "360px";
570
+ Object.assign(el.style, {
571
+ position: "fixed",
572
+ bottom: "16px",
573
+ right: "16px",
574
+ width: W,
575
+ zIndex: "2147483647",
576
+ fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
577
+ fontSize: "11px",
578
+ lineHeight: "1.5",
579
+ color: TEXT,
580
+ background: BG,
581
+ border: `1px solid ${BORDER}`,
582
+ borderRadius: "10px",
583
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
584
+ overflow: "hidden",
585
+ userSelect: "none",
586
+ transition: "height 0.15s ease"
587
+ });
588
+ window.__FEATURELY_DEBUG__ = {
589
+ log: (pkg, level, msg) => {
590
+ this.debugLog(level, `[${pkg}] ${msg}`);
591
+ },
592
+ registerPackage: (pkg, getState) => {
593
+ this[`__debug_${pkg}`] = getState;
594
+ }
595
+ };
596
+ document.body.appendChild(el);
597
+ this.debugOverlayEl = el;
598
+ this.setupGlobalErrorCapture();
599
+ this.debugLog("info", `[site-manager] debug overlay initialized`);
600
+ this.debugLog("info", `[site-manager] v1.1.14 | project: ${this.config.projectId}`);
601
+ this.renderDebugOverlay();
602
+ this.debugRefreshId = setInterval(() => this.renderDebugOverlay(), 1500);
603
+ }
604
+ renderDebugOverlay() {
605
+ var _a, _b, _c, _d, _e;
606
+ const el = this.debugOverlayEl;
607
+ if (!el) return;
608
+ const ACCENT = "#6366f1";
609
+ const BORDER = "rgba(255,255,255,0.12)";
610
+ const TEXT = "#e5e7eb";
611
+ const MUTED = "#9ca3af";
612
+ const GREEN = "#22c55e";
613
+ const RED = "#ef4444";
614
+ const YELLOW = "#eab308";
615
+ const tab = this.debugTab;
616
+ const minimized = this.debugMinimized;
617
+ const cfg = this.config;
618
+ const sc = this.siteConfig;
619
+ const tabBtn = (id, label, badgeCount) => {
620
+ const badgeHtml = badgeCount && badgeCount > 0 ? `<span style="display:inline-flex;align-items:center;justify-content:center;min-width:14px;height:14px;padding:0 3px;margin-left:3px;border-radius:9999px;font-size:8px;font-weight:700;background:${RED};color:#fff;vertical-align:middle">${badgeCount > 9 ? "9+" : badgeCount}</span>` : "";
621
+ return `<button data-tab="${id}" style="padding:3px 10px;border-radius:4px;border:none;cursor:pointer;font-size:10px;font-family:inherit;background:${tab === id ? ACCENT : "rgba(255,255,255,0.06)"};color:${tab === id ? "#fff" : MUTED};transition:background 0.1s">${label}${badgeHtml}</button>`;
622
+ };
623
+ const badge = (text, color) => `<span style="padding:1px 6px;border-radius:9999px;font-size:9px;font-weight:600;background:${color}22;color:${color}">${text}</span>`;
624
+ const row = (label, value) => `<div style="display:flex;justify-content:space-between;align-items:center;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.04)"><span style="color:${MUTED}">${label}</span><span style="color:${TEXT};word-break:break-all;text-align:right;max-width:220px">${value}</span></div>`;
625
+ let content = "";
626
+ if (!minimized) {
627
+ if (tab === "sdk") {
628
+ const maintenance = ((_a = sc == null ? void 0 : sc.maintenance) == null ? void 0 : _a.enabled) ? badge("ON", RED) : badge("OFF", GREEN);
629
+ const flagCount = (_c = (_b = sc == null ? void 0 : sc.featureFlags) == null ? void 0 : _b.length) != null ? _c : 0;
630
+ const msgCount = (_e = (_d = sc == null ? void 0 : sc.messages) == null ? void 0 : _d.length) != null ? _e : 0;
631
+ content = `
632
+ ${row("SDK Version", "1.1.14")}
633
+ ${row("Project", cfg.projectId)}
634
+ ${row("Environment", cfg.environment || "\u2014")}
635
+ ${row("API URL", cfg.apiUrl)}
636
+ ${row("Maintenance", maintenance)}
637
+ ${row("Feature Flags", `${flagCount} active`)}
638
+ ${row("Status Messages", `${msgCount} active`)}
639
+ ${row("Analytics Queue", `${this.analyticsQueue.length} pending`)}
640
+ ${row("Events Sent", `${this.analyticsEventsSent}`)}
641
+ ${row("Session ID", this.sessionId.slice(0, 16) + "\u2026")}
642
+ ${row("User ID", cfg.userId || "\u2014")}
643
+ ${row("Page", this.currentPagePath || "\u2014")}
644
+ `;
645
+ const pkgKeys = Object.keys(this).filter((k) => k.startsWith("__debug_"));
646
+ for (const k of pkgKeys) {
647
+ const fn = this[k];
648
+ if (typeof fn === "function") {
649
+ try {
650
+ content += `<div style="margin-top:6px;padding-top:4px;border-top:1px solid ${BORDER}">${fn()}</div>`;
651
+ } catch {
652
+ }
653
+ }
654
+ }
655
+ } else if (tab === "logs") {
656
+ const logs = this.debugLogs.slice(0, 50);
657
+ const errorsInLog = logs.filter((l) => l.level === "error").length;
658
+ const clearBtn = `<button data-action="clear-logs" style="background:rgba(255,255,255,0.08);border:none;color:${MUTED};cursor:pointer;font-size:9px;font-family:inherit;padding:2px 7px;border-radius:3px">Clear</button>`;
659
+ const errBadge = errorsInLog > 0 ? `<span style="font-size:9px;color:${RED}">${errorsInLog} error${errorsInLog > 1 ? "s" : ""}</span>` : "";
660
+ const logHeader = `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">${errBadge || `<span></span>`}${clearBtn}</div>`;
661
+ if (logs.length === 0) {
662
+ content = logHeader + `<div style="color:${MUTED};padding:12px;text-align:center">No logs yet</div>`;
663
+ } else {
664
+ content = logHeader + logs.map((l) => {
665
+ const c = l.level === "error" ? RED : l.level === "warn" ? YELLOW : "#a5b4fc";
666
+ return `<div style="padding:2px 0;display:flex;gap:6px;align-items:flex-start;border-bottom:1px solid rgba(255,255,255,0.03)"><span style="color:${MUTED};white-space:nowrap;font-size:9px">${l.ts}</span><span style="color:${c};flex:1;word-break:break-word">${l.msg}</span></div>`;
667
+ }).join("");
668
+ }
669
+ } else if (tab === "network") {
670
+ const calls = this.networkLog.slice(0, 20);
671
+ if (calls.length === 0) {
672
+ content = `<div style="color:${MUTED};padding:16px;text-align:center">No network calls yet</div>`;
673
+ } else {
674
+ content = calls.map((n) => {
675
+ const ok = typeof n.status === "number" && n.status >= 200 && n.status < 300;
676
+ const sc2 = ok ? GREEN : RED;
677
+ return `<div style="padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.03)"><div style="display:flex;justify-content:space-between"><span style="color:${sc2};font-weight:600">${n.status}</span><span style="color:${MUTED}">${n.ms}ms</span><span style="color:${MUTED}">${n.ts}</span></div><div style="color:${TEXT};word-break:break-all">${n.url}</div></div>`;
678
+ }).join("");
679
+ }
680
+ } else if (tab === "events") {
681
+ const events = this.recentAnalyticsEvents.slice(0, 30);
682
+ if (events.length === 0) {
683
+ content = `<div style="color:${MUTED};padding:16px;text-align:center">No events yet</div>`;
684
+ } else {
685
+ content = events.map(
686
+ (e) => `<div style="padding:2px 0;display:flex;justify-content:space-between;border-bottom:1px solid rgba(255,255,255,0.03)"><span style="color:#a5b4fc">${e.name}</span><span style="color:${MUTED}">${e.ts}</span></div>`
687
+ ).join("");
688
+ }
689
+ } else if (tab === "test") {
690
+ const btnStyle = (color) => `background:${color};border:none;color:#fff;cursor:pointer;font-size:10px;font-family:inherit;padding:4px 10px;border-radius:4px;transition:opacity 0.1s`;
691
+ const sectionLabel = (text) => `<div style="color:${MUTED};font-size:9px;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:6px;margin-top:10px">${text}</div>`;
692
+ const feedbackHtml = this.testFeedback.slice(0, 6).map(
693
+ (f) => `<div style="display:flex;gap:6px;align-items:center;padding:2px 0;border-bottom:1px solid rgba(255,255,255,0.03)">
694
+ <span style="color:${f.ok ? GREEN : RED};font-size:10px">&#x25cf;</span>
695
+ <span style="color:${TEXT};flex:1">${f.msg}</span>
696
+ <span style="color:${MUTED}">${f.ts}</span>
697
+ </div>`
698
+ ).join("");
699
+ content = `
700
+ ${sectionLabel("Analytics")}
701
+ <div style="display:flex;gap:4px;flex-wrap:wrap">
702
+ <button data-action="test-event" style="${btnStyle(ACCENT)}">Send test event</button>
703
+ <button data-action="flush" style="${btnStyle("rgba(255,255,255,0.15)")}">Flush queue now</button>
704
+ </div>
705
+ ${sectionLabel("Feature Flags")}
706
+ <div style="display:flex;gap:4px;align-items:center">
707
+ <input data-testinput="flagkey" value="${this.testFlagKey.replace(/"/g, "&quot;")}" placeholder="flag-key" style="flex:1;background:rgba(255,255,255,0.07);border:1px solid ${BORDER};border-radius:4px;color:${TEXT};font-family:inherit;font-size:10px;padding:3px 7px;outline:none" />
708
+ <button data-action="check-flag" style="${btnStyle(ACCENT)}">Check</button>
709
+ </div>
710
+ ${sectionLabel("Errors & Bugs")}
711
+ <div style="display:flex;gap:4px;flex-wrap:wrap">
712
+ <button data-action="test-error" style="${btnStyle(RED)}">Send test error</button>
713
+ <button data-action="test-bug" style="${btnStyle(YELLOW)}">Send test bug</button>
714
+ </div>
715
+ ${sectionLabel("Config")}
716
+ <div style="display:flex;gap:4px;flex-wrap:wrap">
717
+ <button data-action="refresh" style="${btnStyle("rgba(255,255,255,0.15)")}">Refresh config</button>
718
+ <button data-action="clear-logs" style="${btnStyle("rgba(255,255,255,0.15)")}">Clear logs</button>
719
+ </div>
720
+ ${this.testFeedback.length > 0 ? `
721
+ <div style="margin-top:10px;padding-top:6px;border-top:1px solid ${BORDER}">${feedbackHtml}</div>
722
+ ` : ""}
723
+ `;
724
+ }
725
+ }
726
+ el.innerHTML = `
727
+ <div style="background:rgba(255,255,255,0.04);border-bottom:1px solid ${BORDER};padding:7px 10px;display:flex;align-items:center;gap:6px;cursor:pointer" id="__ft_dbg_hdr__">
728
+ <span style="color:${ACCENT};font-weight:700;font-size:10px;letter-spacing:0.05em">&#x2b21; FEATURELY DEBUG</span>
729
+ <span style="flex:1"></span>
730
+ ${minimized ? "" : `<div style="display:flex;gap:4px;flex-wrap:wrap">${tabBtn("sdk", "SDK")}${tabBtn("logs", "Logs", this.errorCount)}${tabBtn("network", "Network")}${tabBtn("events", "Events")}${tabBtn("test", "Test")}</div>`}
731
+ <button id="__ft_dbg_min__" style="background:none;border:none;color:${MUTED};cursor:pointer;font-size:14px;line-height:1;padding:0 2px" title="${minimized ? "Expand" : "Minimize"}">${minimized ? "&#x2b06;" : "&#x2b07;"}</button>
732
+ <button id="__ft_dbg_cls__" style="background:none;border:none;color:${MUTED};cursor:pointer;font-size:14px;line-height:1;padding:0 2px" title="Close">&times;</button>
733
+ </div>
734
+ ${minimized ? "" : `<div style="padding:8px 10px;max-height:300px;overflow-y:auto">${content}</div>`}
735
+ `;
736
+ el.querySelectorAll("[data-tab]").forEach((btn) => {
737
+ btn.onclick = (e) => {
738
+ e.stopPropagation();
739
+ this.debugTab = btn.dataset.tab;
740
+ this.renderDebugOverlay();
741
+ };
742
+ });
743
+ const minBtn = el.querySelector("#__ft_dbg_min__");
744
+ if (minBtn) {
745
+ minBtn.onclick = (e) => {
746
+ e.stopPropagation();
747
+ this.debugMinimized = !this.debugMinimized;
748
+ this.renderDebugOverlay();
749
+ };
750
+ }
751
+ const clsBtn = el.querySelector("#__ft_dbg_cls__");
752
+ if (clsBtn) {
753
+ clsBtn.onclick = (e) => {
754
+ e.stopPropagation();
755
+ this.stopDebugOverlay();
756
+ };
757
+ }
758
+ el.querySelectorAll("[data-action]").forEach((btn) => {
759
+ btn.onclick = (e) => {
760
+ e.stopPropagation();
761
+ void this.handleTestAction(btn.dataset.action);
762
+ };
763
+ });
764
+ el.querySelectorAll("[data-testinput='flagkey']").forEach((input) => {
765
+ input.oninput = (e) => {
766
+ e.stopPropagation();
767
+ this.testFlagKey = e.target.value;
768
+ };
769
+ input.onclick = (e) => e.stopPropagation();
770
+ });
771
+ }
772
+ async handleTestAction(action) {
773
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
774
+ const feedback = (msg, ok) => {
775
+ this.testFeedback.unshift({ msg, ok, ts });
776
+ if (this.testFeedback.length > 10) this.testFeedback.length = 10;
777
+ this.renderDebugOverlay();
778
+ };
779
+ switch (action) {
780
+ case "test-event":
781
+ this.trackEvent("debug_test_event", { source: "debug_panel", ts: (/* @__PURE__ */ new Date()).toISOString() });
782
+ feedback("debug_test_event queued", true);
783
+ break;
784
+ case "flush": {
785
+ const before = this.analyticsQueue.length;
786
+ await this.flushAnalytics();
787
+ feedback(`Flushed ${before} event(s)`, true);
788
+ break;
789
+ }
790
+ case "check-flag": {
791
+ const key = this.testFlagKey.trim();
792
+ if (!key) {
793
+ feedback("Enter a flag key first", false);
794
+ break;
795
+ }
796
+ const enabled = this.isFeatureEnabled(key);
797
+ feedback(`"${key}" \u2192 ${enabled ? "ENABLED \u2713" : "DISABLED \u2717"}`, enabled);
798
+ break;
799
+ }
800
+ case "test-error":
801
+ this.trackEvent("error_reported", {
802
+ message: "Test error from debug panel",
803
+ source: "debug_panel",
804
+ severity: "error"
805
+ });
806
+ feedback("error_reported queued", true);
807
+ break;
808
+ case "test-bug":
809
+ this.trackEvent("bug_reported", {
810
+ title: "Test bug from debug panel",
811
+ source: "debug_panel",
812
+ severity: "medium"
813
+ });
814
+ feedback("bug_reported queued", true);
815
+ break;
816
+ case "refresh":
817
+ await this.fetchConfig();
818
+ feedback("Config refreshed", true);
819
+ break;
820
+ case "clear-logs":
821
+ this.debugLogs = [];
822
+ this.testFeedback = [];
823
+ this.errorCount = 0;
824
+ this.renderDebugOverlay();
825
+ break;
826
+ }
827
+ }
425
828
  setupPageTracking() {
426
829
  if (this.pageTrackingSetup) return;
427
830
  this.pageTrackingSetup = true;
@@ -669,8 +1072,10 @@ var _SiteManager = class _SiteManager {
669
1072
  enableMaintenanceMode() {
670
1073
  if (!this.siteConfig) return;
671
1074
  if (this.shouldBypassMaintenance()) {
1075
+ this.debugLog("info", "[maintenance] ENABLED \u2014 user bypassed (whitelist match)");
672
1076
  return;
673
1077
  }
1078
+ this.debugLog("warn", `[maintenance] ENABLED (type: ${this.siteConfig.maintenance.type})`);
674
1079
  this.showMaintenancePage();
675
1080
  this.trackEvent("maintenance_enabled", {
676
1081
  maintenanceType: this.siteConfig.maintenance.type
@@ -680,6 +1085,7 @@ var _SiteManager = class _SiteManager {
680
1085
  }
681
1086
  }
682
1087
  disableMaintenanceMode() {
1088
+ this.debugLog("info", "[maintenance] DISABLED \u2014 site back online");
683
1089
  this.hideMaintenancePage();
684
1090
  this.trackEvent("maintenance_disabled");
685
1091
  if (this.config.onMaintenanceDisabled) {
@@ -774,6 +1180,7 @@ var _SiteManager = class _SiteManager {
774
1180
  // Status Messages
775
1181
  // ============================================================================
776
1182
  updateMessages() {
1183
+ var _a, _b;
777
1184
  if (!this.siteConfig) return;
778
1185
  const currentTime = /* @__PURE__ */ new Date();
779
1186
  const currentPath = window.location.pathname;
@@ -808,8 +1215,10 @@ var _SiteManager = class _SiteManager {
808
1215
  this.messageContainers.delete(id);
809
1216
  }
810
1217
  });
1218
+ this.debugLog("info", `[messages] ${activeMessages.length} active (${(_b = (_a = this.siteConfig) == null ? void 0 : _a.messages.length) != null ? _b : 0} total)`);
811
1219
  activeMessages.forEach((message) => {
812
1220
  if (!this.messageContainers.has(message.id)) {
1221
+ this.debugLog("info", `[messages] showing "${message.title}" (${message.type}, ${message.style})`);
813
1222
  this.showMessage(message);
814
1223
  if (this.config.onMessageReceived) {
815
1224
  this.config.onMessageReceived(message);
package/dist/index.mjs CHANGED
@@ -18,7 +18,23 @@ var _SiteManager = class _SiteManager {
18
18
  this.currentPageSearch = "";
19
19
  this.pageEntryTime = 0;
20
20
  this.lastVersionCheck = null;
21
- var _a, _b, _c, _d, _e;
21
+ this.debugOverlayEl = null;
22
+ this.debugRefreshId = null;
23
+ this.debugLogs = [];
24
+ this.networkLog = [];
25
+ this.analyticsEventsSent = 0;
26
+ this.debugMinimized = false;
27
+ this.debugTab = "sdk";
28
+ this.recentAnalyticsEvents = [];
29
+ this.testFlagKey = "";
30
+ this.testFeedback = [];
31
+ this.errorCount = 0;
32
+ this.consoleIntercepted = false;
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ this.originalConsoleError = null;
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ this.originalConsoleWarn = null;
37
+ var _a, _b, _c, _d, _e, _f;
22
38
  if (!config.apiKey) {
23
39
  throw new Error("Featurely Site Manager: apiKey is required");
24
40
  }
@@ -46,7 +62,9 @@ var _SiteManager = class _SiteManager {
46
62
  versionCheckInterval: (_e = config.versionCheckInterval) != null ? _e : 36e5,
47
63
  // 1 hour
48
64
  onUpdateAvailable: config.onUpdateAvailable,
49
- onUpdateRequired: config.onUpdateRequired
65
+ onUpdateRequired: config.onUpdateRequired,
66
+ debugMode: (_f = config.debugMode) != null ? _f : false,
67
+ environment: config.environment
50
68
  };
51
69
  this.loadDismissedMessages();
52
70
  }
@@ -54,23 +72,34 @@ var _SiteManager = class _SiteManager {
54
72
  * Initialize and start the site manager
55
73
  */
56
74
  async init() {
75
+ var _a;
57
76
  if (typeof window === "undefined" || typeof document === "undefined") {
58
77
  console.warn(
59
78
  "Featurely Site Manager: Can only be initialized in a browser environment"
60
79
  );
61
80
  return;
62
81
  }
63
- await this.fetchConfig();
64
- this.startPolling();
82
+ if (this.config.debugMode) {
83
+ this.setupDebugOverlay();
84
+ }
85
+ this.debugLog("info", `[init] v1.1.14 | project: ${this.config.projectId} | env: ${(_a = this.config.environment) != null ? _a : "\u2014"}`);
86
+ this.debugLog("info", `[init] analytics: ${this.config.enableAnalytics ? "on" : "off"} | poll: ${this.config.pollInterval}ms | debug: ${this.config.debugMode ? "on" : "off"}`);
65
87
  if (this.config.enableAnalytics) {
88
+ this.debugLog("info", "[init] starting analytics + page tracking");
66
89
  this.startAnalyticsFlushing();
67
90
  this.setupPageTracking();
68
91
  }
92
+ this.debugLog("info", "[init] fetching initial config\u2026");
93
+ await this.fetchConfig();
94
+ this.startPolling();
95
+ this.debugLog("info", `[init] polling every ${this.config.pollInterval / 1e3}s`);
69
96
  if (this.config.enableVersionCheck && this.config.appVersion) {
97
+ this.debugLog("info", `[init] checking version (current: ${this.config.appVersion})`);
70
98
  await this.checkVersion();
71
99
  this.startVersionChecking();
72
100
  }
73
101
  this.injectStyles();
102
+ this.debugLog("info", "[init] \u2713 ready");
74
103
  }
75
104
  /**
76
105
  * Stop the site manager and clean up
@@ -80,6 +109,7 @@ var _SiteManager = class _SiteManager {
80
109
  this.stopVersionChecking();
81
110
  this.stopAnalyticsFlushing();
82
111
  this.flushAnalytics();
112
+ this.stopDebugOverlay();
83
113
  this.clearMessages();
84
114
  }
85
115
  /**
@@ -115,6 +145,7 @@ var _SiteManager = class _SiteManager {
115
145
  return false;
116
146
  }
117
147
  const isEnabled = this.evaluateFeatureFlag(flag);
148
+ this.debugLog("info", `[flag] "${flagKey}" \u2192 ${isEnabled ? "ENABLED" : "DISABLED"}`);
118
149
  const trackKey = `feature_flag_tracked_${flagKey}`;
119
150
  if (typeof sessionStorage !== "undefined" && !sessionStorage.getItem(trackKey)) {
120
151
  this.trackEvent("feature_flag_evaluated", {
@@ -238,6 +269,9 @@ var _SiteManager = class _SiteManager {
238
269
  properties,
239
270
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
240
271
  });
272
+ this.recentAnalyticsEvents.unshift({ name: eventName, ts: (/* @__PURE__ */ new Date()).toLocaleTimeString() });
273
+ if (this.recentAnalyticsEvents.length > 50) this.recentAnalyticsEvents.length = 50;
274
+ this.debugLog("info", `trackEvent: ${eventName}`);
241
275
  if (this.analyticsQueue.length >= 10) {
242
276
  this.flushAnalytics();
243
277
  }
@@ -246,8 +280,9 @@ var _SiteManager = class _SiteManager {
246
280
  // Configuration Fetching
247
281
  // ============================================================================
248
282
  async fetchConfig() {
249
- var _a, _b;
283
+ var _a, _b, _c, _d, _e, _f, _g, _h;
250
284
  try {
285
+ const fetchStartMs = Date.now();
251
286
  const response = await fetch(
252
287
  `${this.config.apiUrl}/api/public/v1/site-config?projectId=${this.config.projectId}`,
253
288
  {
@@ -256,6 +291,7 @@ var _SiteManager = class _SiteManager {
256
291
  }
257
292
  }
258
293
  );
294
+ this.debugNetwork(`/api/public/v1/site-config`, response.status, Date.now() - fetchStartMs);
259
295
  if (!response.ok) {
260
296
  const errorData = await response.json().catch(() => ({}));
261
297
  const message = errorData.error || response.statusText;
@@ -278,9 +314,30 @@ var _SiteManager = class _SiteManager {
278
314
  const configChanged = JSON.stringify(newConfig) !== JSON.stringify(this.siteConfig);
279
315
  this.consecutiveFetchFailures = 0;
280
316
  if (configChanged) {
281
- const wasMaintenanceEnabled = (_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled;
282
- const oldFeatureFlags = ((_b = this.siteConfig) == null ? void 0 : _b.featureFlags) || [];
317
+ const prevMaintenance = (_b = (_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled) != null ? _b : false;
318
+ const prevFlagCount = (_d = (_c = this.siteConfig) == null ? void 0 : _c.featureFlags.length) != null ? _d : 0;
319
+ const prevMsgCount = (_f = (_e = this.siteConfig) == null ? void 0 : _e.messages.length) != null ? _f : 0;
320
+ const delta = [];
321
+ if (newConfig.maintenance.enabled !== prevMaintenance)
322
+ delta.push(`maintenance ${newConfig.maintenance.enabled ? "ON" : "OFF"}`);
323
+ if (newConfig.featureFlags.length !== prevFlagCount)
324
+ delta.push(`flags: ${prevFlagCount}\u2192${newConfig.featureFlags.length}`);
325
+ if (newConfig.messages.length !== prevMsgCount)
326
+ delta.push(`messages: ${prevMsgCount}\u2192${newConfig.messages.length}`);
327
+ this.debugLog("info", `[config] updated${delta.length ? ": " + delta.join(", ") : " (structure changed)"}`);
328
+ const wasMaintenanceEnabled = (_g = this.siteConfig) == null ? void 0 : _g.maintenance.enabled;
329
+ const oldFeatureFlags = ((_h = this.siteConfig) == null ? void 0 : _h.featureFlags) || [];
283
330
  this.siteConfig = newConfig;
331
+ if (newConfig.debugMode === true && !this.debugOverlayEl) {
332
+ this.setupDebugOverlay();
333
+ }
334
+ if (!this.debugOverlayEl && this.config.environment) {
335
+ const envs = newConfig.environments;
336
+ const env = envs == null ? void 0 : envs.find((e) => e.slug === this.config.environment || e.name === this.config.environment);
337
+ if ((env == null ? void 0 : env.debugEnabled) === true) {
338
+ this.setupDebugOverlay();
339
+ }
340
+ }
284
341
  if (newConfig.maintenance.enabled && !wasMaintenanceEnabled) {
285
342
  this.enableMaintenanceMode();
286
343
  } else if (!newConfig.maintenance.enabled && wasMaintenanceEnabled) {
@@ -294,17 +351,22 @@ var _SiteManager = class _SiteManager {
294
351
  }
295
352
  }
296
353
  this.updateMessages();
354
+ } else {
355
+ this.debugLog("info", "[config] polled \u2014 no changes");
297
356
  }
298
357
  } catch (error) {
299
358
  this.consecutiveFetchFailures++;
359
+ const errMsg = error instanceof Error ? error.message : String(error);
360
+ this.debugLog("error", `[config] fetch failed (attempt ${this.consecutiveFetchFailures}): ${errMsg}`);
361
+ this.errorCount++;
300
362
  if (this.consecutiveFetchFailures <= _SiteManager.MAX_CONSECUTIVE_FAILURES) {
301
363
  const isNetworkError = error instanceof TypeError && (error.message.includes("NetworkError") || error.message.includes("Failed to fetch") || error.message.includes("fetch"));
302
364
  if (isNetworkError) {
303
- console.error(
304
- `[Featurely] Network error \u2014 request to Featurely was blocked.
365
+ const cspMsg = `[Featurely] Network error \u2014 request to Featurely was blocked.
305
366
  \u2192 If your site has a Content-Security-Policy, add 'https://www.featurely.no' to the connect-src directive.
306
- Example: connect-src 'self' https://www.featurely.no ...`
307
- );
367
+ Example: connect-src 'self' https://www.featurely.no ...`;
368
+ console.error(cspMsg);
369
+ this.debugLog("error", "[config] possible CSP block \u2014 check connect-src");
308
370
  } else {
309
371
  console.error("Featurely Site Manager: Failed to fetch configuration", error);
310
372
  }
@@ -312,9 +374,9 @@ var _SiteManager = class _SiteManager {
312
374
  this.config.onError(error);
313
375
  }
314
376
  } else if (this.consecutiveFetchFailures === _SiteManager.MAX_CONSECUTIVE_FAILURES + 1) {
315
- console.error(
316
- `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`
317
- );
377
+ const silenceMsg = `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`;
378
+ console.error(silenceMsg);
379
+ this.debugLog("error", `[config] silencing errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} consecutive failures`);
318
380
  }
319
381
  }
320
382
  }
@@ -352,6 +414,8 @@ var _SiteManager = class _SiteManager {
352
414
  const url = `${this.config.apiUrl}/api/projects/${this.config.projectId}/analytics/events`;
353
415
  const eventsToSend = [...this.analyticsQueue];
354
416
  this.analyticsQueue = [];
417
+ this.analyticsEventsSent += eventsToSend.length;
418
+ this.debugLog("info", `[analytics] flushing ${eventsToSend.length} event(s) (total sent: ${this.analyticsEventsSent})`);
355
419
  for (const event of eventsToSend) {
356
420
  const payload = JSON.stringify({
357
421
  eventName: event.eventName,
@@ -368,25 +432,364 @@ var _SiteManager = class _SiteManager {
368
432
  }
369
433
  } else {
370
434
  try {
435
+ const t0 = Date.now();
371
436
  const res = await fetch(url, {
372
437
  method: "POST",
373
438
  headers: { "Content-Type": "application/json" },
374
439
  body: payload,
375
440
  keepalive: true
376
441
  });
442
+ this.debugNetwork(`/api/projects/${this.config.projectId}/analytics/events`, res.status, Date.now() - t0);
377
443
  if (!res.ok) {
378
444
  const body = await res.text().catch(() => "(unreadable)");
379
- console.error(`[Featurely] Analytics event "${event.eventName}" rejected: ${res.status} ${res.statusText} \u2014 ${body}`);
445
+ const errDetail = `[analytics] event "${event.eventName}" rejected: ${res.status} ${res.statusText} \u2014 ${body}`;
446
+ console.error(`[Featurely] ${errDetail}`);
447
+ this.debugLog("error", errDetail);
448
+ this.errorCount++;
380
449
  }
381
450
  } catch (fetchError) {
382
451
  const sentViaBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, payload);
383
452
  if (!sentViaBeacon) {
453
+ const errMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
384
454
  console.error(`[Featurely] Failed to send analytics event "${event.eventName}":`, fetchError);
455
+ this.debugLog("error", `[analytics] send failed for "${event.eventName}": ${errMsg}`);
456
+ this.errorCount++;
457
+ } else {
458
+ this.debugLog("warn", `[analytics] fetch failed for "${event.eventName}", delivered via sendBeacon`);
385
459
  }
386
460
  }
387
461
  }
388
462
  }
389
463
  }
464
+ /** Log a message to the debug panel (max 100 entries). */
465
+ debugLog(level, msg) {
466
+ if (!this.config.debugMode && !this.debugOverlayEl) return;
467
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
468
+ this.debugLogs.unshift({ ts, level, msg });
469
+ if (this.debugLogs.length > 100) this.debugLogs.length = 100;
470
+ }
471
+ /** Add a network call entry. */
472
+ debugNetwork(url, status, ms) {
473
+ if (!this.config.debugMode && !this.debugOverlayEl) return;
474
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
475
+ const shortUrl = url.replace(/^https?:\/\/[^/]+/, "");
476
+ this.networkLog.unshift({ url: shortUrl, status, ms, ts });
477
+ if (this.networkLog.length > 50) this.networkLog.length = 50;
478
+ }
479
+ stopDebugOverlay() {
480
+ var _a;
481
+ if (this.debugRefreshId) {
482
+ clearInterval(this.debugRefreshId);
483
+ this.debugRefreshId = null;
484
+ }
485
+ if ((_a = this.debugOverlayEl) == null ? void 0 : _a.parentNode) {
486
+ this.debugOverlayEl.parentNode.removeChild(this.debugOverlayEl);
487
+ }
488
+ this.debugOverlayEl = null;
489
+ if (this.originalConsoleError) {
490
+ console.error = this.originalConsoleError;
491
+ this.originalConsoleError = null;
492
+ }
493
+ if (this.originalConsoleWarn) {
494
+ console.warn = this.originalConsoleWarn;
495
+ this.originalConsoleWarn = null;
496
+ }
497
+ this.consoleIntercepted = false;
498
+ }
499
+ setupGlobalErrorCapture() {
500
+ if (this.consoleIntercepted) return;
501
+ this.consoleIntercepted = true;
502
+ this.originalConsoleError = console.error.bind(console);
503
+ console.error = (...args) => {
504
+ this.originalConsoleError(...args);
505
+ const msg = args.map((a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)).join(" ");
506
+ this.errorCount++;
507
+ this.debugLog("error", `[console.error] ${msg.slice(0, 300)}`);
508
+ };
509
+ this.originalConsoleWarn = console.warn.bind(console);
510
+ console.warn = (...args) => {
511
+ this.originalConsoleWarn(...args);
512
+ const msg = args.map((a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)).join(" ");
513
+ this.debugLog("warn", `[console.warn] ${msg.slice(0, 300)}`);
514
+ };
515
+ window.addEventListener("error", (e) => {
516
+ this.errorCount++;
517
+ const src = e.filename ? `${e.filename.replace(/.*\//, "")}:${e.lineno}:${e.colno}` : "";
518
+ this.debugLog("error", `[uncaught error] ${e.message}${src ? ` @ ${src}` : ""}`);
519
+ });
520
+ window.addEventListener("unhandledrejection", (e) => {
521
+ this.errorCount++;
522
+ const reason = e.reason instanceof Error ? e.reason.message : typeof e.reason === "string" ? e.reason : JSON.stringify(e.reason);
523
+ this.debugLog("error", `[unhandled promise] ${reason.slice(0, 300)}`);
524
+ });
525
+ }
526
+ setupDebugOverlay() {
527
+ if (typeof document === "undefined" || this.debugOverlayEl) return;
528
+ const el = document.createElement("div");
529
+ el.id = "__featurely_debug__";
530
+ el.setAttribute("data-featurely-debug", "true");
531
+ const BG = "rgba(15,15,20,0.97)";
532
+ const BORDER = "rgba(255,255,255,0.12)";
533
+ const TEXT = "#e5e7eb";
534
+ const W = "360px";
535
+ Object.assign(el.style, {
536
+ position: "fixed",
537
+ bottom: "16px",
538
+ right: "16px",
539
+ width: W,
540
+ zIndex: "2147483647",
541
+ fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
542
+ fontSize: "11px",
543
+ lineHeight: "1.5",
544
+ color: TEXT,
545
+ background: BG,
546
+ border: `1px solid ${BORDER}`,
547
+ borderRadius: "10px",
548
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
549
+ overflow: "hidden",
550
+ userSelect: "none",
551
+ transition: "height 0.15s ease"
552
+ });
553
+ window.__FEATURELY_DEBUG__ = {
554
+ log: (pkg, level, msg) => {
555
+ this.debugLog(level, `[${pkg}] ${msg}`);
556
+ },
557
+ registerPackage: (pkg, getState) => {
558
+ this[`__debug_${pkg}`] = getState;
559
+ }
560
+ };
561
+ document.body.appendChild(el);
562
+ this.debugOverlayEl = el;
563
+ this.setupGlobalErrorCapture();
564
+ this.debugLog("info", `[site-manager] debug overlay initialized`);
565
+ this.debugLog("info", `[site-manager] v1.1.14 | project: ${this.config.projectId}`);
566
+ this.renderDebugOverlay();
567
+ this.debugRefreshId = setInterval(() => this.renderDebugOverlay(), 1500);
568
+ }
569
+ renderDebugOverlay() {
570
+ var _a, _b, _c, _d, _e;
571
+ const el = this.debugOverlayEl;
572
+ if (!el) return;
573
+ const ACCENT = "#6366f1";
574
+ const BORDER = "rgba(255,255,255,0.12)";
575
+ const TEXT = "#e5e7eb";
576
+ const MUTED = "#9ca3af";
577
+ const GREEN = "#22c55e";
578
+ const RED = "#ef4444";
579
+ const YELLOW = "#eab308";
580
+ const tab = this.debugTab;
581
+ const minimized = this.debugMinimized;
582
+ const cfg = this.config;
583
+ const sc = this.siteConfig;
584
+ const tabBtn = (id, label, badgeCount) => {
585
+ const badgeHtml = badgeCount && badgeCount > 0 ? `<span style="display:inline-flex;align-items:center;justify-content:center;min-width:14px;height:14px;padding:0 3px;margin-left:3px;border-radius:9999px;font-size:8px;font-weight:700;background:${RED};color:#fff;vertical-align:middle">${badgeCount > 9 ? "9+" : badgeCount}</span>` : "";
586
+ return `<button data-tab="${id}" style="padding:3px 10px;border-radius:4px;border:none;cursor:pointer;font-size:10px;font-family:inherit;background:${tab === id ? ACCENT : "rgba(255,255,255,0.06)"};color:${tab === id ? "#fff" : MUTED};transition:background 0.1s">${label}${badgeHtml}</button>`;
587
+ };
588
+ const badge = (text, color) => `<span style="padding:1px 6px;border-radius:9999px;font-size:9px;font-weight:600;background:${color}22;color:${color}">${text}</span>`;
589
+ const row = (label, value) => `<div style="display:flex;justify-content:space-between;align-items:center;padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.04)"><span style="color:${MUTED}">${label}</span><span style="color:${TEXT};word-break:break-all;text-align:right;max-width:220px">${value}</span></div>`;
590
+ let content = "";
591
+ if (!minimized) {
592
+ if (tab === "sdk") {
593
+ const maintenance = ((_a = sc == null ? void 0 : sc.maintenance) == null ? void 0 : _a.enabled) ? badge("ON", RED) : badge("OFF", GREEN);
594
+ const flagCount = (_c = (_b = sc == null ? void 0 : sc.featureFlags) == null ? void 0 : _b.length) != null ? _c : 0;
595
+ const msgCount = (_e = (_d = sc == null ? void 0 : sc.messages) == null ? void 0 : _d.length) != null ? _e : 0;
596
+ content = `
597
+ ${row("SDK Version", "1.1.14")}
598
+ ${row("Project", cfg.projectId)}
599
+ ${row("Environment", cfg.environment || "\u2014")}
600
+ ${row("API URL", cfg.apiUrl)}
601
+ ${row("Maintenance", maintenance)}
602
+ ${row("Feature Flags", `${flagCount} active`)}
603
+ ${row("Status Messages", `${msgCount} active`)}
604
+ ${row("Analytics Queue", `${this.analyticsQueue.length} pending`)}
605
+ ${row("Events Sent", `${this.analyticsEventsSent}`)}
606
+ ${row("Session ID", this.sessionId.slice(0, 16) + "\u2026")}
607
+ ${row("User ID", cfg.userId || "\u2014")}
608
+ ${row("Page", this.currentPagePath || "\u2014")}
609
+ `;
610
+ const pkgKeys = Object.keys(this).filter((k) => k.startsWith("__debug_"));
611
+ for (const k of pkgKeys) {
612
+ const fn = this[k];
613
+ if (typeof fn === "function") {
614
+ try {
615
+ content += `<div style="margin-top:6px;padding-top:4px;border-top:1px solid ${BORDER}">${fn()}</div>`;
616
+ } catch {
617
+ }
618
+ }
619
+ }
620
+ } else if (tab === "logs") {
621
+ const logs = this.debugLogs.slice(0, 50);
622
+ const errorsInLog = logs.filter((l) => l.level === "error").length;
623
+ const clearBtn = `<button data-action="clear-logs" style="background:rgba(255,255,255,0.08);border:none;color:${MUTED};cursor:pointer;font-size:9px;font-family:inherit;padding:2px 7px;border-radius:3px">Clear</button>`;
624
+ const errBadge = errorsInLog > 0 ? `<span style="font-size:9px;color:${RED}">${errorsInLog} error${errorsInLog > 1 ? "s" : ""}</span>` : "";
625
+ const logHeader = `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">${errBadge || `<span></span>`}${clearBtn}</div>`;
626
+ if (logs.length === 0) {
627
+ content = logHeader + `<div style="color:${MUTED};padding:12px;text-align:center">No logs yet</div>`;
628
+ } else {
629
+ content = logHeader + logs.map((l) => {
630
+ const c = l.level === "error" ? RED : l.level === "warn" ? YELLOW : "#a5b4fc";
631
+ return `<div style="padding:2px 0;display:flex;gap:6px;align-items:flex-start;border-bottom:1px solid rgba(255,255,255,0.03)"><span style="color:${MUTED};white-space:nowrap;font-size:9px">${l.ts}</span><span style="color:${c};flex:1;word-break:break-word">${l.msg}</span></div>`;
632
+ }).join("");
633
+ }
634
+ } else if (tab === "network") {
635
+ const calls = this.networkLog.slice(0, 20);
636
+ if (calls.length === 0) {
637
+ content = `<div style="color:${MUTED};padding:16px;text-align:center">No network calls yet</div>`;
638
+ } else {
639
+ content = calls.map((n) => {
640
+ const ok = typeof n.status === "number" && n.status >= 200 && n.status < 300;
641
+ const sc2 = ok ? GREEN : RED;
642
+ return `<div style="padding:3px 0;border-bottom:1px solid rgba(255,255,255,0.03)"><div style="display:flex;justify-content:space-between"><span style="color:${sc2};font-weight:600">${n.status}</span><span style="color:${MUTED}">${n.ms}ms</span><span style="color:${MUTED}">${n.ts}</span></div><div style="color:${TEXT};word-break:break-all">${n.url}</div></div>`;
643
+ }).join("");
644
+ }
645
+ } else if (tab === "events") {
646
+ const events = this.recentAnalyticsEvents.slice(0, 30);
647
+ if (events.length === 0) {
648
+ content = `<div style="color:${MUTED};padding:16px;text-align:center">No events yet</div>`;
649
+ } else {
650
+ content = events.map(
651
+ (e) => `<div style="padding:2px 0;display:flex;justify-content:space-between;border-bottom:1px solid rgba(255,255,255,0.03)"><span style="color:#a5b4fc">${e.name}</span><span style="color:${MUTED}">${e.ts}</span></div>`
652
+ ).join("");
653
+ }
654
+ } else if (tab === "test") {
655
+ const btnStyle = (color) => `background:${color};border:none;color:#fff;cursor:pointer;font-size:10px;font-family:inherit;padding:4px 10px;border-radius:4px;transition:opacity 0.1s`;
656
+ const sectionLabel = (text) => `<div style="color:${MUTED};font-size:9px;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:6px;margin-top:10px">${text}</div>`;
657
+ const feedbackHtml = this.testFeedback.slice(0, 6).map(
658
+ (f) => `<div style="display:flex;gap:6px;align-items:center;padding:2px 0;border-bottom:1px solid rgba(255,255,255,0.03)">
659
+ <span style="color:${f.ok ? GREEN : RED};font-size:10px">&#x25cf;</span>
660
+ <span style="color:${TEXT};flex:1">${f.msg}</span>
661
+ <span style="color:${MUTED}">${f.ts}</span>
662
+ </div>`
663
+ ).join("");
664
+ content = `
665
+ ${sectionLabel("Analytics")}
666
+ <div style="display:flex;gap:4px;flex-wrap:wrap">
667
+ <button data-action="test-event" style="${btnStyle(ACCENT)}">Send test event</button>
668
+ <button data-action="flush" style="${btnStyle("rgba(255,255,255,0.15)")}">Flush queue now</button>
669
+ </div>
670
+ ${sectionLabel("Feature Flags")}
671
+ <div style="display:flex;gap:4px;align-items:center">
672
+ <input data-testinput="flagkey" value="${this.testFlagKey.replace(/"/g, "&quot;")}" placeholder="flag-key" style="flex:1;background:rgba(255,255,255,0.07);border:1px solid ${BORDER};border-radius:4px;color:${TEXT};font-family:inherit;font-size:10px;padding:3px 7px;outline:none" />
673
+ <button data-action="check-flag" style="${btnStyle(ACCENT)}">Check</button>
674
+ </div>
675
+ ${sectionLabel("Errors & Bugs")}
676
+ <div style="display:flex;gap:4px;flex-wrap:wrap">
677
+ <button data-action="test-error" style="${btnStyle(RED)}">Send test error</button>
678
+ <button data-action="test-bug" style="${btnStyle(YELLOW)}">Send test bug</button>
679
+ </div>
680
+ ${sectionLabel("Config")}
681
+ <div style="display:flex;gap:4px;flex-wrap:wrap">
682
+ <button data-action="refresh" style="${btnStyle("rgba(255,255,255,0.15)")}">Refresh config</button>
683
+ <button data-action="clear-logs" style="${btnStyle("rgba(255,255,255,0.15)")}">Clear logs</button>
684
+ </div>
685
+ ${this.testFeedback.length > 0 ? `
686
+ <div style="margin-top:10px;padding-top:6px;border-top:1px solid ${BORDER}">${feedbackHtml}</div>
687
+ ` : ""}
688
+ `;
689
+ }
690
+ }
691
+ el.innerHTML = `
692
+ <div style="background:rgba(255,255,255,0.04);border-bottom:1px solid ${BORDER};padding:7px 10px;display:flex;align-items:center;gap:6px;cursor:pointer" id="__ft_dbg_hdr__">
693
+ <span style="color:${ACCENT};font-weight:700;font-size:10px;letter-spacing:0.05em">&#x2b21; FEATURELY DEBUG</span>
694
+ <span style="flex:1"></span>
695
+ ${minimized ? "" : `<div style="display:flex;gap:4px;flex-wrap:wrap">${tabBtn("sdk", "SDK")}${tabBtn("logs", "Logs", this.errorCount)}${tabBtn("network", "Network")}${tabBtn("events", "Events")}${tabBtn("test", "Test")}</div>`}
696
+ <button id="__ft_dbg_min__" style="background:none;border:none;color:${MUTED};cursor:pointer;font-size:14px;line-height:1;padding:0 2px" title="${minimized ? "Expand" : "Minimize"}">${minimized ? "&#x2b06;" : "&#x2b07;"}</button>
697
+ <button id="__ft_dbg_cls__" style="background:none;border:none;color:${MUTED};cursor:pointer;font-size:14px;line-height:1;padding:0 2px" title="Close">&times;</button>
698
+ </div>
699
+ ${minimized ? "" : `<div style="padding:8px 10px;max-height:300px;overflow-y:auto">${content}</div>`}
700
+ `;
701
+ el.querySelectorAll("[data-tab]").forEach((btn) => {
702
+ btn.onclick = (e) => {
703
+ e.stopPropagation();
704
+ this.debugTab = btn.dataset.tab;
705
+ this.renderDebugOverlay();
706
+ };
707
+ });
708
+ const minBtn = el.querySelector("#__ft_dbg_min__");
709
+ if (minBtn) {
710
+ minBtn.onclick = (e) => {
711
+ e.stopPropagation();
712
+ this.debugMinimized = !this.debugMinimized;
713
+ this.renderDebugOverlay();
714
+ };
715
+ }
716
+ const clsBtn = el.querySelector("#__ft_dbg_cls__");
717
+ if (clsBtn) {
718
+ clsBtn.onclick = (e) => {
719
+ e.stopPropagation();
720
+ this.stopDebugOverlay();
721
+ };
722
+ }
723
+ el.querySelectorAll("[data-action]").forEach((btn) => {
724
+ btn.onclick = (e) => {
725
+ e.stopPropagation();
726
+ void this.handleTestAction(btn.dataset.action);
727
+ };
728
+ });
729
+ el.querySelectorAll("[data-testinput='flagkey']").forEach((input) => {
730
+ input.oninput = (e) => {
731
+ e.stopPropagation();
732
+ this.testFlagKey = e.target.value;
733
+ };
734
+ input.onclick = (e) => e.stopPropagation();
735
+ });
736
+ }
737
+ async handleTestAction(action) {
738
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
739
+ const feedback = (msg, ok) => {
740
+ this.testFeedback.unshift({ msg, ok, ts });
741
+ if (this.testFeedback.length > 10) this.testFeedback.length = 10;
742
+ this.renderDebugOverlay();
743
+ };
744
+ switch (action) {
745
+ case "test-event":
746
+ this.trackEvent("debug_test_event", { source: "debug_panel", ts: (/* @__PURE__ */ new Date()).toISOString() });
747
+ feedback("debug_test_event queued", true);
748
+ break;
749
+ case "flush": {
750
+ const before = this.analyticsQueue.length;
751
+ await this.flushAnalytics();
752
+ feedback(`Flushed ${before} event(s)`, true);
753
+ break;
754
+ }
755
+ case "check-flag": {
756
+ const key = this.testFlagKey.trim();
757
+ if (!key) {
758
+ feedback("Enter a flag key first", false);
759
+ break;
760
+ }
761
+ const enabled = this.isFeatureEnabled(key);
762
+ feedback(`"${key}" \u2192 ${enabled ? "ENABLED \u2713" : "DISABLED \u2717"}`, enabled);
763
+ break;
764
+ }
765
+ case "test-error":
766
+ this.trackEvent("error_reported", {
767
+ message: "Test error from debug panel",
768
+ source: "debug_panel",
769
+ severity: "error"
770
+ });
771
+ feedback("error_reported queued", true);
772
+ break;
773
+ case "test-bug":
774
+ this.trackEvent("bug_reported", {
775
+ title: "Test bug from debug panel",
776
+ source: "debug_panel",
777
+ severity: "medium"
778
+ });
779
+ feedback("bug_reported queued", true);
780
+ break;
781
+ case "refresh":
782
+ await this.fetchConfig();
783
+ feedback("Config refreshed", true);
784
+ break;
785
+ case "clear-logs":
786
+ this.debugLogs = [];
787
+ this.testFeedback = [];
788
+ this.errorCount = 0;
789
+ this.renderDebugOverlay();
790
+ break;
791
+ }
792
+ }
390
793
  setupPageTracking() {
391
794
  if (this.pageTrackingSetup) return;
392
795
  this.pageTrackingSetup = true;
@@ -634,8 +1037,10 @@ var _SiteManager = class _SiteManager {
634
1037
  enableMaintenanceMode() {
635
1038
  if (!this.siteConfig) return;
636
1039
  if (this.shouldBypassMaintenance()) {
1040
+ this.debugLog("info", "[maintenance] ENABLED \u2014 user bypassed (whitelist match)");
637
1041
  return;
638
1042
  }
1043
+ this.debugLog("warn", `[maintenance] ENABLED (type: ${this.siteConfig.maintenance.type})`);
639
1044
  this.showMaintenancePage();
640
1045
  this.trackEvent("maintenance_enabled", {
641
1046
  maintenanceType: this.siteConfig.maintenance.type
@@ -645,6 +1050,7 @@ var _SiteManager = class _SiteManager {
645
1050
  }
646
1051
  }
647
1052
  disableMaintenanceMode() {
1053
+ this.debugLog("info", "[maintenance] DISABLED \u2014 site back online");
648
1054
  this.hideMaintenancePage();
649
1055
  this.trackEvent("maintenance_disabled");
650
1056
  if (this.config.onMaintenanceDisabled) {
@@ -739,6 +1145,7 @@ var _SiteManager = class _SiteManager {
739
1145
  // Status Messages
740
1146
  // ============================================================================
741
1147
  updateMessages() {
1148
+ var _a, _b;
742
1149
  if (!this.siteConfig) return;
743
1150
  const currentTime = /* @__PURE__ */ new Date();
744
1151
  const currentPath = window.location.pathname;
@@ -773,8 +1180,10 @@ var _SiteManager = class _SiteManager {
773
1180
  this.messageContainers.delete(id);
774
1181
  }
775
1182
  });
1183
+ this.debugLog("info", `[messages] ${activeMessages.length} active (${(_b = (_a = this.siteConfig) == null ? void 0 : _a.messages.length) != null ? _b : 0} total)`);
776
1184
  activeMessages.forEach((message) => {
777
1185
  if (!this.messageContainers.has(message.id)) {
1186
+ this.debugLog("info", `[messages] showing "${message.title}" (${message.type}, ${message.style})`);
778
1187
  this.showMessage(message);
779
1188
  if (this.config.onMessageReceived) {
780
1189
  this.config.onMessageReceived(message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "featurely-site-manager",
3
- "version": "1.1.11",
3
+ "version": "1.1.14",
4
4
  "description": "Complete site management SDK for maintenance mode, status messages, feature flags, version checking, and analytics",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",