featurely-site-manager 1.3.0 → 1.3.2
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 +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +116 -32
- package/dist/index.mjs +116 -32
- package/package.json +2 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const SDK_VERSION = "1.3.
|
|
1
|
+
declare const SDK_VERSION = "1.3.2";
|
|
2
2
|
type MessageType = "info" | "warning" | "error" | "success";
|
|
3
3
|
type MessagePosition = "top" | "bottom";
|
|
4
4
|
type MessageStyle = "banner" | "toast";
|
|
@@ -31,6 +31,7 @@ interface FeatureFlag {
|
|
|
31
31
|
targetEmails?: string[];
|
|
32
32
|
excludeEmails?: string[];
|
|
33
33
|
betaUsersOnly?: boolean;
|
|
34
|
+
availableOnLocalhost?: boolean;
|
|
34
35
|
variants?: {
|
|
35
36
|
key: string;
|
|
36
37
|
name: string;
|
|
@@ -247,6 +248,7 @@ declare class SiteManager {
|
|
|
247
248
|
private matchHostnamePattern;
|
|
248
249
|
private evaluateFeatureFlag;
|
|
249
250
|
private getUserBucket;
|
|
251
|
+
private isLocalhost;
|
|
250
252
|
private simpleHash;
|
|
251
253
|
private getAnonymousId;
|
|
252
254
|
private getOrCreateVisitorId;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const SDK_VERSION = "1.3.
|
|
1
|
+
declare const SDK_VERSION = "1.3.2";
|
|
2
2
|
type MessageType = "info" | "warning" | "error" | "success";
|
|
3
3
|
type MessagePosition = "top" | "bottom";
|
|
4
4
|
type MessageStyle = "banner" | "toast";
|
|
@@ -31,6 +31,7 @@ interface FeatureFlag {
|
|
|
31
31
|
targetEmails?: string[];
|
|
32
32
|
excludeEmails?: string[];
|
|
33
33
|
betaUsersOnly?: boolean;
|
|
34
|
+
availableOnLocalhost?: boolean;
|
|
34
35
|
variants?: {
|
|
35
36
|
key: string;
|
|
36
37
|
name: string;
|
|
@@ -247,6 +248,7 @@ declare class SiteManager {
|
|
|
247
248
|
private matchHostnamePattern;
|
|
248
249
|
private evaluateFeatureFlag;
|
|
249
250
|
private getUserBucket;
|
|
251
|
+
private isLocalhost;
|
|
250
252
|
private simpleHash;
|
|
251
253
|
private getAnonymousId;
|
|
252
254
|
private getOrCreateVisitorId;
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(index_exports);
|
|
38
38
|
var import_dompurify = __toESM(require("dompurify"));
|
|
39
|
-
var SDK_VERSION = "1.3.
|
|
39
|
+
var SDK_VERSION = "1.3.2";
|
|
40
40
|
var _SiteManager = class _SiteManager {
|
|
41
41
|
constructor(config) {
|
|
42
42
|
this.siteConfig = null;
|
|
@@ -153,9 +153,6 @@ var _SiteManager = class _SiteManager {
|
|
|
153
153
|
*/
|
|
154
154
|
async init() {
|
|
155
155
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
156
|
-
console.warn(
|
|
157
|
-
"Featurely Site Manager: Can only be initialized in a browser environment"
|
|
158
|
-
);
|
|
159
156
|
return;
|
|
160
157
|
}
|
|
161
158
|
if (this.config.debugMode) {
|
|
@@ -259,6 +256,7 @@ var _SiteManager = class _SiteManager {
|
|
|
259
256
|
return defaultValue;
|
|
260
257
|
}
|
|
261
258
|
const flag = this.siteConfig.featureFlags.find((f) => f.key === flagKey);
|
|
259
|
+
this.debugLog("info", `[flag-eval] Checking "${flagKey}" - found: ${!!flag}, enabled: ${(flag == null ? void 0 : flag.enabled) || false}`);
|
|
262
260
|
if (!flag || !flag.enabled) {
|
|
263
261
|
return defaultValue;
|
|
264
262
|
}
|
|
@@ -538,15 +536,15 @@ var _SiteManager = class _SiteManager {
|
|
|
538
536
|
const errorData = await response.json().catch(() => ({}));
|
|
539
537
|
const message = errorData.error || response.statusText;
|
|
540
538
|
if (response.status === 401) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
539
|
+
this.debugLog(
|
|
540
|
+
"error",
|
|
541
|
+
`[config] Invalid or missing API key. ${message}. Check your API key at https://www.featurely.no/dashboard/settings`
|
|
544
542
|
);
|
|
545
543
|
this.stopPolling();
|
|
546
544
|
} else if (response.status === 403) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
545
|
+
this.debugLog(
|
|
546
|
+
"error",
|
|
547
|
+
`[config] Permission denied. ${message}. Ensure your API key has the required permissions at https://www.featurely.no/dashboard/settings`
|
|
550
548
|
);
|
|
551
549
|
this.stopPolling();
|
|
552
550
|
}
|
|
@@ -698,23 +696,17 @@ var _SiteManager = class _SiteManager {
|
|
|
698
696
|
if (this.consecutiveFetchFailures <= _SiteManager.MAX_CONSECUTIVE_FAILURES) {
|
|
699
697
|
const isNetworkError = error instanceof TypeError && (error.message.includes("NetworkError") || error.message.includes("Failed to fetch") || error.message.includes("fetch"));
|
|
700
698
|
if (isNetworkError) {
|
|
701
|
-
|
|
702
|
-
\u2192 If your site has a Content-Security-Policy, add 'https://www.featurely.no' to the connect-src directive.
|
|
703
|
-
Example: connect-src 'self' https://www.featurely.no ...`;
|
|
704
|
-
console.error(cspMsg);
|
|
705
|
-
this.debugLog("error", "[config] possible CSP block \u2014 check connect-src");
|
|
699
|
+
this.debugLog("error", "[config] Network error - request blocked. Check Content-Security-Policy connect-src directive includes https://www.featurely.no");
|
|
706
700
|
} else {
|
|
707
|
-
|
|
701
|
+
this.debugLog("error", `[config] Failed to fetch configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
708
702
|
}
|
|
709
703
|
if (this.config.onError && error instanceof Error) {
|
|
710
704
|
this.config.onError(error);
|
|
711
705
|
}
|
|
712
706
|
} else if (this.consecutiveFetchFailures === _SiteManager.MAX_CONSECUTIVE_FAILURES + 1) {
|
|
713
|
-
const silenceMsg = `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`;
|
|
714
|
-
console.error(silenceMsg);
|
|
715
707
|
this.debugLog(
|
|
716
708
|
"error",
|
|
717
|
-
`[config]
|
|
709
|
+
`[config] Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} consecutive failures. Check your apiUrl and Content-Security-Policy configuration.`
|
|
718
710
|
);
|
|
719
711
|
}
|
|
720
712
|
}
|
|
@@ -797,7 +789,6 @@ var _SiteManager = class _SiteManager {
|
|
|
797
789
|
if (!res.ok) {
|
|
798
790
|
const body = await res.text().catch(() => "(unreadable)");
|
|
799
791
|
const errDetail = `[analytics] batch rejected: ${res.status} ${res.statusText} \u2014 ${body}`;
|
|
800
|
-
console.error(`[Featurely] ${errDetail}`);
|
|
801
792
|
this.debugLog("error", errDetail);
|
|
802
793
|
this.errorCount++;
|
|
803
794
|
}
|
|
@@ -805,7 +796,6 @@ var _SiteManager = class _SiteManager {
|
|
|
805
796
|
const sentViaBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, payload);
|
|
806
797
|
const errMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
807
798
|
if (!sentViaBeacon) {
|
|
808
|
-
console.error(`[Featurely] Failed to send analytics batch:`, fetchError);
|
|
809
799
|
this.debugLog("error", `[analytics] batch send failed: ${errMsg}`);
|
|
810
800
|
this.errorCount++;
|
|
811
801
|
} else {
|
|
@@ -922,12 +912,12 @@ var _SiteManager = class _SiteManager {
|
|
|
922
912
|
this.debugOverlayEl = el;
|
|
923
913
|
this.setupGlobalErrorCapture();
|
|
924
914
|
this.debugLog("info", `[site-manager] debug overlay initialized`);
|
|
925
|
-
this.debugLog("info", `[site-manager]
|
|
915
|
+
this.debugLog("info", `[site-manager] v${SDK_VERSION} | project: ${this.config.projectId}`);
|
|
926
916
|
this.renderDebugOverlay();
|
|
927
917
|
this.debugRefreshId = setInterval(() => this.renderDebugOverlay(), 1500);
|
|
928
918
|
}
|
|
929
919
|
renderDebugOverlay() {
|
|
930
|
-
var _a, _b, _c, _d, _e;
|
|
920
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
931
921
|
const el = this.debugOverlayEl;
|
|
932
922
|
if (!el) return;
|
|
933
923
|
const ACCENT = "#6366f1";
|
|
@@ -954,7 +944,7 @@ var _SiteManager = class _SiteManager {
|
|
|
954
944
|
const flagCount = (_c = (_b = sc == null ? void 0 : sc.featureFlags) == null ? void 0 : _b.length) != null ? _c : 0;
|
|
955
945
|
const msgCount = (_e = (_d = sc == null ? void 0 : sc.messages) == null ? void 0 : _d.length) != null ? _e : 0;
|
|
956
946
|
content = `
|
|
957
|
-
${row("SDK Version",
|
|
947
|
+
${row("SDK Version", SDK_VERSION)}
|
|
958
948
|
${row("Project", cfg.projectId)}
|
|
959
949
|
${row("Hostname", typeof window !== "undefined" ? window.location.hostname : "\u2014")}
|
|
960
950
|
${row("API URL", cfg.apiUrl)}
|
|
@@ -1012,6 +1002,64 @@ var _SiteManager = class _SiteManager {
|
|
|
1012
1002
|
(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>`
|
|
1013
1003
|
).join("");
|
|
1014
1004
|
}
|
|
1005
|
+
} else if (tab === "flags") {
|
|
1006
|
+
const flags = (sc == null ? void 0 : sc.featureFlags) || [];
|
|
1007
|
+
if (flags.length === 0) {
|
|
1008
|
+
content = `<div style="color:${MUTED};padding:16px;text-align:center">No feature flags configured</div>`;
|
|
1009
|
+
} else {
|
|
1010
|
+
const flagCards = flags.map((flag) => {
|
|
1011
|
+
var _a2, _b2;
|
|
1012
|
+
const isEnabled = this.isFeatureEnabled(flag.key);
|
|
1013
|
+
const statusColor = isEnabled ? GREEN : MUTED;
|
|
1014
|
+
const statusIcon = isEnabled ? "\u2713" : "\u2717";
|
|
1015
|
+
const details = [];
|
|
1016
|
+
if (flag.rolloutPercentage !== void 0 && flag.rolloutPercentage < 100) {
|
|
1017
|
+
const bucket = this.getUserBucket(flag.key);
|
|
1018
|
+
details.push(`Rollout: ${flag.rolloutPercentage}% (bucket: ${bucket.toFixed(0)})`);
|
|
1019
|
+
}
|
|
1020
|
+
if (flag.betaUsersOnly) {
|
|
1021
|
+
const isBeta = (_b2 = (_a2 = this.siteConfig) == null ? void 0 : _a2.betaUserEmails) == null ? void 0 : _b2.includes(this.config.userEmail || "");
|
|
1022
|
+
details.push(`Beta only: ${isBeta ? "\u2713 enrolled" : "\u2717 not enrolled"}`);
|
|
1023
|
+
}
|
|
1024
|
+
if (flag.availableOnLocalhost) {
|
|
1025
|
+
const isLocal = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
|
|
1026
|
+
details.push(`Localhost: ${isLocal ? "\u2713" : "\u2717"}`);
|
|
1027
|
+
}
|
|
1028
|
+
if (flag.targetEmails && flag.targetEmails.length > 0) {
|
|
1029
|
+
const isTargeted = flag.targetEmails.includes(this.config.userEmail || "");
|
|
1030
|
+
details.push(`Email targeted: ${isTargeted ? "\u2713" : "\u2717"}`);
|
|
1031
|
+
}
|
|
1032
|
+
if (flag.excludeEmails && flag.excludeEmails.length > 0) {
|
|
1033
|
+
const isExcluded = flag.excludeEmails.includes(this.config.userEmail || "");
|
|
1034
|
+
details.push(`Excluded: ${isExcluded ? "\u2713" : "\u2717"}`);
|
|
1035
|
+
}
|
|
1036
|
+
if (flag.targetAttributes && flag.targetAttributes.length > 0) {
|
|
1037
|
+
details.push(`Custom attributes: ${flag.targetAttributes.length} rule(s)`);
|
|
1038
|
+
}
|
|
1039
|
+
if (flag.variants && flag.variants.length > 0) {
|
|
1040
|
+
const variant = this.getFeatureVariant(flag.key);
|
|
1041
|
+
details.push(`Variant: ${variant || flag.defaultVariant || "default"}`);
|
|
1042
|
+
}
|
|
1043
|
+
const detailsHtml = details.length > 0 ? `<div style="font-size:9px;color:${MUTED};margin-top:3px;line-height:1.4">${details.join(" \u2022 ")}</div>` : "";
|
|
1044
|
+
return `
|
|
1045
|
+
<div style="padding:6px 8px;margin:4px 0;background:rgba(255,255,255,0.03);border-radius:6px;border-left:3px solid ${statusColor}">
|
|
1046
|
+
<div style="display:flex;justify-content:space-between;align-items:center">
|
|
1047
|
+
<div style="flex:1">
|
|
1048
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
1049
|
+
<span style="color:${statusColor};font-weight:600;font-size:10px">${statusIcon}</span>
|
|
1050
|
+
<span style="color:${TEXT};font-weight:600;font-family:monospace;font-size:10px">${flag.key}</span>
|
|
1051
|
+
${!flag.enabled ? badge("DISABLED", MUTED) : ""}
|
|
1052
|
+
</div>
|
|
1053
|
+
${flag.name ? `<div style="color:${MUTED};font-size:9px;margin-top:2px">${flag.name}</div>` : ""}
|
|
1054
|
+
${detailsHtml}
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
`;
|
|
1059
|
+
}).join("");
|
|
1060
|
+
const summary = `<div style="display:flex;justify-content:space-between;margin-bottom:8px;padding:4px 0;border-bottom:1px solid ${BORDER}"><span style="color:${MUTED};font-size:9px">TOTAL: ${flags.length}</span><span style="color:${MUTED};font-size:9px">USER: ${this.config.userEmail || this.config.userId || "anonymous"}</span></div>`;
|
|
1061
|
+
content = summary + `<div style="max-height:250px;overflow-y:auto">${flagCards}</div>`;
|
|
1062
|
+
}
|
|
1015
1063
|
} else if (tab === "test") {
|
|
1016
1064
|
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`;
|
|
1017
1065
|
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>`;
|
|
@@ -1049,16 +1097,24 @@ var _SiteManager = class _SiteManager {
|
|
|
1049
1097
|
`;
|
|
1050
1098
|
}
|
|
1051
1099
|
}
|
|
1100
|
+
const contentContainer = el.querySelector("#__ft_dbg_content__");
|
|
1101
|
+
const scrollTop = (_f = contentContainer == null ? void 0 : contentContainer.scrollTop) != null ? _f : 0;
|
|
1052
1102
|
el.innerHTML = `
|
|
1053
1103
|
<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__">
|
|
1054
1104
|
<span style="color:${ACCENT};font-weight:700;font-size:10px;letter-spacing:0.05em">⬡ FEATURELY DEBUG</span>
|
|
1055
1105
|
<span style="flex:1"></span>
|
|
1056
|
-
${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>`}
|
|
1106
|
+
${minimized ? "" : `<div style="display:flex;gap:4px;flex-wrap:wrap">${tabBtn("sdk", "SDK")}${tabBtn("flags", "Flags", ((_g = sc == null ? void 0 : sc.featureFlags) == null ? void 0 : _g.length) || 0)}${tabBtn("logs", "Logs", this.errorCount)}${tabBtn("network", "Network")}${tabBtn("events", "Events")}${tabBtn("test", "Test")}</div>`}
|
|
1057
1107
|
<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 ? "⬆" : "⬇"}</button>
|
|
1058
1108
|
<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">×</button>
|
|
1059
1109
|
</div>
|
|
1060
|
-
${minimized ? "" : `<div style="padding:8px 10px;max-height:300px;overflow-y:auto">${content}</div>`}
|
|
1110
|
+
${minimized ? "" : `<div id="__ft_dbg_content__" style="padding:8px 10px;max-height:300px;overflow-y:auto">${content}</div>`}
|
|
1061
1111
|
`;
|
|
1112
|
+
if (!minimized) {
|
|
1113
|
+
const newContentContainer = el.querySelector("#__ft_dbg_content__");
|
|
1114
|
+
if (newContentContainer) {
|
|
1115
|
+
newContentContainer.scrollTop = scrollTop;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1062
1118
|
el.querySelectorAll("[data-tab]").forEach((btn) => {
|
|
1063
1119
|
btn.onclick = (e) => {
|
|
1064
1120
|
e.stopPropagation();
|
|
@@ -1694,7 +1750,7 @@ var _SiteManager = class _SiteManager {
|
|
|
1694
1750
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1695
1751
|
const versionToCheck = currentVersion || this.config.appVersion;
|
|
1696
1752
|
if (!versionToCheck) {
|
|
1697
|
-
|
|
1753
|
+
this.debugLog("warn", "[version-check] appVersion not provided");
|
|
1698
1754
|
return null;
|
|
1699
1755
|
}
|
|
1700
1756
|
try {
|
|
@@ -1773,7 +1829,7 @@ var _SiteManager = class _SiteManager {
|
|
|
1773
1829
|
}
|
|
1774
1830
|
return versionInfo;
|
|
1775
1831
|
} catch (error) {
|
|
1776
|
-
|
|
1832
|
+
this.debugLog("error", `[version-check] ${error instanceof Error ? error.message : String(error)}`);
|
|
1777
1833
|
if (this.config.onError) {
|
|
1778
1834
|
this.config.onError(
|
|
1779
1835
|
error instanceof Error ? error : new Error("Failed to check version")
|
|
@@ -1899,21 +1955,30 @@ var _SiteManager = class _SiteManager {
|
|
|
1899
1955
|
*/
|
|
1900
1956
|
evaluateFeatureFlag(flag) {
|
|
1901
1957
|
var _a, _b;
|
|
1958
|
+
if (flag.availableOnLocalhost && this.isLocalhost()) {
|
|
1959
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ENABLED (localhost bypass)`);
|
|
1960
|
+
return true;
|
|
1961
|
+
}
|
|
1902
1962
|
if (!flag.enabled) {
|
|
1963
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (flag.enabled=false)`);
|
|
1903
1964
|
return false;
|
|
1904
1965
|
}
|
|
1905
1966
|
const userEmail = this.config.userEmail;
|
|
1906
1967
|
if (flag.excludeEmails && userEmail && flag.excludeEmails.includes(userEmail)) {
|
|
1968
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (user ${userEmail} excluded)`);
|
|
1907
1969
|
return false;
|
|
1908
1970
|
}
|
|
1909
1971
|
if (flag.betaUsersOnly) {
|
|
1910
1972
|
if (!userEmail) {
|
|
1973
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (beta-only, no email provided)`);
|
|
1911
1974
|
return false;
|
|
1912
1975
|
}
|
|
1913
1976
|
const betaUsers = ((_a = this.siteConfig) == null ? void 0 : _a.betaUserEmails) || [];
|
|
1914
1977
|
if (!betaUsers.includes(userEmail)) {
|
|
1978
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (beta-only, ${userEmail} not in beta list)`);
|
|
1915
1979
|
return false;
|
|
1916
1980
|
}
|
|
1981
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": beta check passed for ${userEmail}`);
|
|
1917
1982
|
}
|
|
1918
1983
|
if (flag.targetAttributes && flag.targetAttributes.length > 0) {
|
|
1919
1984
|
const attrs = (_b = this.config.customAttributes) != null ? _b : {};
|
|
@@ -1935,18 +2000,27 @@ var _SiteManager = class _SiteManager {
|
|
|
1935
2000
|
return false;
|
|
1936
2001
|
}
|
|
1937
2002
|
});
|
|
1938
|
-
if (!allMatch)
|
|
2003
|
+
if (!allMatch) {
|
|
2004
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (custom attribute rules not matched)`);
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
2007
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": custom attribute rules matched`);
|
|
1939
2008
|
}
|
|
1940
2009
|
if (flag.targetEmails && flag.targetEmails.length > 0) {
|
|
1941
2010
|
if (!userEmail || !flag.targetEmails.includes(userEmail)) {
|
|
2011
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (not in targetEmails)`);
|
|
1942
2012
|
return false;
|
|
1943
2013
|
}
|
|
2014
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ENABLED (user in targetEmails)`);
|
|
1944
2015
|
return true;
|
|
1945
2016
|
}
|
|
1946
2017
|
if (flag.rolloutPercentage !== void 0 && flag.rolloutPercentage < 100) {
|
|
1947
2018
|
const bucket = this.getUserBucket(flag.key);
|
|
1948
|
-
|
|
2019
|
+
const enabled = bucket < flag.rolloutPercentage;
|
|
2020
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ${enabled ? "ENABLED" : "DISABLED"} (rollout ${flag.rolloutPercentage}%, bucket ${bucket})`);
|
|
2021
|
+
return enabled;
|
|
1949
2022
|
}
|
|
2023
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ENABLED (no restrictions)`);
|
|
1950
2024
|
return true;
|
|
1951
2025
|
}
|
|
1952
2026
|
/**
|
|
@@ -1963,6 +2037,16 @@ var _SiteManager = class _SiteManager {
|
|
|
1963
2037
|
this.featureFlagBuckets.set(flagKey, bucket);
|
|
1964
2038
|
return bucket;
|
|
1965
2039
|
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Check if the current environment is localhost
|
|
2042
|
+
*/
|
|
2043
|
+
isLocalhost() {
|
|
2044
|
+
if (typeof window === "undefined") {
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
const hostname = window.location.hostname;
|
|
2048
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
2049
|
+
}
|
|
1966
2050
|
/**
|
|
1967
2051
|
* Simple hash function for consistent bucketing
|
|
1968
2052
|
*/
|
|
@@ -2173,7 +2257,7 @@ var _SiteManager = class _SiteManager {
|
|
|
2173
2257
|
const regex = new RegExp(escapedPattern);
|
|
2174
2258
|
return regex.test(currentPath);
|
|
2175
2259
|
} catch (e) {
|
|
2176
|
-
|
|
2260
|
+
this.debugLog("warn", `[message] Invalid pattern: ${pattern}`);
|
|
2177
2261
|
return false;
|
|
2178
2262
|
}
|
|
2179
2263
|
});
|
|
@@ -2277,7 +2361,7 @@ var _SiteManager = class _SiteManager {
|
|
|
2277
2361
|
}
|
|
2278
2362
|
handleMessageAction(action, message) {
|
|
2279
2363
|
var _a;
|
|
2280
|
-
|
|
2364
|
+
this.debugLog("info", `[message-action] ${action} on message ${message.id}`);
|
|
2281
2365
|
if ((_a = message.cta) == null ? void 0 : _a.url) {
|
|
2282
2366
|
window.location.href = message.cta.url;
|
|
2283
2367
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import DOMPurify from "dompurify";
|
|
3
|
-
var SDK_VERSION = "1.3.
|
|
3
|
+
var SDK_VERSION = "1.3.2";
|
|
4
4
|
var _SiteManager = class _SiteManager {
|
|
5
5
|
constructor(config) {
|
|
6
6
|
this.siteConfig = null;
|
|
@@ -117,9 +117,6 @@ var _SiteManager = class _SiteManager {
|
|
|
117
117
|
*/
|
|
118
118
|
async init() {
|
|
119
119
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
120
|
-
console.warn(
|
|
121
|
-
"Featurely Site Manager: Can only be initialized in a browser environment"
|
|
122
|
-
);
|
|
123
120
|
return;
|
|
124
121
|
}
|
|
125
122
|
if (this.config.debugMode) {
|
|
@@ -223,6 +220,7 @@ var _SiteManager = class _SiteManager {
|
|
|
223
220
|
return defaultValue;
|
|
224
221
|
}
|
|
225
222
|
const flag = this.siteConfig.featureFlags.find((f) => f.key === flagKey);
|
|
223
|
+
this.debugLog("info", `[flag-eval] Checking "${flagKey}" - found: ${!!flag}, enabled: ${(flag == null ? void 0 : flag.enabled) || false}`);
|
|
226
224
|
if (!flag || !flag.enabled) {
|
|
227
225
|
return defaultValue;
|
|
228
226
|
}
|
|
@@ -502,15 +500,15 @@ var _SiteManager = class _SiteManager {
|
|
|
502
500
|
const errorData = await response.json().catch(() => ({}));
|
|
503
501
|
const message = errorData.error || response.statusText;
|
|
504
502
|
if (response.status === 401) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
503
|
+
this.debugLog(
|
|
504
|
+
"error",
|
|
505
|
+
`[config] Invalid or missing API key. ${message}. Check your API key at https://www.featurely.no/dashboard/settings`
|
|
508
506
|
);
|
|
509
507
|
this.stopPolling();
|
|
510
508
|
} else if (response.status === 403) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
509
|
+
this.debugLog(
|
|
510
|
+
"error",
|
|
511
|
+
`[config] Permission denied. ${message}. Ensure your API key has the required permissions at https://www.featurely.no/dashboard/settings`
|
|
514
512
|
);
|
|
515
513
|
this.stopPolling();
|
|
516
514
|
}
|
|
@@ -662,23 +660,17 @@ var _SiteManager = class _SiteManager {
|
|
|
662
660
|
if (this.consecutiveFetchFailures <= _SiteManager.MAX_CONSECUTIVE_FAILURES) {
|
|
663
661
|
const isNetworkError = error instanceof TypeError && (error.message.includes("NetworkError") || error.message.includes("Failed to fetch") || error.message.includes("fetch"));
|
|
664
662
|
if (isNetworkError) {
|
|
665
|
-
|
|
666
|
-
\u2192 If your site has a Content-Security-Policy, add 'https://www.featurely.no' to the connect-src directive.
|
|
667
|
-
Example: connect-src 'self' https://www.featurely.no ...`;
|
|
668
|
-
console.error(cspMsg);
|
|
669
|
-
this.debugLog("error", "[config] possible CSP block \u2014 check connect-src");
|
|
663
|
+
this.debugLog("error", "[config] Network error - request blocked. Check Content-Security-Policy connect-src directive includes https://www.featurely.no");
|
|
670
664
|
} else {
|
|
671
|
-
|
|
665
|
+
this.debugLog("error", `[config] Failed to fetch configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
672
666
|
}
|
|
673
667
|
if (this.config.onError && error instanceof Error) {
|
|
674
668
|
this.config.onError(error);
|
|
675
669
|
}
|
|
676
670
|
} else if (this.consecutiveFetchFailures === _SiteManager.MAX_CONSECUTIVE_FAILURES + 1) {
|
|
677
|
-
const silenceMsg = `Featurely Site Manager: Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} failures. Check your apiUrl and Content-Security-Policy configuration.`;
|
|
678
|
-
console.error(silenceMsg);
|
|
679
671
|
this.debugLog(
|
|
680
672
|
"error",
|
|
681
|
-
`[config]
|
|
673
|
+
`[config] Silencing repeated fetch errors after ${_SiteManager.MAX_CONSECUTIVE_FAILURES} consecutive failures. Check your apiUrl and Content-Security-Policy configuration.`
|
|
682
674
|
);
|
|
683
675
|
}
|
|
684
676
|
}
|
|
@@ -761,7 +753,6 @@ var _SiteManager = class _SiteManager {
|
|
|
761
753
|
if (!res.ok) {
|
|
762
754
|
const body = await res.text().catch(() => "(unreadable)");
|
|
763
755
|
const errDetail = `[analytics] batch rejected: ${res.status} ${res.statusText} \u2014 ${body}`;
|
|
764
|
-
console.error(`[Featurely] ${errDetail}`);
|
|
765
756
|
this.debugLog("error", errDetail);
|
|
766
757
|
this.errorCount++;
|
|
767
758
|
}
|
|
@@ -769,7 +760,6 @@ var _SiteManager = class _SiteManager {
|
|
|
769
760
|
const sentViaBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && navigator.sendBeacon(url, payload);
|
|
770
761
|
const errMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
771
762
|
if (!sentViaBeacon) {
|
|
772
|
-
console.error(`[Featurely] Failed to send analytics batch:`, fetchError);
|
|
773
763
|
this.debugLog("error", `[analytics] batch send failed: ${errMsg}`);
|
|
774
764
|
this.errorCount++;
|
|
775
765
|
} else {
|
|
@@ -886,12 +876,12 @@ var _SiteManager = class _SiteManager {
|
|
|
886
876
|
this.debugOverlayEl = el;
|
|
887
877
|
this.setupGlobalErrorCapture();
|
|
888
878
|
this.debugLog("info", `[site-manager] debug overlay initialized`);
|
|
889
|
-
this.debugLog("info", `[site-manager]
|
|
879
|
+
this.debugLog("info", `[site-manager] v${SDK_VERSION} | project: ${this.config.projectId}`);
|
|
890
880
|
this.renderDebugOverlay();
|
|
891
881
|
this.debugRefreshId = setInterval(() => this.renderDebugOverlay(), 1500);
|
|
892
882
|
}
|
|
893
883
|
renderDebugOverlay() {
|
|
894
|
-
var _a, _b, _c, _d, _e;
|
|
884
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
895
885
|
const el = this.debugOverlayEl;
|
|
896
886
|
if (!el) return;
|
|
897
887
|
const ACCENT = "#6366f1";
|
|
@@ -918,7 +908,7 @@ var _SiteManager = class _SiteManager {
|
|
|
918
908
|
const flagCount = (_c = (_b = sc == null ? void 0 : sc.featureFlags) == null ? void 0 : _b.length) != null ? _c : 0;
|
|
919
909
|
const msgCount = (_e = (_d = sc == null ? void 0 : sc.messages) == null ? void 0 : _d.length) != null ? _e : 0;
|
|
920
910
|
content = `
|
|
921
|
-
${row("SDK Version",
|
|
911
|
+
${row("SDK Version", SDK_VERSION)}
|
|
922
912
|
${row("Project", cfg.projectId)}
|
|
923
913
|
${row("Hostname", typeof window !== "undefined" ? window.location.hostname : "\u2014")}
|
|
924
914
|
${row("API URL", cfg.apiUrl)}
|
|
@@ -976,6 +966,64 @@ var _SiteManager = class _SiteManager {
|
|
|
976
966
|
(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>`
|
|
977
967
|
).join("");
|
|
978
968
|
}
|
|
969
|
+
} else if (tab === "flags") {
|
|
970
|
+
const flags = (sc == null ? void 0 : sc.featureFlags) || [];
|
|
971
|
+
if (flags.length === 0) {
|
|
972
|
+
content = `<div style="color:${MUTED};padding:16px;text-align:center">No feature flags configured</div>`;
|
|
973
|
+
} else {
|
|
974
|
+
const flagCards = flags.map((flag) => {
|
|
975
|
+
var _a2, _b2;
|
|
976
|
+
const isEnabled = this.isFeatureEnabled(flag.key);
|
|
977
|
+
const statusColor = isEnabled ? GREEN : MUTED;
|
|
978
|
+
const statusIcon = isEnabled ? "\u2713" : "\u2717";
|
|
979
|
+
const details = [];
|
|
980
|
+
if (flag.rolloutPercentage !== void 0 && flag.rolloutPercentage < 100) {
|
|
981
|
+
const bucket = this.getUserBucket(flag.key);
|
|
982
|
+
details.push(`Rollout: ${flag.rolloutPercentage}% (bucket: ${bucket.toFixed(0)})`);
|
|
983
|
+
}
|
|
984
|
+
if (flag.betaUsersOnly) {
|
|
985
|
+
const isBeta = (_b2 = (_a2 = this.siteConfig) == null ? void 0 : _a2.betaUserEmails) == null ? void 0 : _b2.includes(this.config.userEmail || "");
|
|
986
|
+
details.push(`Beta only: ${isBeta ? "\u2713 enrolled" : "\u2717 not enrolled"}`);
|
|
987
|
+
}
|
|
988
|
+
if (flag.availableOnLocalhost) {
|
|
989
|
+
const isLocal = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
|
|
990
|
+
details.push(`Localhost: ${isLocal ? "\u2713" : "\u2717"}`);
|
|
991
|
+
}
|
|
992
|
+
if (flag.targetEmails && flag.targetEmails.length > 0) {
|
|
993
|
+
const isTargeted = flag.targetEmails.includes(this.config.userEmail || "");
|
|
994
|
+
details.push(`Email targeted: ${isTargeted ? "\u2713" : "\u2717"}`);
|
|
995
|
+
}
|
|
996
|
+
if (flag.excludeEmails && flag.excludeEmails.length > 0) {
|
|
997
|
+
const isExcluded = flag.excludeEmails.includes(this.config.userEmail || "");
|
|
998
|
+
details.push(`Excluded: ${isExcluded ? "\u2713" : "\u2717"}`);
|
|
999
|
+
}
|
|
1000
|
+
if (flag.targetAttributes && flag.targetAttributes.length > 0) {
|
|
1001
|
+
details.push(`Custom attributes: ${flag.targetAttributes.length} rule(s)`);
|
|
1002
|
+
}
|
|
1003
|
+
if (flag.variants && flag.variants.length > 0) {
|
|
1004
|
+
const variant = this.getFeatureVariant(flag.key);
|
|
1005
|
+
details.push(`Variant: ${variant || flag.defaultVariant || "default"}`);
|
|
1006
|
+
}
|
|
1007
|
+
const detailsHtml = details.length > 0 ? `<div style="font-size:9px;color:${MUTED};margin-top:3px;line-height:1.4">${details.join(" \u2022 ")}</div>` : "";
|
|
1008
|
+
return `
|
|
1009
|
+
<div style="padding:6px 8px;margin:4px 0;background:rgba(255,255,255,0.03);border-radius:6px;border-left:3px solid ${statusColor}">
|
|
1010
|
+
<div style="display:flex;justify-content:space-between;align-items:center">
|
|
1011
|
+
<div style="flex:1">
|
|
1012
|
+
<div style="display:flex;align-items:center;gap:6px">
|
|
1013
|
+
<span style="color:${statusColor};font-weight:600;font-size:10px">${statusIcon}</span>
|
|
1014
|
+
<span style="color:${TEXT};font-weight:600;font-family:monospace;font-size:10px">${flag.key}</span>
|
|
1015
|
+
${!flag.enabled ? badge("DISABLED", MUTED) : ""}
|
|
1016
|
+
</div>
|
|
1017
|
+
${flag.name ? `<div style="color:${MUTED};font-size:9px;margin-top:2px">${flag.name}</div>` : ""}
|
|
1018
|
+
${detailsHtml}
|
|
1019
|
+
</div>
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>
|
|
1022
|
+
`;
|
|
1023
|
+
}).join("");
|
|
1024
|
+
const summary = `<div style="display:flex;justify-content:space-between;margin-bottom:8px;padding:4px 0;border-bottom:1px solid ${BORDER}"><span style="color:${MUTED};font-size:9px">TOTAL: ${flags.length}</span><span style="color:${MUTED};font-size:9px">USER: ${this.config.userEmail || this.config.userId || "anonymous"}</span></div>`;
|
|
1025
|
+
content = summary + `<div style="max-height:250px;overflow-y:auto">${flagCards}</div>`;
|
|
1026
|
+
}
|
|
979
1027
|
} else if (tab === "test") {
|
|
980
1028
|
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`;
|
|
981
1029
|
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>`;
|
|
@@ -1013,16 +1061,24 @@ var _SiteManager = class _SiteManager {
|
|
|
1013
1061
|
`;
|
|
1014
1062
|
}
|
|
1015
1063
|
}
|
|
1064
|
+
const contentContainer = el.querySelector("#__ft_dbg_content__");
|
|
1065
|
+
const scrollTop = (_f = contentContainer == null ? void 0 : contentContainer.scrollTop) != null ? _f : 0;
|
|
1016
1066
|
el.innerHTML = `
|
|
1017
1067
|
<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__">
|
|
1018
1068
|
<span style="color:${ACCENT};font-weight:700;font-size:10px;letter-spacing:0.05em">⬡ FEATURELY DEBUG</span>
|
|
1019
1069
|
<span style="flex:1"></span>
|
|
1020
|
-
${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>`}
|
|
1070
|
+
${minimized ? "" : `<div style="display:flex;gap:4px;flex-wrap:wrap">${tabBtn("sdk", "SDK")}${tabBtn("flags", "Flags", ((_g = sc == null ? void 0 : sc.featureFlags) == null ? void 0 : _g.length) || 0)}${tabBtn("logs", "Logs", this.errorCount)}${tabBtn("network", "Network")}${tabBtn("events", "Events")}${tabBtn("test", "Test")}</div>`}
|
|
1021
1071
|
<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 ? "⬆" : "⬇"}</button>
|
|
1022
1072
|
<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">×</button>
|
|
1023
1073
|
</div>
|
|
1024
|
-
${minimized ? "" : `<div style="padding:8px 10px;max-height:300px;overflow-y:auto">${content}</div>`}
|
|
1074
|
+
${minimized ? "" : `<div id="__ft_dbg_content__" style="padding:8px 10px;max-height:300px;overflow-y:auto">${content}</div>`}
|
|
1025
1075
|
`;
|
|
1076
|
+
if (!minimized) {
|
|
1077
|
+
const newContentContainer = el.querySelector("#__ft_dbg_content__");
|
|
1078
|
+
if (newContentContainer) {
|
|
1079
|
+
newContentContainer.scrollTop = scrollTop;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1026
1082
|
el.querySelectorAll("[data-tab]").forEach((btn) => {
|
|
1027
1083
|
btn.onclick = (e) => {
|
|
1028
1084
|
e.stopPropagation();
|
|
@@ -1658,7 +1714,7 @@ var _SiteManager = class _SiteManager {
|
|
|
1658
1714
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1659
1715
|
const versionToCheck = currentVersion || this.config.appVersion;
|
|
1660
1716
|
if (!versionToCheck) {
|
|
1661
|
-
|
|
1717
|
+
this.debugLog("warn", "[version-check] appVersion not provided");
|
|
1662
1718
|
return null;
|
|
1663
1719
|
}
|
|
1664
1720
|
try {
|
|
@@ -1737,7 +1793,7 @@ var _SiteManager = class _SiteManager {
|
|
|
1737
1793
|
}
|
|
1738
1794
|
return versionInfo;
|
|
1739
1795
|
} catch (error) {
|
|
1740
|
-
|
|
1796
|
+
this.debugLog("error", `[version-check] ${error instanceof Error ? error.message : String(error)}`);
|
|
1741
1797
|
if (this.config.onError) {
|
|
1742
1798
|
this.config.onError(
|
|
1743
1799
|
error instanceof Error ? error : new Error("Failed to check version")
|
|
@@ -1863,21 +1919,30 @@ var _SiteManager = class _SiteManager {
|
|
|
1863
1919
|
*/
|
|
1864
1920
|
evaluateFeatureFlag(flag) {
|
|
1865
1921
|
var _a, _b;
|
|
1922
|
+
if (flag.availableOnLocalhost && this.isLocalhost()) {
|
|
1923
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ENABLED (localhost bypass)`);
|
|
1924
|
+
return true;
|
|
1925
|
+
}
|
|
1866
1926
|
if (!flag.enabled) {
|
|
1927
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (flag.enabled=false)`);
|
|
1867
1928
|
return false;
|
|
1868
1929
|
}
|
|
1869
1930
|
const userEmail = this.config.userEmail;
|
|
1870
1931
|
if (flag.excludeEmails && userEmail && flag.excludeEmails.includes(userEmail)) {
|
|
1932
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (user ${userEmail} excluded)`);
|
|
1871
1933
|
return false;
|
|
1872
1934
|
}
|
|
1873
1935
|
if (flag.betaUsersOnly) {
|
|
1874
1936
|
if (!userEmail) {
|
|
1937
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (beta-only, no email provided)`);
|
|
1875
1938
|
return false;
|
|
1876
1939
|
}
|
|
1877
1940
|
const betaUsers = ((_a = this.siteConfig) == null ? void 0 : _a.betaUserEmails) || [];
|
|
1878
1941
|
if (!betaUsers.includes(userEmail)) {
|
|
1942
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (beta-only, ${userEmail} not in beta list)`);
|
|
1879
1943
|
return false;
|
|
1880
1944
|
}
|
|
1945
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": beta check passed for ${userEmail}`);
|
|
1881
1946
|
}
|
|
1882
1947
|
if (flag.targetAttributes && flag.targetAttributes.length > 0) {
|
|
1883
1948
|
const attrs = (_b = this.config.customAttributes) != null ? _b : {};
|
|
@@ -1899,18 +1964,27 @@ var _SiteManager = class _SiteManager {
|
|
|
1899
1964
|
return false;
|
|
1900
1965
|
}
|
|
1901
1966
|
});
|
|
1902
|
-
if (!allMatch)
|
|
1967
|
+
if (!allMatch) {
|
|
1968
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (custom attribute rules not matched)`);
|
|
1969
|
+
return false;
|
|
1970
|
+
}
|
|
1971
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": custom attribute rules matched`);
|
|
1903
1972
|
}
|
|
1904
1973
|
if (flag.targetEmails && flag.targetEmails.length > 0) {
|
|
1905
1974
|
if (!userEmail || !flag.targetEmails.includes(userEmail)) {
|
|
1975
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": DISABLED (not in targetEmails)`);
|
|
1906
1976
|
return false;
|
|
1907
1977
|
}
|
|
1978
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ENABLED (user in targetEmails)`);
|
|
1908
1979
|
return true;
|
|
1909
1980
|
}
|
|
1910
1981
|
if (flag.rolloutPercentage !== void 0 && flag.rolloutPercentage < 100) {
|
|
1911
1982
|
const bucket = this.getUserBucket(flag.key);
|
|
1912
|
-
|
|
1983
|
+
const enabled = bucket < flag.rolloutPercentage;
|
|
1984
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ${enabled ? "ENABLED" : "DISABLED"} (rollout ${flag.rolloutPercentage}%, bucket ${bucket})`);
|
|
1985
|
+
return enabled;
|
|
1913
1986
|
}
|
|
1987
|
+
this.debugLog("info", `[flag-eval] "${flag.key}": ENABLED (no restrictions)`);
|
|
1914
1988
|
return true;
|
|
1915
1989
|
}
|
|
1916
1990
|
/**
|
|
@@ -1927,6 +2001,16 @@ var _SiteManager = class _SiteManager {
|
|
|
1927
2001
|
this.featureFlagBuckets.set(flagKey, bucket);
|
|
1928
2002
|
return bucket;
|
|
1929
2003
|
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Check if the current environment is localhost
|
|
2006
|
+
*/
|
|
2007
|
+
isLocalhost() {
|
|
2008
|
+
if (typeof window === "undefined") {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
const hostname = window.location.hostname;
|
|
2012
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
2013
|
+
}
|
|
1930
2014
|
/**
|
|
1931
2015
|
* Simple hash function for consistent bucketing
|
|
1932
2016
|
*/
|
|
@@ -2137,7 +2221,7 @@ var _SiteManager = class _SiteManager {
|
|
|
2137
2221
|
const regex = new RegExp(escapedPattern);
|
|
2138
2222
|
return regex.test(currentPath);
|
|
2139
2223
|
} catch (e) {
|
|
2140
|
-
|
|
2224
|
+
this.debugLog("warn", `[message] Invalid pattern: ${pattern}`);
|
|
2141
2225
|
return false;
|
|
2142
2226
|
}
|
|
2143
2227
|
});
|
|
@@ -2241,7 +2325,7 @@ var _SiteManager = class _SiteManager {
|
|
|
2241
2325
|
}
|
|
2242
2326
|
handleMessageAction(action, message) {
|
|
2243
2327
|
var _a;
|
|
2244
|
-
|
|
2328
|
+
this.debugLog("info", `[message-action] ${action} on message ${message.id}`);
|
|
2245
2329
|
if ((_a = message.cta) == null ? void 0 : _a.url) {
|
|
2246
2330
|
window.location.href = message.cta.url;
|
|
2247
2331
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "featurely-site-manager",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
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",
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"dompurify": "^3.3.3",
|
|
61
61
|
"featurely-error-tracker": "^1.0.23",
|
|
62
|
-
"featurely-mcp": "^1.0.6"
|
|
63
|
-
"featurely-site-manager": "^1.2.0"
|
|
62
|
+
"featurely-mcp": "^1.0.6"
|
|
64
63
|
}
|
|
65
64
|
}
|