featurely-site-manager 1.0.4 → 1.0.7
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 +28 -0
- package/dist/index.js +91 -7
- package/dist/index.mjs +81 -7
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.7] - 2026-03-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **SDK Health Monitoring**: Integrated Featurely error tracking to monitor SDK issues in production
|
|
13
|
+
- Internal error tracking helps us identify and fix SDK bugs faster
|
|
14
|
+
- Error tracking is minimal and privacy-focused (no user data collection)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Cleaned up package dependencies (removed accidental packages: dom, pur, ify)
|
|
19
|
+
|
|
20
|
+
## [1.0.6] - 2026-03-15
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
|
|
24
|
+
- **CRITICAL FIX**: Replaced Math.random() with crypto.getRandomValues() for CSPRNG session IDs and anonymous IDs
|
|
25
|
+
- **CRITICAL FIX**: Added RegExp pattern escaping to prevent ReDoS (Regular Expression Denial of Service) attacks
|
|
26
|
+
- Added error handling for invalid targetPages patterns
|
|
27
|
+
## [1.0.5] - 2026-03-15
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
|
|
31
|
+
- **CRITICAL FIXAdded DOMPurify sanitization for customHtml maintenance pages to prevent XSS attacks
|
|
32
|
+
- Blocks dangerous tags (script, iframe, embed, object, etc.)
|
|
33
|
+
- Removes event handlers (onerror, onload, onclick, etc.)
|
|
34
|
+
- Prevents javascript: URIs and other malicious protocols
|
|
35
|
+
|
|
8
36
|
## [1.0.4] - 2026-03-15
|
|
9
37
|
|
|
10
38
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -24,6 +34,25 @@ __export(index_exports, {
|
|
|
24
34
|
default: () => index_default
|
|
25
35
|
});
|
|
26
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_dompurify = __toESM(require("dompurify"));
|
|
38
|
+
var import_featurely_error_tracker = require("featurely-error-tracker");
|
|
39
|
+
var internalErrorTracker = (() => {
|
|
40
|
+
try {
|
|
41
|
+
const tracker = new import_featurely_error_tracker.ErrorTracker({
|
|
42
|
+
apiKey: "ft_live_R4nAn9dDWxk6X3oMzB-tcQh0NrYvA04IhSfwPMUmyaU",
|
|
43
|
+
apiUrl: "https://featurely.no",
|
|
44
|
+
environment: "production",
|
|
45
|
+
appVersion: "1.0.7",
|
|
46
|
+
maxBreadcrumbs: 30,
|
|
47
|
+
enabled: true
|
|
48
|
+
});
|
|
49
|
+
tracker.install();
|
|
50
|
+
return tracker;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.warn("Failed to initialize SDK error tracking:", e);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
27
56
|
var _SiteManager = class _SiteManager {
|
|
28
57
|
constructor(config) {
|
|
29
58
|
this.siteConfig = null;
|
|
@@ -140,7 +169,11 @@ var _SiteManager = class _SiteManager {
|
|
|
140
169
|
return null;
|
|
141
170
|
}
|
|
142
171
|
const cacheKey = `variant_${flagKey}`;
|
|
143
|
-
|
|
172
|
+
let cached = null;
|
|
173
|
+
try {
|
|
174
|
+
cached = localStorage.getItem(cacheKey);
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
144
177
|
if (cached) {
|
|
145
178
|
return cached;
|
|
146
179
|
}
|
|
@@ -149,12 +182,18 @@ var _SiteManager = class _SiteManager {
|
|
|
149
182
|
for (const variant of flag.variants) {
|
|
150
183
|
cumulative += variant.weight;
|
|
151
184
|
if (bucket < cumulative) {
|
|
152
|
-
|
|
185
|
+
try {
|
|
186
|
+
localStorage.setItem(cacheKey, variant.key);
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
153
189
|
return variant.key;
|
|
154
190
|
}
|
|
155
191
|
}
|
|
156
192
|
const defaultVariant = flag.defaultVariant || flag.variants[0].key;
|
|
157
|
-
|
|
193
|
+
try {
|
|
194
|
+
localStorage.setItem(cacheKey, defaultVariant);
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
158
197
|
return defaultVariant;
|
|
159
198
|
}
|
|
160
199
|
/**
|
|
@@ -308,6 +347,11 @@ var _SiteManager = class _SiteManager {
|
|
|
308
347
|
}
|
|
309
348
|
}
|
|
310
349
|
generateSessionId() {
|
|
350
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
351
|
+
const array = new Uint8Array(16);
|
|
352
|
+
crypto.getRandomValues(array);
|
|
353
|
+
return `${Date.now()}-${Array.from(array, (byte) => byte.toString(36)).join("")}`;
|
|
354
|
+
}
|
|
311
355
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
312
356
|
}
|
|
313
357
|
// ============================================================================
|
|
@@ -369,7 +413,13 @@ var _SiteManager = class _SiteManager {
|
|
|
369
413
|
const storageKey = "featurely_anonymous_id";
|
|
370
414
|
let id = localStorage.getItem(storageKey);
|
|
371
415
|
if (!id) {
|
|
372
|
-
|
|
416
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
417
|
+
const array = new Uint8Array(16);
|
|
418
|
+
crypto.getRandomValues(array);
|
|
419
|
+
id = `anon_${Date.now()}_${Array.from(array, (byte) => byte.toString(36)).join("")}`;
|
|
420
|
+
} else {
|
|
421
|
+
id = `anon_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
422
|
+
}
|
|
373
423
|
localStorage.setItem(storageKey, id);
|
|
374
424
|
}
|
|
375
425
|
return id;
|
|
@@ -435,7 +485,35 @@ var _SiteManager = class _SiteManager {
|
|
|
435
485
|
overlay.id = "featurely-maintenance-overlay";
|
|
436
486
|
overlay.className = "featurely-maintenance-overlay";
|
|
437
487
|
if (config.type === "custom" && config.customHtml) {
|
|
438
|
-
overlay.innerHTML = config.customHtml
|
|
488
|
+
overlay.innerHTML = import_dompurify.default.sanitize(config.customHtml, {
|
|
489
|
+
ALLOWED_TAGS: [
|
|
490
|
+
"div",
|
|
491
|
+
"span",
|
|
492
|
+
"p",
|
|
493
|
+
"h1",
|
|
494
|
+
"h2",
|
|
495
|
+
"h3",
|
|
496
|
+
"h4",
|
|
497
|
+
"h5",
|
|
498
|
+
"h6",
|
|
499
|
+
"strong",
|
|
500
|
+
"em",
|
|
501
|
+
"b",
|
|
502
|
+
"i",
|
|
503
|
+
"u",
|
|
504
|
+
"br",
|
|
505
|
+
"ul",
|
|
506
|
+
"ol",
|
|
507
|
+
"li",
|
|
508
|
+
"a",
|
|
509
|
+
"img",
|
|
510
|
+
"button"
|
|
511
|
+
],
|
|
512
|
+
ALLOWED_ATTR: ["class", "id", "href", "src", "alt", "title", "style"],
|
|
513
|
+
FORBID_TAGS: ["script", "iframe", "embed", "object", "applet", "meta", "link", "form", "input"],
|
|
514
|
+
FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover"],
|
|
515
|
+
ALLOW_DATA_ATTR: false
|
|
516
|
+
});
|
|
439
517
|
} else {
|
|
440
518
|
overlay.innerHTML = this.getDefaultMaintenanceHtml(config);
|
|
441
519
|
}
|
|
@@ -480,8 +558,14 @@ var _SiteManager = class _SiteManager {
|
|
|
480
558
|
}
|
|
481
559
|
if (message.targetPages && message.targetPages.length > 0) {
|
|
482
560
|
const matches = message.targetPages.some((pattern) => {
|
|
483
|
-
|
|
484
|
-
|
|
561
|
+
try {
|
|
562
|
+
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
563
|
+
const regex = new RegExp(escapedPattern);
|
|
564
|
+
return regex.test(currentPath);
|
|
565
|
+
} catch (e) {
|
|
566
|
+
console.warn("Invalid pattern:", pattern, e);
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
485
569
|
});
|
|
486
570
|
if (!matches) return false;
|
|
487
571
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import DOMPurify from "dompurify";
|
|
3
|
+
import { ErrorTracker } from "featurely-error-tracker";
|
|
4
|
+
var internalErrorTracker = (() => {
|
|
5
|
+
try {
|
|
6
|
+
const tracker = new ErrorTracker({
|
|
7
|
+
apiKey: "ft_live_R4nAn9dDWxk6X3oMzB-tcQh0NrYvA04IhSfwPMUmyaU",
|
|
8
|
+
apiUrl: "https://featurely.no",
|
|
9
|
+
environment: "production",
|
|
10
|
+
appVersion: "1.0.7",
|
|
11
|
+
maxBreadcrumbs: 30,
|
|
12
|
+
enabled: true
|
|
13
|
+
});
|
|
14
|
+
tracker.install();
|
|
15
|
+
return tracker;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn("Failed to initialize SDK error tracking:", e);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
2
21
|
var _SiteManager = class _SiteManager {
|
|
3
22
|
constructor(config) {
|
|
4
23
|
this.siteConfig = null;
|
|
@@ -115,7 +134,11 @@ var _SiteManager = class _SiteManager {
|
|
|
115
134
|
return null;
|
|
116
135
|
}
|
|
117
136
|
const cacheKey = `variant_${flagKey}`;
|
|
118
|
-
|
|
137
|
+
let cached = null;
|
|
138
|
+
try {
|
|
139
|
+
cached = localStorage.getItem(cacheKey);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
119
142
|
if (cached) {
|
|
120
143
|
return cached;
|
|
121
144
|
}
|
|
@@ -124,12 +147,18 @@ var _SiteManager = class _SiteManager {
|
|
|
124
147
|
for (const variant of flag.variants) {
|
|
125
148
|
cumulative += variant.weight;
|
|
126
149
|
if (bucket < cumulative) {
|
|
127
|
-
|
|
150
|
+
try {
|
|
151
|
+
localStorage.setItem(cacheKey, variant.key);
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
128
154
|
return variant.key;
|
|
129
155
|
}
|
|
130
156
|
}
|
|
131
157
|
const defaultVariant = flag.defaultVariant || flag.variants[0].key;
|
|
132
|
-
|
|
158
|
+
try {
|
|
159
|
+
localStorage.setItem(cacheKey, defaultVariant);
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
133
162
|
return defaultVariant;
|
|
134
163
|
}
|
|
135
164
|
/**
|
|
@@ -283,6 +312,11 @@ var _SiteManager = class _SiteManager {
|
|
|
283
312
|
}
|
|
284
313
|
}
|
|
285
314
|
generateSessionId() {
|
|
315
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
316
|
+
const array = new Uint8Array(16);
|
|
317
|
+
crypto.getRandomValues(array);
|
|
318
|
+
return `${Date.now()}-${Array.from(array, (byte) => byte.toString(36)).join("")}`;
|
|
319
|
+
}
|
|
286
320
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
287
321
|
}
|
|
288
322
|
// ============================================================================
|
|
@@ -344,7 +378,13 @@ var _SiteManager = class _SiteManager {
|
|
|
344
378
|
const storageKey = "featurely_anonymous_id";
|
|
345
379
|
let id = localStorage.getItem(storageKey);
|
|
346
380
|
if (!id) {
|
|
347
|
-
|
|
381
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
382
|
+
const array = new Uint8Array(16);
|
|
383
|
+
crypto.getRandomValues(array);
|
|
384
|
+
id = `anon_${Date.now()}_${Array.from(array, (byte) => byte.toString(36)).join("")}`;
|
|
385
|
+
} else {
|
|
386
|
+
id = `anon_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
387
|
+
}
|
|
348
388
|
localStorage.setItem(storageKey, id);
|
|
349
389
|
}
|
|
350
390
|
return id;
|
|
@@ -410,7 +450,35 @@ var _SiteManager = class _SiteManager {
|
|
|
410
450
|
overlay.id = "featurely-maintenance-overlay";
|
|
411
451
|
overlay.className = "featurely-maintenance-overlay";
|
|
412
452
|
if (config.type === "custom" && config.customHtml) {
|
|
413
|
-
overlay.innerHTML = config.customHtml
|
|
453
|
+
overlay.innerHTML = DOMPurify.sanitize(config.customHtml, {
|
|
454
|
+
ALLOWED_TAGS: [
|
|
455
|
+
"div",
|
|
456
|
+
"span",
|
|
457
|
+
"p",
|
|
458
|
+
"h1",
|
|
459
|
+
"h2",
|
|
460
|
+
"h3",
|
|
461
|
+
"h4",
|
|
462
|
+
"h5",
|
|
463
|
+
"h6",
|
|
464
|
+
"strong",
|
|
465
|
+
"em",
|
|
466
|
+
"b",
|
|
467
|
+
"i",
|
|
468
|
+
"u",
|
|
469
|
+
"br",
|
|
470
|
+
"ul",
|
|
471
|
+
"ol",
|
|
472
|
+
"li",
|
|
473
|
+
"a",
|
|
474
|
+
"img",
|
|
475
|
+
"button"
|
|
476
|
+
],
|
|
477
|
+
ALLOWED_ATTR: ["class", "id", "href", "src", "alt", "title", "style"],
|
|
478
|
+
FORBID_TAGS: ["script", "iframe", "embed", "object", "applet", "meta", "link", "form", "input"],
|
|
479
|
+
FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover"],
|
|
480
|
+
ALLOW_DATA_ATTR: false
|
|
481
|
+
});
|
|
414
482
|
} else {
|
|
415
483
|
overlay.innerHTML = this.getDefaultMaintenanceHtml(config);
|
|
416
484
|
}
|
|
@@ -455,8 +523,14 @@ var _SiteManager = class _SiteManager {
|
|
|
455
523
|
}
|
|
456
524
|
if (message.targetPages && message.targetPages.length > 0) {
|
|
457
525
|
const matches = message.targetPages.some((pattern) => {
|
|
458
|
-
|
|
459
|
-
|
|
526
|
+
try {
|
|
527
|
+
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
528
|
+
const regex = new RegExp(escapedPattern);
|
|
529
|
+
return regex.test(currentPath);
|
|
530
|
+
} catch (e) {
|
|
531
|
+
console.warn("Invalid pattern:", pattern, e);
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
460
534
|
});
|
|
461
535
|
if (!matches) return false;
|
|
462
536
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "featurely-site-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Site management SDK for maintenance mode, status messages, and feature flags",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -44,9 +44,12 @@
|
|
|
44
44
|
},
|
|
45
45
|
"homepage": "https://featurely.no",
|
|
46
46
|
"devDependencies": {
|
|
47
|
+
"@types/dompurify": "^3.0.5",
|
|
47
48
|
"tsup": "^8.5.1",
|
|
48
49
|
"typescript": "^5.0.0"
|
|
49
50
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"dompurify": "^3.3.3",
|
|
53
|
+
"featurely-error-tracker": "^1.0.7"
|
|
54
|
+
}
|
|
52
55
|
}
|