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 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
- const cached = localStorage.getItem(cacheKey);
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
- localStorage.setItem(cacheKey, variant.key);
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
- localStorage.setItem(cacheKey, defaultVariant);
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
- id = `anon_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
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
- const regex = new RegExp(pattern);
484
- return regex.test(currentPath);
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
- const cached = localStorage.getItem(cacheKey);
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
- localStorage.setItem(cacheKey, variant.key);
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
- localStorage.setItem(cacheKey, defaultVariant);
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
- id = `anon_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
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
- const regex = new RegExp(pattern);
459
- return regex.test(currentPath);
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.4",
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
- "peerDependencies": {},
51
- "dependencies": {}
51
+ "dependencies": {
52
+ "dompurify": "^3.3.3",
53
+ "featurely-error-tracker": "^1.0.7"
54
+ }
52
55
  }