featurely-site-manager 1.0.8 → 1.1.1
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 +37 -0
- package/README.md +337 -0
- package/dist/index.d.mts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +86 -3
- package/dist/index.mjs +86 -3
- package/package.json +8 -3
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;
|
|
@@ -101,6 +122,10 @@ declare class SiteManager {
|
|
|
101
122
|
private stopAnalyticsFlushing;
|
|
102
123
|
private flushAnalytics;
|
|
103
124
|
private generateSessionId;
|
|
125
|
+
checkVersion(currentVersion?: string): Promise<VersionCheckResponse | null>;
|
|
126
|
+
getLastVersionCheck(): VersionCheckResponse | null;
|
|
127
|
+
private startVersionChecking;
|
|
128
|
+
private stopVersionChecking;
|
|
104
129
|
private evaluateFeatureFlag;
|
|
105
130
|
private getUserBucket;
|
|
106
131
|
private simpleHash;
|
|
@@ -123,4 +148,4 @@ declare class SiteManager {
|
|
|
123
148
|
private injectStyles;
|
|
124
149
|
}
|
|
125
150
|
|
|
126
|
-
export { type FeatureFlag, type MaintenanceConfig, type MessagePosition, type MessageStyle, type MessageType, type SiteConfig, SiteManager, type SiteManagerConfig, type StatusMessage, SiteManager as default };
|
|
151
|
+
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;
|
|
@@ -101,6 +122,10 @@ declare class SiteManager {
|
|
|
101
122
|
private stopAnalyticsFlushing;
|
|
102
123
|
private flushAnalytics;
|
|
103
124
|
private generateSessionId;
|
|
125
|
+
checkVersion(currentVersion?: string): Promise<VersionCheckResponse | null>;
|
|
126
|
+
getLastVersionCheck(): VersionCheckResponse | null;
|
|
127
|
+
private startVersionChecking;
|
|
128
|
+
private stopVersionChecking;
|
|
104
129
|
private evaluateFeatureFlag;
|
|
105
130
|
private getUserBucket;
|
|
106
131
|
private simpleHash;
|
|
@@ -123,4 +148,4 @@ declare class SiteManager {
|
|
|
123
148
|
private injectStyles;
|
|
124
149
|
}
|
|
125
150
|
|
|
126
|
-
export { type FeatureFlag, type MaintenanceConfig, type MessagePosition, type MessageStyle, type MessageType, type SiteConfig, SiteManager, type SiteManagerConfig, type StatusMessage, SiteManager as default };
|
|
151
|
+
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
|
@@ -42,7 +42,7 @@ var internalErrorTracker = (() => {
|
|
|
42
42
|
apiKey: "ft_live_R4nAn9dDWxk6X3oMzB-tcQh0NrYvA04IhSfwPMUmyaU",
|
|
43
43
|
apiUrl: "https://www.featurely.no",
|
|
44
44
|
environment: "production",
|
|
45
|
-
appVersion: "1.
|
|
45
|
+
appVersion: "1.1.1",
|
|
46
46
|
maxBreadcrumbs: 30,
|
|
47
47
|
enabled: true
|
|
48
48
|
});
|
|
@@ -57,6 +57,7 @@ var _SiteManager = class _SiteManager {
|
|
|
57
57
|
constructor(config) {
|
|
58
58
|
this.siteConfig = null;
|
|
59
59
|
this.pollIntervalId = null;
|
|
60
|
+
this.versionCheckIntervalId = null;
|
|
60
61
|
this.messageContainers = /* @__PURE__ */ new Map();
|
|
61
62
|
this.dismissedMessages = /* @__PURE__ */ new Set();
|
|
62
63
|
this.featureFlagBuckets = /* @__PURE__ */ new Map();
|
|
@@ -65,7 +66,8 @@ var _SiteManager = class _SiteManager {
|
|
|
65
66
|
this.analyticsFlushIntervalId = null;
|
|
66
67
|
this.sessionId = this.generateSessionId();
|
|
67
68
|
this.consecutiveFetchFailures = 0;
|
|
68
|
-
|
|
69
|
+
this.lastVersionCheck = null;
|
|
70
|
+
var _a, _b, _c, _d, _e;
|
|
69
71
|
if (!config.apiKey) {
|
|
70
72
|
throw new Error("Featurely Site Manager: apiKey is required");
|
|
71
73
|
}
|
|
@@ -87,7 +89,13 @@ var _SiteManager = class _SiteManager {
|
|
|
87
89
|
onFeatureFlagsUpdated: config.onFeatureFlagsUpdated,
|
|
88
90
|
onError: config.onError,
|
|
89
91
|
enableAnalytics: (_b = config.enableAnalytics) != null ? _b : true,
|
|
90
|
-
analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4
|
|
92
|
+
analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4,
|
|
93
|
+
appVersion: config.appVersion,
|
|
94
|
+
enableVersionCheck: (_d = config.enableVersionCheck) != null ? _d : false,
|
|
95
|
+
versionCheckInterval: (_e = config.versionCheckInterval) != null ? _e : 36e5,
|
|
96
|
+
// 1 hour
|
|
97
|
+
onUpdateAvailable: config.onUpdateAvailable,
|
|
98
|
+
onUpdateRequired: config.onUpdateRequired
|
|
91
99
|
};
|
|
92
100
|
this.loadDismissedMessages();
|
|
93
101
|
}
|
|
@@ -106,6 +114,10 @@ var _SiteManager = class _SiteManager {
|
|
|
106
114
|
if (this.config.enableAnalytics) {
|
|
107
115
|
this.startAnalyticsFlushing();
|
|
108
116
|
}
|
|
117
|
+
if (this.config.enableVersionCheck && this.config.appVersion) {
|
|
118
|
+
await this.checkVersion();
|
|
119
|
+
this.startVersionChecking();
|
|
120
|
+
}
|
|
109
121
|
this.injectStyles();
|
|
110
122
|
}
|
|
111
123
|
/**
|
|
@@ -113,6 +125,7 @@ var _SiteManager = class _SiteManager {
|
|
|
113
125
|
*/
|
|
114
126
|
destroy() {
|
|
115
127
|
this.stopPolling();
|
|
128
|
+
this.stopVersionChecking();
|
|
116
129
|
this.stopAnalyticsFlushing();
|
|
117
130
|
this.flushAnalytics();
|
|
118
131
|
this.clearMessages();
|
|
@@ -355,6 +368,76 @@ var _SiteManager = class _SiteManager {
|
|
|
355
368
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
356
369
|
}
|
|
357
370
|
// ============================================================================
|
|
371
|
+
// Version Checking
|
|
372
|
+
// ============================================================================
|
|
373
|
+
/**
|
|
374
|
+
* Check if an app update is available
|
|
375
|
+
* @param currentVersion - Optional version to check (defaults to config.appVersion)
|
|
376
|
+
* @returns Version check response with update status
|
|
377
|
+
*/
|
|
378
|
+
async checkVersion(currentVersion) {
|
|
379
|
+
const versionToCheck = currentVersion || this.config.appVersion;
|
|
380
|
+
if (!versionToCheck) {
|
|
381
|
+
console.warn(
|
|
382
|
+
"Featurely Site Manager: appVersion not provided for version check"
|
|
383
|
+
);
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const response = await fetch(
|
|
388
|
+
`${this.config.apiUrl}/api/public/v1/version-check?projectId=${this.config.projectId}¤tVersion=${versionToCheck}`,
|
|
389
|
+
{
|
|
390
|
+
headers: {
|
|
391
|
+
"X-API-Key": this.config.apiKey
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
if (!response.ok) {
|
|
396
|
+
throw new Error(`Version check failed: ${response.statusText}`);
|
|
397
|
+
}
|
|
398
|
+
const versionInfo = await response.json();
|
|
399
|
+
this.lastVersionCheck = versionInfo;
|
|
400
|
+
this.trackEvent("version_checked", {
|
|
401
|
+
currentVersion: versionToCheck,
|
|
402
|
+
updateAvailable: versionInfo.updateAvailable,
|
|
403
|
+
updateRequired: versionInfo.updateRequired,
|
|
404
|
+
updateRecommended: versionInfo.updateRecommended
|
|
405
|
+
});
|
|
406
|
+
if (versionInfo.updateRequired && this.config.onUpdateRequired) {
|
|
407
|
+
this.config.onUpdateRequired(versionInfo);
|
|
408
|
+
} else if (versionInfo.updateAvailable && this.config.onUpdateAvailable) {
|
|
409
|
+
this.config.onUpdateAvailable(versionInfo);
|
|
410
|
+
}
|
|
411
|
+
return versionInfo;
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error("Error checking version:", error);
|
|
414
|
+
if (this.config.onError) {
|
|
415
|
+
this.config.onError(
|
|
416
|
+
error instanceof Error ? error : new Error("Failed to check version")
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get the last version check result (cached)
|
|
424
|
+
*/
|
|
425
|
+
getLastVersionCheck() {
|
|
426
|
+
return this.lastVersionCheck;
|
|
427
|
+
}
|
|
428
|
+
startVersionChecking() {
|
|
429
|
+
if (this.versionCheckIntervalId) return;
|
|
430
|
+
this.versionCheckIntervalId = setInterval(() => {
|
|
431
|
+
this.checkVersion();
|
|
432
|
+
}, this.config.versionCheckInterval);
|
|
433
|
+
}
|
|
434
|
+
stopVersionChecking() {
|
|
435
|
+
if (this.versionCheckIntervalId) {
|
|
436
|
+
clearInterval(this.versionCheckIntervalId);
|
|
437
|
+
this.versionCheckIntervalId = null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// ============================================================================
|
|
358
441
|
// Feature Flags
|
|
359
442
|
// ============================================================================
|
|
360
443
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ var internalErrorTracker = (() => {
|
|
|
7
7
|
apiKey: "ft_live_R4nAn9dDWxk6X3oMzB-tcQh0NrYvA04IhSfwPMUmyaU",
|
|
8
8
|
apiUrl: "https://www.featurely.no",
|
|
9
9
|
environment: "production",
|
|
10
|
-
appVersion: "1.
|
|
10
|
+
appVersion: "1.1.1",
|
|
11
11
|
maxBreadcrumbs: 30,
|
|
12
12
|
enabled: true
|
|
13
13
|
});
|
|
@@ -22,6 +22,7 @@ var _SiteManager = class _SiteManager {
|
|
|
22
22
|
constructor(config) {
|
|
23
23
|
this.siteConfig = null;
|
|
24
24
|
this.pollIntervalId = null;
|
|
25
|
+
this.versionCheckIntervalId = null;
|
|
25
26
|
this.messageContainers = /* @__PURE__ */ new Map();
|
|
26
27
|
this.dismissedMessages = /* @__PURE__ */ new Set();
|
|
27
28
|
this.featureFlagBuckets = /* @__PURE__ */ new Map();
|
|
@@ -30,7 +31,8 @@ var _SiteManager = class _SiteManager {
|
|
|
30
31
|
this.analyticsFlushIntervalId = null;
|
|
31
32
|
this.sessionId = this.generateSessionId();
|
|
32
33
|
this.consecutiveFetchFailures = 0;
|
|
33
|
-
|
|
34
|
+
this.lastVersionCheck = null;
|
|
35
|
+
var _a, _b, _c, _d, _e;
|
|
34
36
|
if (!config.apiKey) {
|
|
35
37
|
throw new Error("Featurely Site Manager: apiKey is required");
|
|
36
38
|
}
|
|
@@ -52,7 +54,13 @@ var _SiteManager = class _SiteManager {
|
|
|
52
54
|
onFeatureFlagsUpdated: config.onFeatureFlagsUpdated,
|
|
53
55
|
onError: config.onError,
|
|
54
56
|
enableAnalytics: (_b = config.enableAnalytics) != null ? _b : true,
|
|
55
|
-
analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4
|
|
57
|
+
analyticsFlushInterval: (_c = config.analyticsFlushInterval) != null ? _c : 6e4,
|
|
58
|
+
appVersion: config.appVersion,
|
|
59
|
+
enableVersionCheck: (_d = config.enableVersionCheck) != null ? _d : false,
|
|
60
|
+
versionCheckInterval: (_e = config.versionCheckInterval) != null ? _e : 36e5,
|
|
61
|
+
// 1 hour
|
|
62
|
+
onUpdateAvailable: config.onUpdateAvailable,
|
|
63
|
+
onUpdateRequired: config.onUpdateRequired
|
|
56
64
|
};
|
|
57
65
|
this.loadDismissedMessages();
|
|
58
66
|
}
|
|
@@ -71,6 +79,10 @@ var _SiteManager = class _SiteManager {
|
|
|
71
79
|
if (this.config.enableAnalytics) {
|
|
72
80
|
this.startAnalyticsFlushing();
|
|
73
81
|
}
|
|
82
|
+
if (this.config.enableVersionCheck && this.config.appVersion) {
|
|
83
|
+
await this.checkVersion();
|
|
84
|
+
this.startVersionChecking();
|
|
85
|
+
}
|
|
74
86
|
this.injectStyles();
|
|
75
87
|
}
|
|
76
88
|
/**
|
|
@@ -78,6 +90,7 @@ var _SiteManager = class _SiteManager {
|
|
|
78
90
|
*/
|
|
79
91
|
destroy() {
|
|
80
92
|
this.stopPolling();
|
|
93
|
+
this.stopVersionChecking();
|
|
81
94
|
this.stopAnalyticsFlushing();
|
|
82
95
|
this.flushAnalytics();
|
|
83
96
|
this.clearMessages();
|
|
@@ -320,6 +333,76 @@ var _SiteManager = class _SiteManager {
|
|
|
320
333
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
321
334
|
}
|
|
322
335
|
// ============================================================================
|
|
336
|
+
// Version Checking
|
|
337
|
+
// ============================================================================
|
|
338
|
+
/**
|
|
339
|
+
* Check if an app update is available
|
|
340
|
+
* @param currentVersion - Optional version to check (defaults to config.appVersion)
|
|
341
|
+
* @returns Version check response with update status
|
|
342
|
+
*/
|
|
343
|
+
async checkVersion(currentVersion) {
|
|
344
|
+
const versionToCheck = currentVersion || this.config.appVersion;
|
|
345
|
+
if (!versionToCheck) {
|
|
346
|
+
console.warn(
|
|
347
|
+
"Featurely Site Manager: appVersion not provided for version check"
|
|
348
|
+
);
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const response = await fetch(
|
|
353
|
+
`${this.config.apiUrl}/api/public/v1/version-check?projectId=${this.config.projectId}¤tVersion=${versionToCheck}`,
|
|
354
|
+
{
|
|
355
|
+
headers: {
|
|
356
|
+
"X-API-Key": this.config.apiKey
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
throw new Error(`Version check failed: ${response.statusText}`);
|
|
362
|
+
}
|
|
363
|
+
const versionInfo = await response.json();
|
|
364
|
+
this.lastVersionCheck = versionInfo;
|
|
365
|
+
this.trackEvent("version_checked", {
|
|
366
|
+
currentVersion: versionToCheck,
|
|
367
|
+
updateAvailable: versionInfo.updateAvailable,
|
|
368
|
+
updateRequired: versionInfo.updateRequired,
|
|
369
|
+
updateRecommended: versionInfo.updateRecommended
|
|
370
|
+
});
|
|
371
|
+
if (versionInfo.updateRequired && this.config.onUpdateRequired) {
|
|
372
|
+
this.config.onUpdateRequired(versionInfo);
|
|
373
|
+
} else if (versionInfo.updateAvailable && this.config.onUpdateAvailable) {
|
|
374
|
+
this.config.onUpdateAvailable(versionInfo);
|
|
375
|
+
}
|
|
376
|
+
return versionInfo;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error("Error checking version:", error);
|
|
379
|
+
if (this.config.onError) {
|
|
380
|
+
this.config.onError(
|
|
381
|
+
error instanceof Error ? error : new Error("Failed to check version")
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get the last version check result (cached)
|
|
389
|
+
*/
|
|
390
|
+
getLastVersionCheck() {
|
|
391
|
+
return this.lastVersionCheck;
|
|
392
|
+
}
|
|
393
|
+
startVersionChecking() {
|
|
394
|
+
if (this.versionCheckIntervalId) return;
|
|
395
|
+
this.versionCheckIntervalId = setInterval(() => {
|
|
396
|
+
this.checkVersion();
|
|
397
|
+
}, this.config.versionCheckInterval);
|
|
398
|
+
}
|
|
399
|
+
stopVersionChecking() {
|
|
400
|
+
if (this.versionCheckIntervalId) {
|
|
401
|
+
clearInterval(this.versionCheckIntervalId);
|
|
402
|
+
this.versionCheckIntervalId = null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// ============================================================================
|
|
323
406
|
// Feature Flags
|
|
324
407
|
// ============================================================================
|
|
325
408
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "featurely-site-manager",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.1",
|
|
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,8 +29,13 @@
|
|
|
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",
|