featurely-site-manager 1.1.18 → 1.1.20
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/CHANGELOG.md +2 -1
- package/README.md +41 -37
- package/dist/index.js +139 -37
- package/dist/index.mjs +139 -37
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -69,11 +69,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
69
69
|
- **CRITICAL FIX**: Replaced Math.random() with crypto.getRandomValues() for CSPRNG session IDs and anonymous IDs
|
|
70
70
|
- **CRITICAL FIX**: Added RegExp pattern escaping to prevent ReDoS (Regular Expression Denial of Service) attacks
|
|
71
71
|
- Added error handling for invalid targetPages patterns
|
|
72
|
+
|
|
72
73
|
## [1.0.5] - 2026-03-15
|
|
73
74
|
|
|
74
75
|
### Security
|
|
75
76
|
|
|
76
|
-
-
|
|
77
|
+
- \*\*CRITICAL FIXAdded DOMPurify sanitization for customHtml maintenance pages to prevent XSS attacks
|
|
77
78
|
- Blocks dangerous tags (script, iframe, embed, object, etc.)
|
|
78
79
|
- Removes event handlers (onerror, onload, onclick, etc.)
|
|
79
80
|
- Prevents javascript: URIs and other malicious protocols
|
package/README.md
CHANGED
|
@@ -179,8 +179,12 @@ const manager = new SiteManager({
|
|
|
179
179
|
onUpdateRequired: (versionInfo) => {
|
|
180
180
|
console.log("Update required:", versionInfo.latestVersion);
|
|
181
181
|
// Force user to update (breaking changes)
|
|
182
|
-
if (
|
|
183
|
-
|
|
182
|
+
if (
|
|
183
|
+
confirm(
|
|
184
|
+
`Update required: ${versionInfo.latestVersion.title}\n\n${versionInfo.latestVersion.releaseNotes}`
|
|
185
|
+
)
|
|
186
|
+
) {
|
|
187
|
+
window.location.href = versionInfo.latestVersion.downloadUrl || "/update";
|
|
184
188
|
}
|
|
185
189
|
},
|
|
186
190
|
});
|
|
@@ -255,12 +259,12 @@ manager.trackEvent("purchase_completed", {
|
|
|
255
259
|
|
|
256
260
|
When `enableAnalytics` is `true` (default), the SDK automatically tracks:
|
|
257
261
|
|
|
258
|
-
| Event
|
|
259
|
-
|
|
|
260
|
-
| `session_start` | On first page load
|
|
261
|
-
| `page_view`
|
|
262
|
-
| `page_exit`
|
|
263
|
-
| `user_login`
|
|
262
|
+
| Event | When | Properties |
|
|
263
|
+
| --------------- | ---------------------------------------------- | ------------------------------------------------------------------ |
|
|
264
|
+
| `session_start` | On first page load | `path`, `title`, `referrer` |
|
|
265
|
+
| `page_view` | On every page navigation | `path`, `title`, `referrer`, `isLoggedIn`, `userId?`, `userEmail?` |
|
|
266
|
+
| `page_exit` | When navigating away from a page | `path`, `durationSeconds` |
|
|
267
|
+
| `user_login` | First time `setUser()` is called with a userId | `userId`, `userEmail?`, `userName?` |
|
|
264
268
|
|
|
265
269
|
Page tracking is SPA-compatible — it patches `history.pushState` and `history.replaceState` and listens to `popstate` so navigations in React, Next.js, Vue, and similar frameworks are captured automatically.
|
|
266
270
|
|
|
@@ -282,13 +286,13 @@ manager.init();
|
|
|
282
286
|
|
|
283
287
|
The overlay panel has five tabs:
|
|
284
288
|
|
|
285
|
-
| Tab
|
|
286
|
-
|
|
|
287
|
-
| **SDK**
|
|
288
|
-
| **Logs**
|
|
289
|
-
| **Network** | Config fetch log with status codes and timestamps
|
|
290
|
-
| **Events**
|
|
291
|
-
| **Test**
|
|
289
|
+
| Tab | Contents |
|
|
290
|
+
| ----------- | ---------------------------------------------------------------------------- |
|
|
291
|
+
| **SDK** | Environment, maintenance status, active messages, feature flag count |
|
|
292
|
+
| **Logs** | Real-time log stream (info / warn / error); error badge on tab; Clear button |
|
|
293
|
+
| **Network** | Config fetch log with status codes and timestamps |
|
|
294
|
+
| **Events** | Analytics events queued/sent |
|
|
295
|
+
| **Test** | Buttons to test analytics, errors, bugs, feature flags, flush, and refresh |
|
|
292
296
|
|
|
293
297
|
#### Test Tab Actions
|
|
294
298
|
|
|
@@ -332,29 +336,29 @@ manager.init();
|
|
|
332
336
|
|
|
333
337
|
### SiteManagerConfig
|
|
334
338
|
|
|
335
|
-
| Option | Type | Required | Default
|
|
336
|
-
| ------------------------ | --------------- | -------- |
|
|
337
|
-
| `apiKey` | `string` | ✅ | -
|
|
338
|
-
| `projectId` | `string` | ✅ | -
|
|
339
|
+
| Option | Type | Required | Default | Description |
|
|
340
|
+
| ------------------------ | --------------- | -------- | ------------------------ | ----------------------------------------- |
|
|
341
|
+
| `apiKey` | `string` | ✅ | - | Your Featurely API key |
|
|
342
|
+
| `projectId` | `string` | ✅ | - | Your Featurely project ID |
|
|
339
343
|
| `apiUrl` | `string` | ❌ | `'https://featurely.no'` | Custom API endpoint |
|
|
340
|
-
| `pollInterval` | `number` | ❌ | `60000`
|
|
341
|
-
| `userEmail` | `string` | ❌ | -
|
|
342
|
-
| `userId` | `string` | ❌ | -
|
|
343
|
-
| `bypassCheck` | `() => boolean` | ❌ | -
|
|
344
|
-
| `onMaintenanceEnabled` | `function` | ❌ | -
|
|
345
|
-
| `onMaintenanceDisabled` | `function` | ❌ | -
|
|
346
|
-
| `onMessageReceived` | `function` | ❌ | -
|
|
347
|
-
| `onMessageDismissed` | `function` | ❌ | -
|
|
348
|
-
| `onFeatureFlagsUpdated` | `function` | ❌ | -
|
|
349
|
-
| `enableAnalytics` | `boolean` | ❌ | `true`
|
|
350
|
-
| `analyticsFlushInterval` | `number` | ❌ | `60000`
|
|
351
|
-
| `appVersion` | `string` | ❌ | -
|
|
352
|
-
| `enableVersionCheck` | `boolean` | ❌ | `false`
|
|
353
|
-
| `versionCheckInterval` | `number` | ❌ | `3600000`
|
|
354
|
-
| `onUpdateAvailable` | `function` | ❌ | -
|
|
355
|
-
| `onUpdateRequired` | `function` | ❌ | -
|
|
356
|
-
| `onError` | `function` | ❌ | -
|
|
357
|
-
| `debugMode` | `boolean` | ❌ | `false`
|
|
344
|
+
| `pollInterval` | `number` | ❌ | `60000` | Config polling interval in ms |
|
|
345
|
+
| `userEmail` | `string` | ❌ | - | User email for whitelist & targeting |
|
|
346
|
+
| `userId` | `string` | ❌ | - | User ID for analytics & feature flags |
|
|
347
|
+
| `bypassCheck` | `() => boolean` | ❌ | - | Custom maintenance bypass function |
|
|
348
|
+
| `onMaintenanceEnabled` | `function` | ❌ | - | Maintenance enabled callback |
|
|
349
|
+
| `onMaintenanceDisabled` | `function` | ❌ | - | Maintenance disabled callback |
|
|
350
|
+
| `onMessageReceived` | `function` | ❌ | - | Message received callback |
|
|
351
|
+
| `onMessageDismissed` | `function` | ❌ | - | Message dismissed callback |
|
|
352
|
+
| `onFeatureFlagsUpdated` | `function` | ❌ | - | Feature flags updated callback |
|
|
353
|
+
| `enableAnalytics` | `boolean` | ❌ | `true` | Enable analytics tracking |
|
|
354
|
+
| `analyticsFlushInterval` | `number` | ❌ | `60000` | Analytics flush interval in ms |
|
|
355
|
+
| `appVersion` | `string` | ❌ | - | Current app version for version checking |
|
|
356
|
+
| `enableVersionCheck` | `boolean` | ❌ | `false` | Enable automatic version checking |
|
|
357
|
+
| `versionCheckInterval` | `number` | ❌ | `3600000` | Version check interval in ms (1 hour) |
|
|
358
|
+
| `onUpdateAvailable` | `function` | ❌ | - | Callback when update is available |
|
|
359
|
+
| `onUpdateRequired` | `function` | ❌ | - | Callback when update is required (forced) |
|
|
360
|
+
| `onError` | `function` | ❌ | - | Error callback |
|
|
361
|
+
| `debugMode` | `boolean` | ❌ | `false` | Force-enable debug overlay locally |
|
|
358
362
|
|
|
359
363
|
## 🎯 API Methods
|
|
360
364
|
|
package/dist/index.js
CHANGED
|
@@ -115,8 +115,14 @@ var _SiteManager = class _SiteManager {
|
|
|
115
115
|
if (this.config.debugMode) {
|
|
116
116
|
this.setupDebugOverlay();
|
|
117
117
|
}
|
|
118
|
-
this.debugLog(
|
|
119
|
-
|
|
118
|
+
this.debugLog(
|
|
119
|
+
"info",
|
|
120
|
+
`[init] v1.1.19 | project: ${this.config.projectId} | hostname: ${typeof window !== "undefined" ? window.location.hostname : "\u2014"}`
|
|
121
|
+
);
|
|
122
|
+
this.debugLog(
|
|
123
|
+
"info",
|
|
124
|
+
`[init] analytics: ${this.config.enableAnalytics ? "on" : "off"} | poll: ${this.config.pollInterval}ms | debug: ${this.config.debugMode ? "on" : "off"}`
|
|
125
|
+
);
|
|
120
126
|
if (this.config.enableAnalytics) {
|
|
121
127
|
this.debugLog("info", "[init] starting analytics + page tracking");
|
|
122
128
|
this.startAnalyticsFlushing();
|
|
@@ -127,7 +133,10 @@ var _SiteManager = class _SiteManager {
|
|
|
127
133
|
this.startPolling();
|
|
128
134
|
this.debugLog("info", `[init] polling every ${this.config.pollInterval / 1e3}s`);
|
|
129
135
|
if (this.config.enableVersionCheck && this.config.appVersion) {
|
|
130
|
-
this.debugLog(
|
|
136
|
+
this.debugLog(
|
|
137
|
+
"info",
|
|
138
|
+
`[init] checking version (current: ${this.config.appVersion})`
|
|
139
|
+
);
|
|
131
140
|
await this.checkVersion();
|
|
132
141
|
this.startVersionChecking();
|
|
133
142
|
}
|
|
@@ -302,7 +311,10 @@ var _SiteManager = class _SiteManager {
|
|
|
302
311
|
properties,
|
|
303
312
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
304
313
|
});
|
|
305
|
-
this.recentAnalyticsEvents.unshift({
|
|
314
|
+
this.recentAnalyticsEvents.unshift({
|
|
315
|
+
name: eventName,
|
|
316
|
+
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString()
|
|
317
|
+
});
|
|
306
318
|
if (this.recentAnalyticsEvents.length > 50) this.recentAnalyticsEvents.length = 50;
|
|
307
319
|
this.debugLog("info", `trackEvent: ${eventName}`);
|
|
308
320
|
if (this.analyticsQueue.length >= 10) {
|
|
@@ -324,7 +336,11 @@ var _SiteManager = class _SiteManager {
|
|
|
324
336
|
}
|
|
325
337
|
}
|
|
326
338
|
);
|
|
327
|
-
this.debugNetwork(
|
|
339
|
+
this.debugNetwork(
|
|
340
|
+
`/api/public/v1/site-config`,
|
|
341
|
+
response.status,
|
|
342
|
+
Date.now() - fetchStartMs
|
|
343
|
+
);
|
|
328
344
|
if (!response.ok) {
|
|
329
345
|
const errorData = await response.json().catch(() => ({}));
|
|
330
346
|
const message = errorData.error || response.statusText;
|
|
@@ -357,20 +373,55 @@ var _SiteManager = class _SiteManager {
|
|
|
357
373
|
delta.push(`flags: ${prevFlagCount}\u2192${newConfig.featureFlags.length}`);
|
|
358
374
|
if (newConfig.messages.length !== prevMsgCount)
|
|
359
375
|
delta.push(`messages: ${prevMsgCount}\u2192${newConfig.messages.length}`);
|
|
360
|
-
this.debugLog(
|
|
376
|
+
this.debugLog(
|
|
377
|
+
"info",
|
|
378
|
+
`[config] updated${delta.length ? ": " + delta.join(", ") : " (structure changed)"}`
|
|
379
|
+
);
|
|
361
380
|
const wasMaintenanceEnabled = (_g = this.siteConfig) == null ? void 0 : _g.maintenance.enabled;
|
|
362
381
|
const oldFeatureFlags = ((_h = this.siteConfig) == null ? void 0 : _h.featureFlags) || [];
|
|
363
382
|
this.siteConfig = newConfig;
|
|
364
|
-
|
|
383
|
+
const isGlobalDebug = newConfig.debugMode === true;
|
|
384
|
+
const hostname = typeof window !== "undefined" ? window.location.hostname : "";
|
|
385
|
+
const envs = newConfig.environments;
|
|
386
|
+
const matchedEnv = hostname ? envs == null ? void 0 : envs.find((e) => {
|
|
387
|
+
if (typeof e.url !== "string" || e.url === "") return false;
|
|
388
|
+
let storedHost;
|
|
389
|
+
try {
|
|
390
|
+
storedHost = e.url.includes("://") ? new URL(e.url).hostname : e.url;
|
|
391
|
+
} catch {
|
|
392
|
+
storedHost = e.url;
|
|
393
|
+
}
|
|
394
|
+
return storedHost === hostname;
|
|
395
|
+
}) : void 0;
|
|
396
|
+
const isEnvDebug = (matchedEnv == null ? void 0 : matchedEnv.debugEnabled) === true;
|
|
397
|
+
const shouldDebug = isGlobalDebug || isEnvDebug;
|
|
398
|
+
if (shouldDebug && !this.debugOverlayEl) {
|
|
399
|
+
if (isEnvDebug && !isGlobalDebug) {
|
|
400
|
+
this.debugLog(
|
|
401
|
+
"info",
|
|
402
|
+
`[env] matched environment "${matchedEnv.name}" (${hostname}) \u2014 enabling debug overlay`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
365
405
|
this.setupDebugOverlay();
|
|
406
|
+
} else if (!shouldDebug && this.debugOverlayEl) {
|
|
407
|
+
this.debugLog("info", "[debug] debug disabled by config \u2014 removing overlay");
|
|
408
|
+
this.stopDebugOverlay();
|
|
366
409
|
}
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.
|
|
410
|
+
if (matchedEnv && matchedEnv.analyticsEnabled === false) {
|
|
411
|
+
if (this.config.enableAnalytics) {
|
|
412
|
+
this.debugLog(
|
|
413
|
+
"info",
|
|
414
|
+
`[env] analytics disabled for environment "${matchedEnv.name}" (${hostname}) \u2014 stopping analytics`
|
|
415
|
+
);
|
|
416
|
+
this.stopAnalyticsFlushing();
|
|
417
|
+
}
|
|
418
|
+
} else if (matchedEnv && matchedEnv.analyticsEnabled !== false && this.config.enableAnalytics) {
|
|
419
|
+
if (!this.analyticsFlushIntervalId) {
|
|
420
|
+
this.debugLog(
|
|
421
|
+
"info",
|
|
422
|
+
`[env] analytics re-enabled for environment "${matchedEnv.name}" (${hostname})`
|
|
423
|
+
);
|
|
424
|
+
this.startAnalyticsFlushing();
|
|
374
425
|
}
|
|
375
426
|
}
|
|
376
427
|
if (newConfig.maintenance.enabled && !wasMaintenanceEnabled) {
|
|
@@ -392,7 +443,10 @@ var _SiteManager = class _SiteManager {
|
|
|
392
443
|
} catch (error) {
|
|
393
444
|
this.consecutiveFetchFailures++;
|
|
394
445
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
395
|
-
this.debugLog(
|
|
446
|
+
this.debugLog(
|
|
447
|
+
"error",
|
|
448
|
+
`[config] fetch failed (attempt ${this.consecutiveFetchFailures}): ${errMsg}`
|
|
449
|
+
);
|
|
396
450
|
this.errorCount++;
|
|
397
451
|
if (this.consecutiveFetchFailures <= _SiteManager.MAX_CONSECUTIVE_FAILURES) {
|
|
398
452
|
const isNetworkError = error instanceof TypeError && (error.message.includes("NetworkError") || error.message.includes("Failed to fetch") || error.message.includes("fetch"));
|
|
@@ -411,7 +465,10 @@ var _SiteManager = class _SiteManager {
|
|
|
411
465
|
} else if (this.consecutiveFetchFailures === _SiteManager.MAX_CONSECUTIVE_FAILURES + 1) {
|
|
412
466
|
const silenceMsg = `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`;
|
|
413
467
|
console.error(silenceMsg);
|
|
414
|
-
this.debugLog(
|
|
468
|
+
this.debugLog(
|
|
469
|
+
"error",
|
|
470
|
+
`[config] silencing errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} consecutive failures`
|
|
471
|
+
);
|
|
415
472
|
}
|
|
416
473
|
}
|
|
417
474
|
}
|
|
@@ -450,7 +507,10 @@ var _SiteManager = class _SiteManager {
|
|
|
450
507
|
const eventsToSend = [...this.analyticsQueue];
|
|
451
508
|
this.analyticsQueue = [];
|
|
452
509
|
this.analyticsEventsSent += eventsToSend.length;
|
|
453
|
-
this.debugLog(
|
|
510
|
+
this.debugLog(
|
|
511
|
+
"info",
|
|
512
|
+
`[analytics] flushing ${eventsToSend.length} event(s) (total sent: ${this.analyticsEventsSent})`
|
|
513
|
+
);
|
|
454
514
|
for (const event of eventsToSend) {
|
|
455
515
|
const payload = JSON.stringify({
|
|
456
516
|
eventName: event.eventName,
|
|
@@ -474,7 +534,11 @@ var _SiteManager = class _SiteManager {
|
|
|
474
534
|
body: payload,
|
|
475
535
|
keepalive: true
|
|
476
536
|
});
|
|
477
|
-
this.debugNetwork(
|
|
537
|
+
this.debugNetwork(
|
|
538
|
+
`/api/projects/${this.config.projectId}/analytics/events`,
|
|
539
|
+
res.status,
|
|
540
|
+
Date.now() - t0
|
|
541
|
+
);
|
|
478
542
|
if (!res.ok) {
|
|
479
543
|
const body = await res.text().catch(() => "(unreadable)");
|
|
480
544
|
const errDetail = `[analytics] event "${event.eventName}" rejected: ${res.status} ${res.statusText} \u2014 ${body}`;
|
|
@@ -486,11 +550,20 @@ var _SiteManager = class _SiteManager {
|
|
|
486
550
|
const sentViaBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, payload);
|
|
487
551
|
const errMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
488
552
|
if (!sentViaBeacon) {
|
|
489
|
-
console.error(
|
|
490
|
-
|
|
553
|
+
console.error(
|
|
554
|
+
`[Featurely] Failed to send analytics event "${event.eventName}":`,
|
|
555
|
+
fetchError
|
|
556
|
+
);
|
|
557
|
+
this.debugLog(
|
|
558
|
+
"error",
|
|
559
|
+
`[analytics] send failed for "${event.eventName}": ${errMsg}`
|
|
560
|
+
);
|
|
491
561
|
this.errorCount++;
|
|
492
562
|
} else {
|
|
493
|
-
this.debugLog(
|
|
563
|
+
this.debugLog(
|
|
564
|
+
"warn",
|
|
565
|
+
`[analytics] fetch failed for "${event.eventName}" (${errMsg}), delivered via sendBeacon`
|
|
566
|
+
);
|
|
494
567
|
}
|
|
495
568
|
}
|
|
496
569
|
}
|
|
@@ -537,14 +610,18 @@ var _SiteManager = class _SiteManager {
|
|
|
537
610
|
this.originalConsoleError = console.error.bind(console);
|
|
538
611
|
console.error = (...args) => {
|
|
539
612
|
this.originalConsoleError(...args);
|
|
540
|
-
const msg = args.map(
|
|
613
|
+
const msg = args.map(
|
|
614
|
+
(a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)
|
|
615
|
+
).join(" ");
|
|
541
616
|
this.errorCount++;
|
|
542
617
|
this.debugLog("error", `[console.error] ${msg.slice(0, 300)}`);
|
|
543
618
|
};
|
|
544
619
|
this.originalConsoleWarn = console.warn.bind(console);
|
|
545
620
|
console.warn = (...args) => {
|
|
546
621
|
this.originalConsoleWarn(...args);
|
|
547
|
-
const msg = args.map(
|
|
622
|
+
const msg = args.map(
|
|
623
|
+
(a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)
|
|
624
|
+
).join(" ");
|
|
548
625
|
this.debugLog("warn", `[console.warn] ${msg.slice(0, 300)}`);
|
|
549
626
|
};
|
|
550
627
|
window.addEventListener("error", (e) => {
|
|
@@ -597,7 +674,7 @@ var _SiteManager = class _SiteManager {
|
|
|
597
674
|
this.debugOverlayEl = el;
|
|
598
675
|
this.setupGlobalErrorCapture();
|
|
599
676
|
this.debugLog("info", `[site-manager] debug overlay initialized`);
|
|
600
|
-
this.debugLog("info", `[site-manager] v1.1.
|
|
677
|
+
this.debugLog("info", `[site-manager] v1.1.19 | project: ${this.config.projectId}`);
|
|
601
678
|
this.renderDebugOverlay();
|
|
602
679
|
this.debugRefreshId = setInterval(() => this.renderDebugOverlay(), 1500);
|
|
603
680
|
}
|
|
@@ -779,7 +856,10 @@ var _SiteManager = class _SiteManager {
|
|
|
779
856
|
};
|
|
780
857
|
switch (action) {
|
|
781
858
|
case "test-event":
|
|
782
|
-
this.trackEvent("debug_test_event", {
|
|
859
|
+
this.trackEvent("debug_test_event", {
|
|
860
|
+
source: "debug_panel",
|
|
861
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
862
|
+
});
|
|
783
863
|
feedback("debug_test_event queued", true);
|
|
784
864
|
break;
|
|
785
865
|
case "flush": {
|
|
@@ -821,7 +901,10 @@ var _SiteManager = class _SiteManager {
|
|
|
821
901
|
case "copy-logs": {
|
|
822
902
|
const text = this.debugLogs.slice().reverse().map((l) => `[${l.ts}] [${l.level.toUpperCase()}] ${l.msg}`).join("\n");
|
|
823
903
|
void navigator.clipboard.writeText(text).then(() => {
|
|
824
|
-
feedback(
|
|
904
|
+
feedback(
|
|
905
|
+
`Copied ${this.debugLogs.length} log line${this.debugLogs.length !== 1 ? "s" : ""}`,
|
|
906
|
+
true
|
|
907
|
+
);
|
|
825
908
|
}).catch(() => {
|
|
826
909
|
feedback("Copy failed \u2014 clipboard not available", false);
|
|
827
910
|
});
|
|
@@ -939,9 +1022,7 @@ var _SiteManager = class _SiteManager {
|
|
|
939
1022
|
async checkVersion(currentVersion) {
|
|
940
1023
|
const versionToCheck = currentVersion || this.config.appVersion;
|
|
941
1024
|
if (!versionToCheck) {
|
|
942
|
-
console.warn(
|
|
943
|
-
"Featurely Site Manager: appVersion not provided for version check"
|
|
944
|
-
);
|
|
1025
|
+
console.warn("Featurely Site Manager: appVersion not provided for version check");
|
|
945
1026
|
return null;
|
|
946
1027
|
}
|
|
947
1028
|
try {
|
|
@@ -1085,7 +1166,10 @@ var _SiteManager = class _SiteManager {
|
|
|
1085
1166
|
this.debugLog("info", "[maintenance] ENABLED \u2014 user bypassed (whitelist match)");
|
|
1086
1167
|
return;
|
|
1087
1168
|
}
|
|
1088
|
-
this.debugLog(
|
|
1169
|
+
this.debugLog(
|
|
1170
|
+
"warn",
|
|
1171
|
+
`[maintenance] ENABLED (type: ${this.siteConfig.maintenance.type})`
|
|
1172
|
+
);
|
|
1089
1173
|
this.showMaintenancePage();
|
|
1090
1174
|
this.trackEvent("maintenance_enabled", {
|
|
1091
1175
|
maintenanceType: this.siteConfig.maintenance.type
|
|
@@ -1157,7 +1241,17 @@ var _SiteManager = class _SiteManager {
|
|
|
1157
1241
|
"button"
|
|
1158
1242
|
],
|
|
1159
1243
|
ALLOWED_ATTR: ["class", "id", "href", "src", "alt", "title", "style"],
|
|
1160
|
-
FORBID_TAGS: [
|
|
1244
|
+
FORBID_TAGS: [
|
|
1245
|
+
"script",
|
|
1246
|
+
"iframe",
|
|
1247
|
+
"embed",
|
|
1248
|
+
"object",
|
|
1249
|
+
"applet",
|
|
1250
|
+
"meta",
|
|
1251
|
+
"link",
|
|
1252
|
+
"form",
|
|
1253
|
+
"input"
|
|
1254
|
+
],
|
|
1161
1255
|
FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover"],
|
|
1162
1256
|
ALLOW_DATA_ATTR: false
|
|
1163
1257
|
});
|
|
@@ -1225,10 +1319,16 @@ var _SiteManager = class _SiteManager {
|
|
|
1225
1319
|
this.messageContainers.delete(id);
|
|
1226
1320
|
}
|
|
1227
1321
|
});
|
|
1228
|
-
this.debugLog(
|
|
1322
|
+
this.debugLog(
|
|
1323
|
+
"info",
|
|
1324
|
+
`[messages] ${activeMessages.length} active (${(_b = (_a = this.siteConfig) == null ? void 0 : _a.messages.length) != null ? _b : 0} total)`
|
|
1325
|
+
);
|
|
1229
1326
|
activeMessages.forEach((message) => {
|
|
1230
1327
|
if (!this.messageContainers.has(message.id)) {
|
|
1231
|
-
this.debugLog(
|
|
1328
|
+
this.debugLog(
|
|
1329
|
+
"info",
|
|
1330
|
+
`[messages] showing "${message.title}" (${message.type}, ${message.style})`
|
|
1331
|
+
);
|
|
1232
1332
|
this.showMessage(message);
|
|
1233
1333
|
if (this.config.onMessageReceived) {
|
|
1234
1334
|
this.config.onMessageReceived(message);
|
|
@@ -1494,7 +1594,8 @@ var _SiteManager = class _SiteManager {
|
|
|
1494
1594
|
|
|
1495
1595
|
.featurely-message-body {
|
|
1496
1596
|
font-size: 14px;
|
|
1497
|
-
opacity
|
|
1597
|
+
/* opacity < 1 reduces effective contrast; inherit full color from parent */
|
|
1598
|
+
opacity: 1;
|
|
1498
1599
|
}
|
|
1499
1600
|
|
|
1500
1601
|
.featurely-message-cta {
|
|
@@ -1545,16 +1646,17 @@ var _SiteManager = class _SiteManager {
|
|
|
1545
1646
|
|
|
1546
1647
|
.featurely-message-warning {
|
|
1547
1648
|
background: #fff3e0;
|
|
1548
|
-
|
|
1649
|
+
/* #803600 on #fff3e0 \u2248 7.8:1 contrast \u2014 WCAG AA compliant */
|
|
1650
|
+
color: #803600;
|
|
1549
1651
|
}
|
|
1550
1652
|
|
|
1551
1653
|
.featurely-message-warning .featurely-message-cta {
|
|
1552
|
-
background: #
|
|
1654
|
+
background: #803600;
|
|
1553
1655
|
color: white;
|
|
1554
1656
|
}
|
|
1555
1657
|
|
|
1556
1658
|
.featurely-message-warning .featurely-message-cta:hover {
|
|
1557
|
-
background: #
|
|
1659
|
+
background: #6b2d00;
|
|
1558
1660
|
}
|
|
1559
1661
|
|
|
1560
1662
|
.featurely-message-error {
|
package/dist/index.mjs
CHANGED
|
@@ -80,8 +80,14 @@ var _SiteManager = class _SiteManager {
|
|
|
80
80
|
if (this.config.debugMode) {
|
|
81
81
|
this.setupDebugOverlay();
|
|
82
82
|
}
|
|
83
|
-
this.debugLog(
|
|
84
|
-
|
|
83
|
+
this.debugLog(
|
|
84
|
+
"info",
|
|
85
|
+
`[init] v1.1.19 | project: ${this.config.projectId} | hostname: ${typeof window !== "undefined" ? window.location.hostname : "\u2014"}`
|
|
86
|
+
);
|
|
87
|
+
this.debugLog(
|
|
88
|
+
"info",
|
|
89
|
+
`[init] analytics: ${this.config.enableAnalytics ? "on" : "off"} | poll: ${this.config.pollInterval}ms | debug: ${this.config.debugMode ? "on" : "off"}`
|
|
90
|
+
);
|
|
85
91
|
if (this.config.enableAnalytics) {
|
|
86
92
|
this.debugLog("info", "[init] starting analytics + page tracking");
|
|
87
93
|
this.startAnalyticsFlushing();
|
|
@@ -92,7 +98,10 @@ var _SiteManager = class _SiteManager {
|
|
|
92
98
|
this.startPolling();
|
|
93
99
|
this.debugLog("info", `[init] polling every ${this.config.pollInterval / 1e3}s`);
|
|
94
100
|
if (this.config.enableVersionCheck && this.config.appVersion) {
|
|
95
|
-
this.debugLog(
|
|
101
|
+
this.debugLog(
|
|
102
|
+
"info",
|
|
103
|
+
`[init] checking version (current: ${this.config.appVersion})`
|
|
104
|
+
);
|
|
96
105
|
await this.checkVersion();
|
|
97
106
|
this.startVersionChecking();
|
|
98
107
|
}
|
|
@@ -267,7 +276,10 @@ var _SiteManager = class _SiteManager {
|
|
|
267
276
|
properties,
|
|
268
277
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
269
278
|
});
|
|
270
|
-
this.recentAnalyticsEvents.unshift({
|
|
279
|
+
this.recentAnalyticsEvents.unshift({
|
|
280
|
+
name: eventName,
|
|
281
|
+
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString()
|
|
282
|
+
});
|
|
271
283
|
if (this.recentAnalyticsEvents.length > 50) this.recentAnalyticsEvents.length = 50;
|
|
272
284
|
this.debugLog("info", `trackEvent: ${eventName}`);
|
|
273
285
|
if (this.analyticsQueue.length >= 10) {
|
|
@@ -289,7 +301,11 @@ var _SiteManager = class _SiteManager {
|
|
|
289
301
|
}
|
|
290
302
|
}
|
|
291
303
|
);
|
|
292
|
-
this.debugNetwork(
|
|
304
|
+
this.debugNetwork(
|
|
305
|
+
`/api/public/v1/site-config`,
|
|
306
|
+
response.status,
|
|
307
|
+
Date.now() - fetchStartMs
|
|
308
|
+
);
|
|
293
309
|
if (!response.ok) {
|
|
294
310
|
const errorData = await response.json().catch(() => ({}));
|
|
295
311
|
const message = errorData.error || response.statusText;
|
|
@@ -322,20 +338,55 @@ var _SiteManager = class _SiteManager {
|
|
|
322
338
|
delta.push(`flags: ${prevFlagCount}\u2192${newConfig.featureFlags.length}`);
|
|
323
339
|
if (newConfig.messages.length !== prevMsgCount)
|
|
324
340
|
delta.push(`messages: ${prevMsgCount}\u2192${newConfig.messages.length}`);
|
|
325
|
-
this.debugLog(
|
|
341
|
+
this.debugLog(
|
|
342
|
+
"info",
|
|
343
|
+
`[config] updated${delta.length ? ": " + delta.join(", ") : " (structure changed)"}`
|
|
344
|
+
);
|
|
326
345
|
const wasMaintenanceEnabled = (_g = this.siteConfig) == null ? void 0 : _g.maintenance.enabled;
|
|
327
346
|
const oldFeatureFlags = ((_h = this.siteConfig) == null ? void 0 : _h.featureFlags) || [];
|
|
328
347
|
this.siteConfig = newConfig;
|
|
329
|
-
|
|
348
|
+
const isGlobalDebug = newConfig.debugMode === true;
|
|
349
|
+
const hostname = typeof window !== "undefined" ? window.location.hostname : "";
|
|
350
|
+
const envs = newConfig.environments;
|
|
351
|
+
const matchedEnv = hostname ? envs == null ? void 0 : envs.find((e) => {
|
|
352
|
+
if (typeof e.url !== "string" || e.url === "") return false;
|
|
353
|
+
let storedHost;
|
|
354
|
+
try {
|
|
355
|
+
storedHost = e.url.includes("://") ? new URL(e.url).hostname : e.url;
|
|
356
|
+
} catch {
|
|
357
|
+
storedHost = e.url;
|
|
358
|
+
}
|
|
359
|
+
return storedHost === hostname;
|
|
360
|
+
}) : void 0;
|
|
361
|
+
const isEnvDebug = (matchedEnv == null ? void 0 : matchedEnv.debugEnabled) === true;
|
|
362
|
+
const shouldDebug = isGlobalDebug || isEnvDebug;
|
|
363
|
+
if (shouldDebug && !this.debugOverlayEl) {
|
|
364
|
+
if (isEnvDebug && !isGlobalDebug) {
|
|
365
|
+
this.debugLog(
|
|
366
|
+
"info",
|
|
367
|
+
`[env] matched environment "${matchedEnv.name}" (${hostname}) \u2014 enabling debug overlay`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
330
370
|
this.setupDebugOverlay();
|
|
371
|
+
} else if (!shouldDebug && this.debugOverlayEl) {
|
|
372
|
+
this.debugLog("info", "[debug] debug disabled by config \u2014 removing overlay");
|
|
373
|
+
this.stopDebugOverlay();
|
|
331
374
|
}
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
this.
|
|
375
|
+
if (matchedEnv && matchedEnv.analyticsEnabled === false) {
|
|
376
|
+
if (this.config.enableAnalytics) {
|
|
377
|
+
this.debugLog(
|
|
378
|
+
"info",
|
|
379
|
+
`[env] analytics disabled for environment "${matchedEnv.name}" (${hostname}) \u2014 stopping analytics`
|
|
380
|
+
);
|
|
381
|
+
this.stopAnalyticsFlushing();
|
|
382
|
+
}
|
|
383
|
+
} else if (matchedEnv && matchedEnv.analyticsEnabled !== false && this.config.enableAnalytics) {
|
|
384
|
+
if (!this.analyticsFlushIntervalId) {
|
|
385
|
+
this.debugLog(
|
|
386
|
+
"info",
|
|
387
|
+
`[env] analytics re-enabled for environment "${matchedEnv.name}" (${hostname})`
|
|
388
|
+
);
|
|
389
|
+
this.startAnalyticsFlushing();
|
|
339
390
|
}
|
|
340
391
|
}
|
|
341
392
|
if (newConfig.maintenance.enabled && !wasMaintenanceEnabled) {
|
|
@@ -357,7 +408,10 @@ var _SiteManager = class _SiteManager {
|
|
|
357
408
|
} catch (error) {
|
|
358
409
|
this.consecutiveFetchFailures++;
|
|
359
410
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
360
|
-
this.debugLog(
|
|
411
|
+
this.debugLog(
|
|
412
|
+
"error",
|
|
413
|
+
`[config] fetch failed (attempt ${this.consecutiveFetchFailures}): ${errMsg}`
|
|
414
|
+
);
|
|
361
415
|
this.errorCount++;
|
|
362
416
|
if (this.consecutiveFetchFailures <= _SiteManager.MAX_CONSECUTIVE_FAILURES) {
|
|
363
417
|
const isNetworkError = error instanceof TypeError && (error.message.includes("NetworkError") || error.message.includes("Failed to fetch") || error.message.includes("fetch"));
|
|
@@ -376,7 +430,10 @@ var _SiteManager = class _SiteManager {
|
|
|
376
430
|
} else if (this.consecutiveFetchFailures === _SiteManager.MAX_CONSECUTIVE_FAILURES + 1) {
|
|
377
431
|
const silenceMsg = `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`;
|
|
378
432
|
console.error(silenceMsg);
|
|
379
|
-
this.debugLog(
|
|
433
|
+
this.debugLog(
|
|
434
|
+
"error",
|
|
435
|
+
`[config] silencing errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} consecutive failures`
|
|
436
|
+
);
|
|
380
437
|
}
|
|
381
438
|
}
|
|
382
439
|
}
|
|
@@ -415,7 +472,10 @@ var _SiteManager = class _SiteManager {
|
|
|
415
472
|
const eventsToSend = [...this.analyticsQueue];
|
|
416
473
|
this.analyticsQueue = [];
|
|
417
474
|
this.analyticsEventsSent += eventsToSend.length;
|
|
418
|
-
this.debugLog(
|
|
475
|
+
this.debugLog(
|
|
476
|
+
"info",
|
|
477
|
+
`[analytics] flushing ${eventsToSend.length} event(s) (total sent: ${this.analyticsEventsSent})`
|
|
478
|
+
);
|
|
419
479
|
for (const event of eventsToSend) {
|
|
420
480
|
const payload = JSON.stringify({
|
|
421
481
|
eventName: event.eventName,
|
|
@@ -439,7 +499,11 @@ var _SiteManager = class _SiteManager {
|
|
|
439
499
|
body: payload,
|
|
440
500
|
keepalive: true
|
|
441
501
|
});
|
|
442
|
-
this.debugNetwork(
|
|
502
|
+
this.debugNetwork(
|
|
503
|
+
`/api/projects/${this.config.projectId}/analytics/events`,
|
|
504
|
+
res.status,
|
|
505
|
+
Date.now() - t0
|
|
506
|
+
);
|
|
443
507
|
if (!res.ok) {
|
|
444
508
|
const body = await res.text().catch(() => "(unreadable)");
|
|
445
509
|
const errDetail = `[analytics] event "${event.eventName}" rejected: ${res.status} ${res.statusText} \u2014 ${body}`;
|
|
@@ -451,11 +515,20 @@ var _SiteManager = class _SiteManager {
|
|
|
451
515
|
const sentViaBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, payload);
|
|
452
516
|
const errMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
453
517
|
if (!sentViaBeacon) {
|
|
454
|
-
console.error(
|
|
455
|
-
|
|
518
|
+
console.error(
|
|
519
|
+
`[Featurely] Failed to send analytics event "${event.eventName}":`,
|
|
520
|
+
fetchError
|
|
521
|
+
);
|
|
522
|
+
this.debugLog(
|
|
523
|
+
"error",
|
|
524
|
+
`[analytics] send failed for "${event.eventName}": ${errMsg}`
|
|
525
|
+
);
|
|
456
526
|
this.errorCount++;
|
|
457
527
|
} else {
|
|
458
|
-
this.debugLog(
|
|
528
|
+
this.debugLog(
|
|
529
|
+
"warn",
|
|
530
|
+
`[analytics] fetch failed for "${event.eventName}" (${errMsg}), delivered via sendBeacon`
|
|
531
|
+
);
|
|
459
532
|
}
|
|
460
533
|
}
|
|
461
534
|
}
|
|
@@ -502,14 +575,18 @@ var _SiteManager = class _SiteManager {
|
|
|
502
575
|
this.originalConsoleError = console.error.bind(console);
|
|
503
576
|
console.error = (...args) => {
|
|
504
577
|
this.originalConsoleError(...args);
|
|
505
|
-
const msg = args.map(
|
|
578
|
+
const msg = args.map(
|
|
579
|
+
(a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)
|
|
580
|
+
).join(" ");
|
|
506
581
|
this.errorCount++;
|
|
507
582
|
this.debugLog("error", `[console.error] ${msg.slice(0, 300)}`);
|
|
508
583
|
};
|
|
509
584
|
this.originalConsoleWarn = console.warn.bind(console);
|
|
510
585
|
console.warn = (...args) => {
|
|
511
586
|
this.originalConsoleWarn(...args);
|
|
512
|
-
const msg = args.map(
|
|
587
|
+
const msg = args.map(
|
|
588
|
+
(a) => typeof a === "string" ? a : a instanceof Error ? `${a.message}` : JSON.stringify(a)
|
|
589
|
+
).join(" ");
|
|
513
590
|
this.debugLog("warn", `[console.warn] ${msg.slice(0, 300)}`);
|
|
514
591
|
};
|
|
515
592
|
window.addEventListener("error", (e) => {
|
|
@@ -562,7 +639,7 @@ var _SiteManager = class _SiteManager {
|
|
|
562
639
|
this.debugOverlayEl = el;
|
|
563
640
|
this.setupGlobalErrorCapture();
|
|
564
641
|
this.debugLog("info", `[site-manager] debug overlay initialized`);
|
|
565
|
-
this.debugLog("info", `[site-manager] v1.1.
|
|
642
|
+
this.debugLog("info", `[site-manager] v1.1.19 | project: ${this.config.projectId}`);
|
|
566
643
|
this.renderDebugOverlay();
|
|
567
644
|
this.debugRefreshId = setInterval(() => this.renderDebugOverlay(), 1500);
|
|
568
645
|
}
|
|
@@ -744,7 +821,10 @@ var _SiteManager = class _SiteManager {
|
|
|
744
821
|
};
|
|
745
822
|
switch (action) {
|
|
746
823
|
case "test-event":
|
|
747
|
-
this.trackEvent("debug_test_event", {
|
|
824
|
+
this.trackEvent("debug_test_event", {
|
|
825
|
+
source: "debug_panel",
|
|
826
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
827
|
+
});
|
|
748
828
|
feedback("debug_test_event queued", true);
|
|
749
829
|
break;
|
|
750
830
|
case "flush": {
|
|
@@ -786,7 +866,10 @@ var _SiteManager = class _SiteManager {
|
|
|
786
866
|
case "copy-logs": {
|
|
787
867
|
const text = this.debugLogs.slice().reverse().map((l) => `[${l.ts}] [${l.level.toUpperCase()}] ${l.msg}`).join("\n");
|
|
788
868
|
void navigator.clipboard.writeText(text).then(() => {
|
|
789
|
-
feedback(
|
|
869
|
+
feedback(
|
|
870
|
+
`Copied ${this.debugLogs.length} log line${this.debugLogs.length !== 1 ? "s" : ""}`,
|
|
871
|
+
true
|
|
872
|
+
);
|
|
790
873
|
}).catch(() => {
|
|
791
874
|
feedback("Copy failed \u2014 clipboard not available", false);
|
|
792
875
|
});
|
|
@@ -904,9 +987,7 @@ var _SiteManager = class _SiteManager {
|
|
|
904
987
|
async checkVersion(currentVersion) {
|
|
905
988
|
const versionToCheck = currentVersion || this.config.appVersion;
|
|
906
989
|
if (!versionToCheck) {
|
|
907
|
-
console.warn(
|
|
908
|
-
"Featurely Site Manager: appVersion not provided for version check"
|
|
909
|
-
);
|
|
990
|
+
console.warn("Featurely Site Manager: appVersion not provided for version check");
|
|
910
991
|
return null;
|
|
911
992
|
}
|
|
912
993
|
try {
|
|
@@ -1050,7 +1131,10 @@ var _SiteManager = class _SiteManager {
|
|
|
1050
1131
|
this.debugLog("info", "[maintenance] ENABLED \u2014 user bypassed (whitelist match)");
|
|
1051
1132
|
return;
|
|
1052
1133
|
}
|
|
1053
|
-
this.debugLog(
|
|
1134
|
+
this.debugLog(
|
|
1135
|
+
"warn",
|
|
1136
|
+
`[maintenance] ENABLED (type: ${this.siteConfig.maintenance.type})`
|
|
1137
|
+
);
|
|
1054
1138
|
this.showMaintenancePage();
|
|
1055
1139
|
this.trackEvent("maintenance_enabled", {
|
|
1056
1140
|
maintenanceType: this.siteConfig.maintenance.type
|
|
@@ -1122,7 +1206,17 @@ var _SiteManager = class _SiteManager {
|
|
|
1122
1206
|
"button"
|
|
1123
1207
|
],
|
|
1124
1208
|
ALLOWED_ATTR: ["class", "id", "href", "src", "alt", "title", "style"],
|
|
1125
|
-
FORBID_TAGS: [
|
|
1209
|
+
FORBID_TAGS: [
|
|
1210
|
+
"script",
|
|
1211
|
+
"iframe",
|
|
1212
|
+
"embed",
|
|
1213
|
+
"object",
|
|
1214
|
+
"applet",
|
|
1215
|
+
"meta",
|
|
1216
|
+
"link",
|
|
1217
|
+
"form",
|
|
1218
|
+
"input"
|
|
1219
|
+
],
|
|
1126
1220
|
FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover"],
|
|
1127
1221
|
ALLOW_DATA_ATTR: false
|
|
1128
1222
|
});
|
|
@@ -1190,10 +1284,16 @@ var _SiteManager = class _SiteManager {
|
|
|
1190
1284
|
this.messageContainers.delete(id);
|
|
1191
1285
|
}
|
|
1192
1286
|
});
|
|
1193
|
-
this.debugLog(
|
|
1287
|
+
this.debugLog(
|
|
1288
|
+
"info",
|
|
1289
|
+
`[messages] ${activeMessages.length} active (${(_b = (_a = this.siteConfig) == null ? void 0 : _a.messages.length) != null ? _b : 0} total)`
|
|
1290
|
+
);
|
|
1194
1291
|
activeMessages.forEach((message) => {
|
|
1195
1292
|
if (!this.messageContainers.has(message.id)) {
|
|
1196
|
-
this.debugLog(
|
|
1293
|
+
this.debugLog(
|
|
1294
|
+
"info",
|
|
1295
|
+
`[messages] showing "${message.title}" (${message.type}, ${message.style})`
|
|
1296
|
+
);
|
|
1197
1297
|
this.showMessage(message);
|
|
1198
1298
|
if (this.config.onMessageReceived) {
|
|
1199
1299
|
this.config.onMessageReceived(message);
|
|
@@ -1459,7 +1559,8 @@ var _SiteManager = class _SiteManager {
|
|
|
1459
1559
|
|
|
1460
1560
|
.featurely-message-body {
|
|
1461
1561
|
font-size: 14px;
|
|
1462
|
-
opacity
|
|
1562
|
+
/* opacity < 1 reduces effective contrast; inherit full color from parent */
|
|
1563
|
+
opacity: 1;
|
|
1463
1564
|
}
|
|
1464
1565
|
|
|
1465
1566
|
.featurely-message-cta {
|
|
@@ -1510,16 +1611,17 @@ var _SiteManager = class _SiteManager {
|
|
|
1510
1611
|
|
|
1511
1612
|
.featurely-message-warning {
|
|
1512
1613
|
background: #fff3e0;
|
|
1513
|
-
|
|
1614
|
+
/* #803600 on #fff3e0 \u2248 7.8:1 contrast \u2014 WCAG AA compliant */
|
|
1615
|
+
color: #803600;
|
|
1514
1616
|
}
|
|
1515
1617
|
|
|
1516
1618
|
.featurely-message-warning .featurely-message-cta {
|
|
1517
|
-
background: #
|
|
1619
|
+
background: #803600;
|
|
1518
1620
|
color: white;
|
|
1519
1621
|
}
|
|
1520
1622
|
|
|
1521
1623
|
.featurely-message-warning .featurely-message-cta:hover {
|
|
1522
|
-
background: #
|
|
1624
|
+
background: #6b2d00;
|
|
1523
1625
|
}
|
|
1524
1626
|
|
|
1525
1627
|
.featurely-message-error {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "featurely-site-manager",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.20",
|
|
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",
|
|
@@ -50,10 +50,13 @@
|
|
|
50
50
|
"homepage": "https://featurely.no",
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/dompurify": "^3.0.5",
|
|
53
|
+
"drizzle-kit": "^0.31.10",
|
|
53
54
|
"tsup": "^8.5.1",
|
|
54
55
|
"typescript": "^5.0.0"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
|
-
"
|
|
58
|
+
"@neondatabase/serverless": "^1.0.2",
|
|
59
|
+
"dompurify": "^3.3.3",
|
|
60
|
+
"drizzle-orm": "^0.45.2"
|
|
58
61
|
}
|
|
59
62
|
}
|