featurely-site-manager 1.0.8 → 1.1.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/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ 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.1.0] - 2026-03-15
9
+
10
+ ### Added
11
+
12
+ - **Version Management**: Complete app version checking and update notification system
13
+ - Automatic version checking with configurable intervals (default: 1 hour)
14
+ - Force update support for critical releases (minimum version)
15
+ - Recommended update notifications
16
+ - `checkVersion(currentVersion?)` - Manual version checking
17
+ - `getLastVersionCheck()` - Get cached version check result
18
+ - `onUpdateAvailable` callback for optional updates
19
+ - `onUpdateRequired` callback for forced updates
20
+ - Release notes and download URL support
21
+ - Beta version flagging
22
+ - **Feature Flags**: Complete feature flag support
23
+ - `isFeatureEnabled(flagKey)` - Check if flag is enabled
24
+ - `getFeatureVariant(flagKey)` - Get A/B test variant
25
+ - `getAllFeatureFlags()` - Get all flags
26
+ - `getEnabledFeatures()` - Get enabled flags for current user
27
+ - Email-based targeting
28
+ - Percentage-based rollouts with consistent bucketing
29
+ - A/B testing with weighted variants
30
+ - `onFeatureFlagsUpdated` callback
31
+ - **Analytics**: Custom event tracking
32
+ - `trackEvent(eventName, properties)` - Track custom events
33
+ - Automatic feature flag usage tracking
34
+ - Session management
35
+ - Configurable flush intervals
36
+ - User identification support
37
+
38
+ ### Changed
39
+
40
+ - Updated package description to reflect full feature set
41
+ - Added comprehensive README documentation for all features
42
+ - Enhanced TypeScript types for version management
43
+ - Improved configuration options with granular control
44
+
8
45
  ## [1.0.8] - 2026-03-15
9
46
 
10
47
  ### Fixed
package/README.md CHANGED
@@ -43,6 +43,31 @@ siteManager.init();
43
43
  - Call-to-action buttons
44
44
  - Auto-expire functionality
45
45
 
46
+ ### 🚀 Version Management
47
+
48
+ - Automatic version checking
49
+ - Force updates for critical releases (minimum version)
50
+ - Recommended version notifications
51
+ - Configurable check intervals (default: 1 hour)
52
+ - Release notes and download URLs
53
+ - Beta version support
54
+ - Update callbacks for custom UI
55
+
56
+ ### 🔐 Feature Flags
57
+
58
+ - Enable/disable features remotely
59
+ - Percentage-based rollouts
60
+ - User targeting by email
61
+ - A/B testing with variants
62
+ - Consistent user bucketing
63
+
64
+ ### 📊 Analytics
65
+
66
+ - Track custom events
67
+ - Automatic feature flag usage tracking
68
+ - Session management
69
+ - User identification
70
+
46
71
  ## 📖 Usage
47
72
 
48
73
  ### Basic Setup
@@ -115,6 +140,318 @@ const manager = new SiteManager({
115
140
  manager.init();
116
141
  ```
117
142
 
143
+ ### Version Checking
144
+
145
+ ```typescript
146
+ const manager = new SiteManager({
147
+ apiKey: "ft_live_your_api_key",
148
+ projectId: "proj_your_project_id",
149
+ appVersion: "1.2.3", // Your current app version
150
+ enableVersionCheck: true,
151
+ versionCheckInterval: 3600000, // Check every hour (default)
152
+
153
+ onUpdateAvailable: (versionInfo) => {
154
+ console.log("Update available:", versionInfo.latestVersion);
155
+ // Show optional update notification to user
156
+ },
157
+
158
+ onUpdateRequired: (versionInfo) => {
159
+ console.log("Update required:", versionInfo.latestVersion);
160
+ // Force user to update (breaking changes)
161
+ if (confirm(`Update required: ${versionInfo.latestVersion.title}\n\n${versionInfo.latestVersion.releaseNotes}`)) {
162
+ window.location.href = versionInfo.latestVersion.downloadUrl || '/update';
163
+ }
164
+ },
165
+ });
166
+
167
+ manager.init();
168
+
169
+ // Manual version check
170
+ const versionStatus = await manager.checkVersion();
171
+ if (versionStatus?.updateAvailable) {
172
+ console.log("New version:", versionStatus.latestVersion);
173
+ }
174
+ ```
175
+
176
+ ### Feature Flags
177
+
178
+ ```typescript
179
+ const manager = new SiteManager({
180
+ apiKey: "ft_live_your_api_key",
181
+ projectId: "proj_your_project_id",
182
+ userEmail: "user@example.com",
183
+
184
+ onFeatureFlagsUpdated: (flags) => {
185
+ console.log("Feature flags updated:", flags);
186
+ },
187
+ });
188
+
189
+ manager.init();
190
+
191
+ // Check if a feature is enabled
192
+ if (manager.isFeatureEnabled("new-checkout")) {
193
+ // Show new checkout UI
194
+ }
195
+
196
+ // Get A/B test variant
197
+ const variant = manager.getFeatureVariant("homepage-design");
198
+ if (variant === "variant-a") {
199
+ // Show design A
200
+ }
201
+
202
+ // Get all enabled features
203
+ const enabledFeatures = manager.getEnabledFeatures();
204
+ console.log("Enabled features:", enabledFeatures);
205
+ ```
206
+
207
+ ### Analytics
208
+
209
+ ```typescript
210
+ const manager = new SiteManager({
211
+ apiKey: "ft_live_your_api_key",
212
+ projectId: "proj_your_project_id",
213
+ userId: "user_123",
214
+ enableAnalytics: true,
215
+ analyticsFlushInterval: 60000, // Flush every minute
216
+ });
217
+
218
+ manager.init();
219
+
220
+ // Track custom events
221
+ manager.trackEvent("button_clicked", {
222
+ button: "signup",
223
+ page: "/home",
224
+ });
225
+
226
+ manager.trackEvent("feature_used", {
227
+ feature: "dark_mode",
228
+ enabled: true,
229
+ });
230
+ ```
231
+
232
+ ### Custom Poll Interval
233
+
234
+ ```typescript
235
+ const manager = new SiteManager({
236
+ apiKey: "ft_live_your_api_key",
237
+ projectId: "proj_your_project_id",
238
+ pollInterval: 30000, // Check every 30 seconds instead of default 60s
239
+ });
240
+
241
+ manager.init();
242
+ ```
243
+
244
+ ## 🔧 Configuration
245
+
246
+ ### SiteManagerConfig
247
+
248
+ | Option | Type | Required | Default | Description |
249
+ | ------------------------ | --------------- | -------- | ------------------------- | ----------------------------------------- |
250
+ | `apiKey` | `string` | ✅ | - | Your Featurely API key |
251
+ | `projectId` | `string` | ✅ | - | Your Featurely project ID |
252
+ | `apiUrl` | `string` | ❌ | `'https://featurely.no'` | Custom API endpoint |
253
+ | `pollInterval` | `number` | ❌ | `60000` | Config polling interval in ms |
254
+ | `userEmail` | `string` | ❌ | - | User email for whitelist & targeting |
255
+ | `userId` | `string` | ❌ | - | User ID for analytics & feature flags |
256
+ | `bypassCheck` | `() => boolean` | ❌ | - | Custom maintenance bypass function |
257
+ | `onMaintenanceEnabled` | `function` | ❌ | - | Maintenance enabled callback |
258
+ | `onMaintenanceDisabled` | `function` | ❌ | - | Maintenance disabled callback |
259
+ | `onMessageReceived` | `function` | ❌ | - | Message received callback |
260
+ | `onMessageDismissed` | `function` | ❌ | - | Message dismissed callback |
261
+ | `onFeatureFlagsUpdated` | `function` | ❌ | - | Feature flags updated callback |
262
+ | `enableAnalytics` | `boolean` | ❌ | `true` | Enable analytics tracking |
263
+ | `analyticsFlushInterval` | `number` | ❌ | `60000` | Analytics flush interval in ms |
264
+ | `appVersion` | `string` | ❌ | - | Current app version for version checking |
265
+ | `enableVersionCheck` | `boolean` | ❌ | `false` | Enable automatic version checking |
266
+ | `versionCheckInterval` | `number` | ❌ | `3600000` | Version check interval in ms (1 hour) |
267
+ | `onUpdateAvailable` | `function` | ❌ | - | Callback when update is available |
268
+ | `onUpdateRequired` | `function` | ❌ | - | Callback when update is required (forced) |
269
+ | `onError` | `function` | ❌ | - | Error callback |
270
+
271
+ ## 🎯 API Methods
272
+
273
+ ### Core Methods
274
+
275
+ #### `init()`
276
+
277
+ Initialize and start the site manager.
278
+
279
+ ```typescript
280
+ await manager.init();
281
+ ```
282
+
283
+ #### `setUser(email: string, userId?: string)`
284
+
285
+ Update user email and ID for whitelist and targeting.
286
+
287
+ ```typescript
288
+ manager.setUser("user@example.com", "user_123");
289
+ ```
290
+
291
+ #### `refresh()`
292
+
293
+ Manually refresh configuration from server.
294
+
295
+ ```typescript
296
+ await manager.refresh();
297
+ ```
298
+
299
+ #### `destroy()`
300
+
301
+ Stop the manager and clean up.
302
+
303
+ ```typescript
304
+ manager.destroy();
305
+ ```
306
+
307
+ ### Version Management Methods
308
+
309
+ #### `checkVersion(currentVersion?: string)`
310
+
311
+ Manually check if an update is available.
312
+
313
+ ```typescript
314
+ const versionInfo = await manager.checkVersion();
315
+ if (versionInfo?.updateAvailable) {
316
+ console.log("Update available:", versionInfo.latestVersion);
317
+ console.log("Is required?", versionInfo.updateRequired);
318
+ console.log("Is recommended?", versionInfo.updateRecommended);
319
+ }
320
+
321
+ // Check a specific version
322
+ const customCheck = await manager.checkVersion("1.0.0");
323
+ ```
324
+
325
+ #### `getLastVersionCheck()`
326
+
327
+ Get the last cached version check result.
328
+
329
+ ```typescript
330
+ const lastCheck = manager.getLastVersionCheck();
331
+ if (lastCheck?.updateAvailable) {
332
+ // Show update notification
333
+ }
334
+ ```
335
+
336
+ ### Feature Flag Methods
337
+
338
+ #### `isFeatureEnabled(flagKey: string)`
339
+
340
+ Check if a feature flag is enabled for the current user.
341
+
342
+ ```typescript
343
+ if (manager.isFeatureEnabled("new-dashboard")) {
344
+ // Show new dashboard
345
+ }
346
+ ```
347
+
348
+ #### `getFeatureVariant(flagKey: string)`
349
+
350
+ Get the assigned variant for A/B testing.
351
+
352
+ ```typescript
353
+ const variant = manager.getFeatureVariant("homepage-redesign");
354
+ // Returns: 'control', 'variant-a', 'variant-b', etc.
355
+ ```
356
+
357
+ #### `getAllFeatureFlags()`
358
+
359
+ Get all feature flags.
360
+
361
+ ```typescript
362
+ const flags = manager.getAllFeatureFlags();
363
+ console.log("All flags:", flags);
364
+ ```
365
+
366
+ #### `getEnabledFeatures()`
367
+
368
+ Get all enabled feature flag keys for the current user.
369
+
370
+ ```typescript
371
+ const enabledFeatures = manager.getEnabledFeatures();
372
+ // Returns: ['feature-1', 'feature-2', ...]
373
+ ```
374
+
375
+ ### Analytics Methods
376
+
377
+ #### `trackEvent(eventName: string, properties?: object)`
378
+
379
+ Track a custom analytics event.
380
+
381
+ ```typescript
382
+ manager.trackEvent("button_clicked", {
383
+ button: "signup",
384
+ page: "/pricing",
385
+ });
386
+
387
+ manager.trackEvent("purchase_completed", {
388
+ amount: 99.99,
389
+ currency: "USD",
390
+ plan: "pro",
391
+ });
392
+ ```
393
+
394
+ ## 💼 Use Cases
395
+
396
+ ### Update Notifications
397
+
398
+ ```typescript
399
+ const manager = new SiteManager({
400
+ apiKey: "ft_live_...",
401
+ projectId: "proj_...",
402
+ appVersion: "1.2.3",
403
+ enableVersionCheck: true,
404
+
405
+ onUpdateRequired: (versionInfo) => {
406
+ // Block app usage until updated
407
+ document.body.innerHTML = `
408
+ <div style="text-align: center; padding: 60px;">
409
+ <h1>Critical Update Required</h1>
410
+ <p>${versionInfo.latestVersion.title}</p>
411
+ <p>${versionInfo.latestVersion.releaseNotes}</p>
412
+ <a href="${versionInfo.latestVersion.downloadUrl}">
413
+ <button>Download Update</button>
414
+ </a>
415
+ </div>
416
+ `;
417
+ },
418
+
419
+ onUpdateAvailable: (versionInfo) => {
420
+ // Show non-blocking notification
421
+ if (!versionInfo.latestVersion.isBeta) {
422
+ showToast(`Update available: ${versionInfo.latestVersion.version}`);
423
+ }
424
+ },
425
+ });
426
+ ```
427
+
428
+ ### Feature Rollouts
429
+
430
+ ```typescript
431
+ // Dashboard configuration
432
+ manager.init();
433
+
434
+ // Gradually enable new checkout for 20% of users
435
+ if (manager.isFeatureEnabled("new-checkout-v2")) {
436
+ loadNewCheckout();
437
+ } else {
438
+ loadLegacyCheckout();
439
+ }
440
+
441
+ // A/B test with variants
442
+ const buttonColor = manager.getFeatureVariant("cta-button-color");
443
+ switch (buttonColor) {
444
+ case "green":
445
+ setButtonColor("#00CC66");
446
+ break;
447
+ case "blue":
448
+ setButtonColor("#0066CC");
449
+ break;
450
+ default:
451
+ setButtonColor("#FF6600"); // control
452
+ }
453
+ ```
454
+
118
455
  ### Custom Poll Interval
119
456
 
120
457
  ```typescript
package/dist/index.d.mts CHANGED
@@ -49,6 +49,20 @@ interface MaintenanceConfig {
49
49
  ips?: string[];
50
50
  };
51
51
  }
52
+ interface AppVersion {
53
+ version: string;
54
+ title: string;
55
+ releaseNotes: string;
56
+ downloadUrl?: string;
57
+ releaseDate: string;
58
+ isBeta?: boolean;
59
+ }
60
+ interface VersionCheckResponse {
61
+ updateAvailable: boolean;
62
+ updateRequired: boolean;
63
+ updateRecommended: boolean;
64
+ latestVersion?: AppVersion;
65
+ }
52
66
  interface SiteConfig {
53
67
  maintenance: MaintenanceConfig;
54
68
  messages: StatusMessage[];
@@ -70,12 +84,18 @@ interface SiteManagerConfig {
70
84
  userId?: string;
71
85
  enableAnalytics?: boolean;
72
86
  analyticsFlushInterval?: number;
87
+ appVersion?: string;
88
+ enableVersionCheck?: boolean;
89
+ versionCheckInterval?: number;
90
+ onUpdateAvailable?: (versionInfo: VersionCheckResponse) => void;
91
+ onUpdateRequired?: (versionInfo: VersionCheckResponse) => void;
73
92
  onError?: (error: Error) => void;
74
93
  }
75
94
  declare class SiteManager {
76
95
  private config;
77
96
  private siteConfig;
78
97
  private pollIntervalId;
98
+ private versionCheckIntervalId;
79
99
  private messageContainers;
80
100
  private dismissedMessages;
81
101
  private featureFlagBuckets;
@@ -84,6 +104,7 @@ declare class SiteManager {
84
104
  private sessionId;
85
105
  private consecutiveFetchFailures;
86
106
  private static readonly MAX_CONSECUTIVE_FAILURES;
107
+ private lastVersionCheck;
87
108
  constructor(config: SiteManagerConfig);
88
109
  init(): Promise<void>;
89
110
  destroy(): void;
@@ -92,6 +113,8 @@ declare class SiteManager {
92
113
  getFeatureVariant(flagKey: string): string | null;
93
114
  getAllFeatureFlags(): FeatureFlag[];
94
115
  getEnabledFeatures(): string[];
116
+ isInMaintenanceMode(): boolean;
117
+ getActiveMessages(): StatusMessage[];
95
118
  refresh(): Promise<void>;
96
119
  trackEvent(eventName: string, properties?: Record<string, string | number | boolean>): void;
97
120
  private fetchConfig;
@@ -101,6 +124,10 @@ declare class SiteManager {
101
124
  private stopAnalyticsFlushing;
102
125
  private flushAnalytics;
103
126
  private generateSessionId;
127
+ checkVersion(currentVersion?: string): Promise<VersionCheckResponse | null>;
128
+ getLastVersionCheck(): VersionCheckResponse | null;
129
+ private startVersionChecking;
130
+ private stopVersionChecking;
104
131
  private evaluateFeatureFlag;
105
132
  private getUserBucket;
106
133
  private simpleHash;
@@ -123,4 +150,4 @@ declare class SiteManager {
123
150
  private injectStyles;
124
151
  }
125
152
 
126
- export { type FeatureFlag, type MaintenanceConfig, type MessagePosition, type MessageStyle, type MessageType, type SiteConfig, SiteManager, type SiteManagerConfig, type StatusMessage, SiteManager as default };
153
+ export { type AppVersion, type FeatureFlag, type MaintenanceConfig, type MessagePosition, type MessageStyle, type MessageType, type SiteConfig, SiteManager, type SiteManagerConfig, type StatusMessage, type VersionCheckResponse, SiteManager as default };
package/dist/index.d.ts CHANGED
@@ -49,6 +49,20 @@ interface MaintenanceConfig {
49
49
  ips?: string[];
50
50
  };
51
51
  }
52
+ interface AppVersion {
53
+ version: string;
54
+ title: string;
55
+ releaseNotes: string;
56
+ downloadUrl?: string;
57
+ releaseDate: string;
58
+ isBeta?: boolean;
59
+ }
60
+ interface VersionCheckResponse {
61
+ updateAvailable: boolean;
62
+ updateRequired: boolean;
63
+ updateRecommended: boolean;
64
+ latestVersion?: AppVersion;
65
+ }
52
66
  interface SiteConfig {
53
67
  maintenance: MaintenanceConfig;
54
68
  messages: StatusMessage[];
@@ -70,12 +84,18 @@ interface SiteManagerConfig {
70
84
  userId?: string;
71
85
  enableAnalytics?: boolean;
72
86
  analyticsFlushInterval?: number;
87
+ appVersion?: string;
88
+ enableVersionCheck?: boolean;
89
+ versionCheckInterval?: number;
90
+ onUpdateAvailable?: (versionInfo: VersionCheckResponse) => void;
91
+ onUpdateRequired?: (versionInfo: VersionCheckResponse) => void;
73
92
  onError?: (error: Error) => void;
74
93
  }
75
94
  declare class SiteManager {
76
95
  private config;
77
96
  private siteConfig;
78
97
  private pollIntervalId;
98
+ private versionCheckIntervalId;
79
99
  private messageContainers;
80
100
  private dismissedMessages;
81
101
  private featureFlagBuckets;
@@ -84,6 +104,7 @@ declare class SiteManager {
84
104
  private sessionId;
85
105
  private consecutiveFetchFailures;
86
106
  private static readonly MAX_CONSECUTIVE_FAILURES;
107
+ private lastVersionCheck;
87
108
  constructor(config: SiteManagerConfig);
88
109
  init(): Promise<void>;
89
110
  destroy(): void;
@@ -92,6 +113,8 @@ declare class SiteManager {
92
113
  getFeatureVariant(flagKey: string): string | null;
93
114
  getAllFeatureFlags(): FeatureFlag[];
94
115
  getEnabledFeatures(): string[];
116
+ isInMaintenanceMode(): boolean;
117
+ getActiveMessages(): StatusMessage[];
95
118
  refresh(): Promise<void>;
96
119
  trackEvent(eventName: string, properties?: Record<string, string | number | boolean>): void;
97
120
  private fetchConfig;
@@ -101,6 +124,10 @@ declare class SiteManager {
101
124
  private stopAnalyticsFlushing;
102
125
  private flushAnalytics;
103
126
  private generateSessionId;
127
+ checkVersion(currentVersion?: string): Promise<VersionCheckResponse | null>;
128
+ getLastVersionCheck(): VersionCheckResponse | null;
129
+ private startVersionChecking;
130
+ private stopVersionChecking;
104
131
  private evaluateFeatureFlag;
105
132
  private getUserBucket;
106
133
  private simpleHash;
@@ -123,4 +150,4 @@ declare class SiteManager {
123
150
  private injectStyles;
124
151
  }
125
152
 
126
- export { type FeatureFlag, type MaintenanceConfig, type MessagePosition, type MessageStyle, type MessageType, type SiteConfig, SiteManager, type SiteManagerConfig, type StatusMessage, SiteManager as default };
153
+ export { type AppVersion, type FeatureFlag, type MaintenanceConfig, type MessagePosition, type MessageStyle, type MessageType, type SiteConfig, SiteManager, type SiteManagerConfig, type StatusMessage, type VersionCheckResponse, SiteManager as default };
package/dist/index.js CHANGED
@@ -35,28 +35,11 @@ __export(index_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
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://www.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
- })();
56
38
  var _SiteManager = class _SiteManager {
57
39
  constructor(config) {
58
40
  this.siteConfig = null;
59
41
  this.pollIntervalId = null;
42
+ this.versionCheckIntervalId = null;
60
43
  this.messageContainers = /* @__PURE__ */ new Map();
61
44
  this.dismissedMessages = /* @__PURE__ */ new Set();
62
45
  this.featureFlagBuckets = /* @__PURE__ */ new Map();
@@ -65,7 +48,8 @@ var _SiteManager = class _SiteManager {
65
48
  this.analyticsFlushIntervalId = null;
66
49
  this.sessionId = this.generateSessionId();
67
50
  this.consecutiveFetchFailures = 0;
68
- var _a, _b, _c;
51
+ this.lastVersionCheck = null;
52
+ var _a, _b, _c, _d, _e;
69
53
  if (!config.apiKey) {
70
54
  throw new Error("Featurely Site Manager: apiKey is required");
71
55
  }
@@ -87,7 +71,13 @@ var _SiteManager = class _SiteManager {
87
71
  onFeatureFlagsUpdated: config.onFeatureFlagsUpdated,
88
72
  onError: config.onError,
89
73
  enableAnalytics: (_b = config.enableAnalytics) != null ? _b : true,
90
- analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4
74
+ analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4,
75
+ appVersion: config.appVersion,
76
+ enableVersionCheck: (_d = config.enableVersionCheck) != null ? _d : false,
77
+ versionCheckInterval: (_e = config.versionCheckInterval) != null ? _e : 36e5,
78
+ // 1 hour
79
+ onUpdateAvailable: config.onUpdateAvailable,
80
+ onUpdateRequired: config.onUpdateRequired
91
81
  };
92
82
  this.loadDismissedMessages();
93
83
  }
@@ -106,6 +96,10 @@ var _SiteManager = class _SiteManager {
106
96
  if (this.config.enableAnalytics) {
107
97
  this.startAnalyticsFlushing();
108
98
  }
99
+ if (this.config.enableVersionCheck && this.config.appVersion) {
100
+ await this.checkVersion();
101
+ this.startVersionChecking();
102
+ }
109
103
  this.injectStyles();
110
104
  }
111
105
  /**
@@ -113,6 +107,7 @@ var _SiteManager = class _SiteManager {
113
107
  */
114
108
  destroy() {
115
109
  this.stopPolling();
110
+ this.stopVersionChecking();
116
111
  this.stopAnalyticsFlushing();
117
112
  this.flushAnalytics();
118
113
  this.clearMessages();
@@ -213,6 +208,39 @@ var _SiteManager = class _SiteManager {
213
208
  }
214
209
  return this.siteConfig.featureFlags.filter((flag) => flag.enabled && this.evaluateFeatureFlag(flag)).map((flag) => flag.key);
215
210
  }
211
+ /**
212
+ * Check if the site is currently in maintenance mode (accounting for bypass)
213
+ */
214
+ isInMaintenanceMode() {
215
+ var _a;
216
+ if (!((_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled)) return false;
217
+ return !this.shouldBypassMaintenance();
218
+ }
219
+ /**
220
+ * Get all currently active status messages for the current page and time
221
+ */
222
+ getActiveMessages() {
223
+ if (!this.siteConfig) return [];
224
+ const currentTime = /* @__PURE__ */ new Date();
225
+ const currentPath = typeof window !== "undefined" ? window.location.pathname : "";
226
+ return this.siteConfig.messages.filter((message) => {
227
+ if (this.dismissedMessages.has(message.id)) return false;
228
+ if (message.startsAt && new Date(message.startsAt) > currentTime) return false;
229
+ if (message.expiresAt && new Date(message.expiresAt) < currentTime) return false;
230
+ if (message.targetPages && message.targetPages.length > 0) {
231
+ const matches = message.targetPages.some((pattern) => {
232
+ try {
233
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
234
+ return new RegExp(escaped).test(currentPath);
235
+ } catch {
236
+ return false;
237
+ }
238
+ });
239
+ if (!matches) return false;
240
+ }
241
+ return true;
242
+ });
243
+ }
216
244
  /**
217
245
  * Manually refresh configuration
218
246
  */
@@ -355,6 +383,76 @@ var _SiteManager = class _SiteManager {
355
383
  return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
356
384
  }
357
385
  // ============================================================================
386
+ // Version Checking
387
+ // ============================================================================
388
+ /**
389
+ * Check if an app update is available
390
+ * @param currentVersion - Optional version to check (defaults to config.appVersion)
391
+ * @returns Version check response with update status
392
+ */
393
+ async checkVersion(currentVersion) {
394
+ const versionToCheck = currentVersion || this.config.appVersion;
395
+ if (!versionToCheck) {
396
+ console.warn(
397
+ "Featurely Site Manager: appVersion not provided for version check"
398
+ );
399
+ return null;
400
+ }
401
+ try {
402
+ const response = await fetch(
403
+ `${this.config.apiUrl}/api/public/v1/version-check?projectId=${this.config.projectId}&currentVersion=${versionToCheck}`,
404
+ {
405
+ headers: {
406
+ "X-API-Key": this.config.apiKey
407
+ }
408
+ }
409
+ );
410
+ if (!response.ok) {
411
+ throw new Error(`Version check failed: ${response.statusText}`);
412
+ }
413
+ const versionInfo = await response.json();
414
+ this.lastVersionCheck = versionInfo;
415
+ this.trackEvent("version_checked", {
416
+ currentVersion: versionToCheck,
417
+ updateAvailable: versionInfo.updateAvailable,
418
+ updateRequired: versionInfo.updateRequired,
419
+ updateRecommended: versionInfo.updateRecommended
420
+ });
421
+ if (versionInfo.updateRequired && this.config.onUpdateRequired) {
422
+ this.config.onUpdateRequired(versionInfo);
423
+ } else if (versionInfo.updateAvailable && this.config.onUpdateAvailable) {
424
+ this.config.onUpdateAvailable(versionInfo);
425
+ }
426
+ return versionInfo;
427
+ } catch (error) {
428
+ console.error("Error checking version:", error);
429
+ if (this.config.onError) {
430
+ this.config.onError(
431
+ error instanceof Error ? error : new Error("Failed to check version")
432
+ );
433
+ }
434
+ return null;
435
+ }
436
+ }
437
+ /**
438
+ * Get the last version check result (cached)
439
+ */
440
+ getLastVersionCheck() {
441
+ return this.lastVersionCheck;
442
+ }
443
+ startVersionChecking() {
444
+ if (this.versionCheckIntervalId) return;
445
+ this.versionCheckIntervalId = setInterval(() => {
446
+ this.checkVersion();
447
+ }, this.config.versionCheckInterval);
448
+ }
449
+ stopVersionChecking() {
450
+ if (this.versionCheckIntervalId) {
451
+ clearInterval(this.versionCheckIntervalId);
452
+ this.versionCheckIntervalId = null;
453
+ }
454
+ }
455
+ // ============================================================================
358
456
  // Feature Flags
359
457
  // ============================================================================
360
458
  /**
package/dist/index.mjs CHANGED
@@ -1,27 +1,10 @@
1
1
  // src/index.ts
2
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://www.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
- })();
21
3
  var _SiteManager = class _SiteManager {
22
4
  constructor(config) {
23
5
  this.siteConfig = null;
24
6
  this.pollIntervalId = null;
7
+ this.versionCheckIntervalId = null;
25
8
  this.messageContainers = /* @__PURE__ */ new Map();
26
9
  this.dismissedMessages = /* @__PURE__ */ new Set();
27
10
  this.featureFlagBuckets = /* @__PURE__ */ new Map();
@@ -30,7 +13,8 @@ var _SiteManager = class _SiteManager {
30
13
  this.analyticsFlushIntervalId = null;
31
14
  this.sessionId = this.generateSessionId();
32
15
  this.consecutiveFetchFailures = 0;
33
- var _a, _b, _c;
16
+ this.lastVersionCheck = null;
17
+ var _a, _b, _c, _d, _e;
34
18
  if (!config.apiKey) {
35
19
  throw new Error("Featurely Site Manager: apiKey is required");
36
20
  }
@@ -52,7 +36,13 @@ var _SiteManager = class _SiteManager {
52
36
  onFeatureFlagsUpdated: config.onFeatureFlagsUpdated,
53
37
  onError: config.onError,
54
38
  enableAnalytics: (_b = config.enableAnalytics) != null ? _b : true,
55
- analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4
39
+ analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4,
40
+ appVersion: config.appVersion,
41
+ enableVersionCheck: (_d = config.enableVersionCheck) != null ? _d : false,
42
+ versionCheckInterval: (_e = config.versionCheckInterval) != null ? _e : 36e5,
43
+ // 1 hour
44
+ onUpdateAvailable: config.onUpdateAvailable,
45
+ onUpdateRequired: config.onUpdateRequired
56
46
  };
57
47
  this.loadDismissedMessages();
58
48
  }
@@ -71,6 +61,10 @@ var _SiteManager = class _SiteManager {
71
61
  if (this.config.enableAnalytics) {
72
62
  this.startAnalyticsFlushing();
73
63
  }
64
+ if (this.config.enableVersionCheck && this.config.appVersion) {
65
+ await this.checkVersion();
66
+ this.startVersionChecking();
67
+ }
74
68
  this.injectStyles();
75
69
  }
76
70
  /**
@@ -78,6 +72,7 @@ var _SiteManager = class _SiteManager {
78
72
  */
79
73
  destroy() {
80
74
  this.stopPolling();
75
+ this.stopVersionChecking();
81
76
  this.stopAnalyticsFlushing();
82
77
  this.flushAnalytics();
83
78
  this.clearMessages();
@@ -178,6 +173,39 @@ var _SiteManager = class _SiteManager {
178
173
  }
179
174
  return this.siteConfig.featureFlags.filter((flag) => flag.enabled && this.evaluateFeatureFlag(flag)).map((flag) => flag.key);
180
175
  }
176
+ /**
177
+ * Check if the site is currently in maintenance mode (accounting for bypass)
178
+ */
179
+ isInMaintenanceMode() {
180
+ var _a;
181
+ if (!((_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled)) return false;
182
+ return !this.shouldBypassMaintenance();
183
+ }
184
+ /**
185
+ * Get all currently active status messages for the current page and time
186
+ */
187
+ getActiveMessages() {
188
+ if (!this.siteConfig) return [];
189
+ const currentTime = /* @__PURE__ */ new Date();
190
+ const currentPath = typeof window !== "undefined" ? window.location.pathname : "";
191
+ return this.siteConfig.messages.filter((message) => {
192
+ if (this.dismissedMessages.has(message.id)) return false;
193
+ if (message.startsAt && new Date(message.startsAt) > currentTime) return false;
194
+ if (message.expiresAt && new Date(message.expiresAt) < currentTime) return false;
195
+ if (message.targetPages && message.targetPages.length > 0) {
196
+ const matches = message.targetPages.some((pattern) => {
197
+ try {
198
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
199
+ return new RegExp(escaped).test(currentPath);
200
+ } catch {
201
+ return false;
202
+ }
203
+ });
204
+ if (!matches) return false;
205
+ }
206
+ return true;
207
+ });
208
+ }
181
209
  /**
182
210
  * Manually refresh configuration
183
211
  */
@@ -320,6 +348,76 @@ var _SiteManager = class _SiteManager {
320
348
  return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
321
349
  }
322
350
  // ============================================================================
351
+ // Version Checking
352
+ // ============================================================================
353
+ /**
354
+ * Check if an app update is available
355
+ * @param currentVersion - Optional version to check (defaults to config.appVersion)
356
+ * @returns Version check response with update status
357
+ */
358
+ async checkVersion(currentVersion) {
359
+ const versionToCheck = currentVersion || this.config.appVersion;
360
+ if (!versionToCheck) {
361
+ console.warn(
362
+ "Featurely Site Manager: appVersion not provided for version check"
363
+ );
364
+ return null;
365
+ }
366
+ try {
367
+ const response = await fetch(
368
+ `${this.config.apiUrl}/api/public/v1/version-check?projectId=${this.config.projectId}&currentVersion=${versionToCheck}`,
369
+ {
370
+ headers: {
371
+ "X-API-Key": this.config.apiKey
372
+ }
373
+ }
374
+ );
375
+ if (!response.ok) {
376
+ throw new Error(`Version check failed: ${response.statusText}`);
377
+ }
378
+ const versionInfo = await response.json();
379
+ this.lastVersionCheck = versionInfo;
380
+ this.trackEvent("version_checked", {
381
+ currentVersion: versionToCheck,
382
+ updateAvailable: versionInfo.updateAvailable,
383
+ updateRequired: versionInfo.updateRequired,
384
+ updateRecommended: versionInfo.updateRecommended
385
+ });
386
+ if (versionInfo.updateRequired && this.config.onUpdateRequired) {
387
+ this.config.onUpdateRequired(versionInfo);
388
+ } else if (versionInfo.updateAvailable && this.config.onUpdateAvailable) {
389
+ this.config.onUpdateAvailable(versionInfo);
390
+ }
391
+ return versionInfo;
392
+ } catch (error) {
393
+ console.error("Error checking version:", error);
394
+ if (this.config.onError) {
395
+ this.config.onError(
396
+ error instanceof Error ? error : new Error("Failed to check version")
397
+ );
398
+ }
399
+ return null;
400
+ }
401
+ }
402
+ /**
403
+ * Get the last version check result (cached)
404
+ */
405
+ getLastVersionCheck() {
406
+ return this.lastVersionCheck;
407
+ }
408
+ startVersionChecking() {
409
+ if (this.versionCheckIntervalId) return;
410
+ this.versionCheckIntervalId = setInterval(() => {
411
+ this.checkVersion();
412
+ }, this.config.versionCheckInterval);
413
+ }
414
+ stopVersionChecking() {
415
+ if (this.versionCheckIntervalId) {
416
+ clearInterval(this.versionCheckIntervalId);
417
+ this.versionCheckIntervalId = null;
418
+ }
419
+ }
420
+ // ============================================================================
323
421
  // Feature Flags
324
422
  // ============================================================================
325
423
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "featurely-site-manager",
3
- "version": "1.0.8",
4
- "description": "Site management SDK for maintenance mode, status messages, and feature flags",
3
+ "version": "1.1.2",
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",
7
7
  "types": "dist/index.d.ts",
@@ -29,18 +29,23 @@
29
29
  "maintenance-mode",
30
30
  "status-messages",
31
31
  "feature-flags",
32
+ "version-checking",
33
+ "update-notifications",
34
+ "analytics",
32
35
  "downtime",
33
- "banners"
36
+ "banners",
37
+ "a/b-testing",
38
+ "feature-toggles"
34
39
  ],
35
40
  "author": "Featurely",
36
41
  "license": "MIT",
37
42
  "repository": {
38
43
  "type": "git",
39
- "url": "https://github.com/yourusername/featurely.git",
44
+ "url": "https://github.com/featurely/site-manager",
40
45
  "directory": "packages/featurely-site-manager"
41
46
  },
42
47
  "bugs": {
43
- "url": "https://github.com/yourusername/featurely/issues"
48
+ "url": "https://github.com/featurely/site-manager/issues"
44
49
  },
45
50
  "homepage": "https://featurely.no",
46
51
  "devDependencies": {
@@ -49,7 +54,6 @@
49
54
  "typescript": "^5.0.0"
50
55
  },
51
56
  "dependencies": {
52
- "dompurify": "^3.3.3",
53
- "featurely-error-tracker": "^1.0.7"
57
+ "dompurify": "^3.3.3"
54
58
  }
55
59
  }