featurely-site-manager 1.1.4 → 1.1.6
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/README.md +37 -48
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +79 -0
- package/dist/index.mjs +79 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,9 +63,11 @@ siteManager.init();
|
|
|
63
63
|
|
|
64
64
|
### 📊 Analytics
|
|
65
65
|
|
|
66
|
+
- Automatic page view tracking (SPA-compatible)
|
|
67
|
+
- Session start and end tracking
|
|
68
|
+
- User login event tracking
|
|
69
|
+
- Page exit tracking with time-on-page duration
|
|
66
70
|
- Track custom events
|
|
67
|
-
- Automatic feature flag usage tracking
|
|
68
|
-
- Session management
|
|
69
71
|
- User identification
|
|
70
72
|
|
|
71
73
|
## 📖 Usage
|
|
@@ -216,6 +218,7 @@ const manager = new SiteManager({
|
|
|
216
218
|
});
|
|
217
219
|
|
|
218
220
|
manager.init();
|
|
221
|
+
// Automatically tracks: session_start, page_view, page_exit on every navigation
|
|
219
222
|
|
|
220
223
|
// Track custom events
|
|
221
224
|
manager.trackEvent("button_clicked", {
|
|
@@ -223,12 +226,25 @@ manager.trackEvent("button_clicked", {
|
|
|
223
226
|
page: "/home",
|
|
224
227
|
});
|
|
225
228
|
|
|
226
|
-
manager.trackEvent("
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
manager.trackEvent("purchase_completed", {
|
|
230
|
+
amount: 99.99,
|
|
231
|
+
plan: "pro",
|
|
229
232
|
});
|
|
230
233
|
```
|
|
231
234
|
|
|
235
|
+
#### Automatic Events
|
|
236
|
+
|
|
237
|
+
When `enableAnalytics` is `true` (default), the SDK automatically tracks:
|
|
238
|
+
|
|
239
|
+
| Event | When | Properties |
|
|
240
|
+
| --- | --- | --- |
|
|
241
|
+
| `session_start` | On first page load | `path`, `title`, `referrer` |
|
|
242
|
+
| `page_view` | On every page navigation | `path`, `title`, `referrer`, `isLoggedIn`, `userId?`, `userEmail?` |
|
|
243
|
+
| `page_exit` | When navigating away from a page | `path`, `durationSeconds` |
|
|
244
|
+
| `user_login` | First time `setUser()` is called with a userId | `userId`, `userEmail?`, `userName?` |
|
|
245
|
+
|
|
246
|
+
Page tracking is SPA-compatible — it patches `history.pushState` and `history.replaceState` and listens to `popstate` so navigations in React, Next.js, Vue, and similar frameworks are captured automatically.
|
|
247
|
+
|
|
232
248
|
### Custom Poll Interval
|
|
233
249
|
|
|
234
250
|
```typescript
|
|
@@ -280,12 +296,12 @@ Initialize and start the site manager.
|
|
|
280
296
|
await manager.init();
|
|
281
297
|
```
|
|
282
298
|
|
|
283
|
-
#### `setUser(email: string, userId?: string)`
|
|
299
|
+
#### `setUser(email: string, userId?: string, userName?: string)`
|
|
284
300
|
|
|
285
|
-
Update user
|
|
301
|
+
Update user context for whitelist, targeting, and analytics. If `userId` is provided for the first time, a `user_login` analytics event is fired automatically.
|
|
286
302
|
|
|
287
303
|
```typescript
|
|
288
|
-
manager.setUser("user@example.com", "user_123");
|
|
304
|
+
manager.setUser("user@example.com", "user_123", "Jane Doe");
|
|
289
305
|
```
|
|
290
306
|
|
|
291
307
|
#### `refresh()`
|
|
@@ -464,56 +480,29 @@ const manager = new SiteManager({
|
|
|
464
480
|
manager.init();
|
|
465
481
|
```
|
|
466
482
|
|
|
467
|
-
##
|
|
483
|
+
## 🔒 Content Security Policy (CSP)
|
|
468
484
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
| Option | Type | Required | Default | Description |
|
|
472
|
-
| ----------------------- | --------------- | -------- | ------------------------- | ----------------------------- |
|
|
473
|
-
| `apiKey` | `string` | ✅ | - | Your Featurely API key |
|
|
474
|
-
| `projectId` | `string` | ✅ | - | Your Featurely project ID |
|
|
475
|
-
| `apiUrl` | `string` | ❌ | `'https://featurely.no'` | Custom API endpoint |
|
|
476
|
-
| `pollInterval` | `number` | ❌ | `60000` | Polling interval in ms |
|
|
477
|
-
| `userEmail` | `string` | ❌ | - | User email for whitelist |
|
|
478
|
-
| `bypassCheck` | `() => boolean` | ❌ | - | Custom bypass function |
|
|
479
|
-
| `onMaintenanceEnabled` | `function` | ❌ | - | Maintenance enabled callback |
|
|
480
|
-
| `onMaintenanceDisabled` | `function` | ❌ | - | Maintenance disabled callback |
|
|
481
|
-
| `onMessageReceived` | `function` | ❌ | - | Message received callback |
|
|
482
|
-
| `onMessageDismissed` | `function` | ❌ | - | Message dismissed callback |
|
|
483
|
-
| `onError` | `function` | ❌ | - | Error callback |
|
|
484
|
-
|
|
485
|
-
## 🎯 API Methods
|
|
485
|
+
If your site uses a `Content-Security-Policy` header, you must allow connections to `https://www.featurely.no`. Without this, all API calls (config polling, analytics, feature flags) will be silently blocked.
|
|
486
486
|
|
|
487
|
-
|
|
487
|
+
Add the following to your `connect-src` directive:
|
|
488
488
|
|
|
489
|
-
Initialize and start the site manager.
|
|
490
|
-
|
|
491
|
-
```typescript
|
|
492
|
-
await manager.init();
|
|
493
489
|
```
|
|
494
|
-
|
|
495
|
-
### `setUser(email: string)`
|
|
496
|
-
|
|
497
|
-
Update user email for whitelist checks.
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
manager.setUser("user@example.com");
|
|
490
|
+
connect-src 'self' https://www.featurely.no;
|
|
501
491
|
```
|
|
502
492
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
Manually refresh configuration from server.
|
|
493
|
+
**Next.js example** (`next.config.js`):
|
|
506
494
|
|
|
507
|
-
```
|
|
508
|
-
|
|
495
|
+
```javascript
|
|
496
|
+
const cspHeader = `
|
|
497
|
+
connect-src 'self' https://www.featurely.no;
|
|
498
|
+
`.trim();
|
|
509
499
|
```
|
|
510
500
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
Stop the manager and clean up.
|
|
501
|
+
If the SDK is being blocked by CSP, you will see a message like this in the browser console:
|
|
514
502
|
|
|
515
|
-
```
|
|
516
|
-
|
|
503
|
+
```
|
|
504
|
+
[SiteManager] ⚠️ Connection to Featurely was blocked — check your Content-Security-Policy.
|
|
505
|
+
Add "https://www.featurely.no" to your connect-src directive.
|
|
517
506
|
```
|
|
518
507
|
|
|
519
508
|
## 🎨 Maintenance Mode
|
package/dist/index.d.mts
CHANGED
|
@@ -103,6 +103,9 @@ declare class SiteManager {
|
|
|
103
103
|
private analyticsFlushIntervalId;
|
|
104
104
|
private sessionId;
|
|
105
105
|
private consecutiveFetchFailures;
|
|
106
|
+
private pageTrackingSetup;
|
|
107
|
+
private currentPagePath;
|
|
108
|
+
private pageEntryTime;
|
|
106
109
|
private static readonly MAX_CONSECUTIVE_FAILURES;
|
|
107
110
|
private lastVersionCheck;
|
|
108
111
|
constructor(config: SiteManagerConfig);
|
|
@@ -123,6 +126,9 @@ declare class SiteManager {
|
|
|
123
126
|
private startAnalyticsFlushing;
|
|
124
127
|
private stopAnalyticsFlushing;
|
|
125
128
|
private flushAnalytics;
|
|
129
|
+
private setupPageTracking;
|
|
130
|
+
private trackPageView;
|
|
131
|
+
private onNavigate;
|
|
126
132
|
private generateSessionId;
|
|
127
133
|
checkVersion(currentVersion?: string): Promise<VersionCheckResponse | null>;
|
|
128
134
|
getLastVersionCheck(): VersionCheckResponse | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,9 @@ declare class SiteManager {
|
|
|
103
103
|
private analyticsFlushIntervalId;
|
|
104
104
|
private sessionId;
|
|
105
105
|
private consecutiveFetchFailures;
|
|
106
|
+
private pageTrackingSetup;
|
|
107
|
+
private currentPagePath;
|
|
108
|
+
private pageEntryTime;
|
|
106
109
|
private static readonly MAX_CONSECUTIVE_FAILURES;
|
|
107
110
|
private lastVersionCheck;
|
|
108
111
|
constructor(config: SiteManagerConfig);
|
|
@@ -123,6 +126,9 @@ declare class SiteManager {
|
|
|
123
126
|
private startAnalyticsFlushing;
|
|
124
127
|
private stopAnalyticsFlushing;
|
|
125
128
|
private flushAnalytics;
|
|
129
|
+
private setupPageTracking;
|
|
130
|
+
private trackPageView;
|
|
131
|
+
private onNavigate;
|
|
126
132
|
private generateSessionId;
|
|
127
133
|
checkVersion(currentVersion?: string): Promise<VersionCheckResponse | null>;
|
|
128
134
|
getLastVersionCheck(): VersionCheckResponse | null;
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,9 @@ var _SiteManager = class _SiteManager {
|
|
|
48
48
|
this.analyticsFlushIntervalId = null;
|
|
49
49
|
this.sessionId = this.generateSessionId();
|
|
50
50
|
this.consecutiveFetchFailures = 0;
|
|
51
|
+
this.pageTrackingSetup = false;
|
|
52
|
+
this.currentPagePath = "";
|
|
53
|
+
this.pageEntryTime = 0;
|
|
51
54
|
this.lastVersionCheck = null;
|
|
52
55
|
var _a, _b, _c, _d, _e;
|
|
53
56
|
if (!config.apiKey) {
|
|
@@ -95,6 +98,7 @@ var _SiteManager = class _SiteManager {
|
|
|
95
98
|
this.startPolling();
|
|
96
99
|
if (this.config.enableAnalytics) {
|
|
97
100
|
this.startAnalyticsFlushing();
|
|
101
|
+
this.setupPageTracking();
|
|
98
102
|
}
|
|
99
103
|
if (this.config.enableVersionCheck && this.config.appVersion) {
|
|
100
104
|
await this.checkVersion();
|
|
@@ -117,10 +121,17 @@ var _SiteManager = class _SiteManager {
|
|
|
117
121
|
*/
|
|
118
122
|
setUser(email, userId) {
|
|
119
123
|
var _a;
|
|
124
|
+
const wasLoggedIn = !!this.config.userId;
|
|
120
125
|
this.config.userEmail = email;
|
|
121
126
|
if (userId) {
|
|
122
127
|
this.config.userId = userId;
|
|
123
128
|
}
|
|
129
|
+
if (!wasLoggedIn && this.config.userId && this.config.enableAnalytics) {
|
|
130
|
+
this.trackEvent("user_login", {
|
|
131
|
+
userId: this.config.userId,
|
|
132
|
+
...this.config.userEmail ? { userEmail: this.config.userEmail } : {}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
124
135
|
if ((_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled) {
|
|
125
136
|
this.checkMaintenanceMode();
|
|
126
137
|
}
|
|
@@ -399,6 +410,74 @@ var _SiteManager = class _SiteManager {
|
|
|
399
410
|
}
|
|
400
411
|
}
|
|
401
412
|
}
|
|
413
|
+
setupPageTracking() {
|
|
414
|
+
if (this.pageTrackingSetup) return;
|
|
415
|
+
this.pageTrackingSetup = true;
|
|
416
|
+
this.trackEvent("session_start", {
|
|
417
|
+
referrer: document.referrer || "",
|
|
418
|
+
language: navigator.language,
|
|
419
|
+
screenWidth: window.screen.width,
|
|
420
|
+
screenHeight: window.screen.height,
|
|
421
|
+
isLoggedIn: !!this.config.userId
|
|
422
|
+
});
|
|
423
|
+
this.currentPagePath = window.location.pathname;
|
|
424
|
+
this.pageEntryTime = Date.now();
|
|
425
|
+
this.trackPageView();
|
|
426
|
+
const originalPushState = history.pushState.bind(history);
|
|
427
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
428
|
+
const self = this;
|
|
429
|
+
history.pushState = function(...args) {
|
|
430
|
+
originalPushState(...args);
|
|
431
|
+
self.onNavigate();
|
|
432
|
+
};
|
|
433
|
+
history.replaceState = function(...args) {
|
|
434
|
+
originalReplaceState(...args);
|
|
435
|
+
if (window.location.pathname !== self.currentPagePath) {
|
|
436
|
+
self.onNavigate();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
window.addEventListener("popstate", () => this.onNavigate());
|
|
440
|
+
window.addEventListener("visibilitychange", () => {
|
|
441
|
+
if (document.visibilityState === "hidden") {
|
|
442
|
+
const duration = Math.round((Date.now() - this.pageEntryTime) / 1e3);
|
|
443
|
+
if (duration > 0) {
|
|
444
|
+
this.trackEvent("page_exit", {
|
|
445
|
+
path: this.currentPagePath,
|
|
446
|
+
durationSeconds: duration
|
|
447
|
+
});
|
|
448
|
+
this.flushAnalytics();
|
|
449
|
+
}
|
|
450
|
+
} else if (document.visibilityState === "visible") {
|
|
451
|
+
this.pageEntryTime = Date.now();
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
trackPageView() {
|
|
456
|
+
const props = {
|
|
457
|
+
path: this.currentPagePath,
|
|
458
|
+
title: document.title,
|
|
459
|
+
referrer: document.referrer || "",
|
|
460
|
+
isLoggedIn: !!this.config.userId
|
|
461
|
+
};
|
|
462
|
+
if (this.config.userId) props.userId = this.config.userId;
|
|
463
|
+
if (this.config.userEmail) props.userEmail = this.config.userEmail;
|
|
464
|
+
this.trackEvent("page_view", props);
|
|
465
|
+
}
|
|
466
|
+
onNavigate() {
|
|
467
|
+
const newPath = window.location.pathname;
|
|
468
|
+
if (this.currentPagePath && this.pageEntryTime > 0) {
|
|
469
|
+
const duration = Math.round((Date.now() - this.pageEntryTime) / 1e3);
|
|
470
|
+
if (duration > 0) {
|
|
471
|
+
this.trackEvent("page_exit", {
|
|
472
|
+
path: this.currentPagePath,
|
|
473
|
+
durationSeconds: duration
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
this.currentPagePath = newPath;
|
|
478
|
+
this.pageEntryTime = Date.now();
|
|
479
|
+
setTimeout(() => this.trackPageView(), 100);
|
|
480
|
+
}
|
|
402
481
|
generateSessionId() {
|
|
403
482
|
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
404
483
|
const array = new Uint8Array(16);
|
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,9 @@ var _SiteManager = class _SiteManager {
|
|
|
13
13
|
this.analyticsFlushIntervalId = null;
|
|
14
14
|
this.sessionId = this.generateSessionId();
|
|
15
15
|
this.consecutiveFetchFailures = 0;
|
|
16
|
+
this.pageTrackingSetup = false;
|
|
17
|
+
this.currentPagePath = "";
|
|
18
|
+
this.pageEntryTime = 0;
|
|
16
19
|
this.lastVersionCheck = null;
|
|
17
20
|
var _a, _b, _c, _d, _e;
|
|
18
21
|
if (!config.apiKey) {
|
|
@@ -60,6 +63,7 @@ var _SiteManager = class _SiteManager {
|
|
|
60
63
|
this.startPolling();
|
|
61
64
|
if (this.config.enableAnalytics) {
|
|
62
65
|
this.startAnalyticsFlushing();
|
|
66
|
+
this.setupPageTracking();
|
|
63
67
|
}
|
|
64
68
|
if (this.config.enableVersionCheck && this.config.appVersion) {
|
|
65
69
|
await this.checkVersion();
|
|
@@ -82,10 +86,17 @@ var _SiteManager = class _SiteManager {
|
|
|
82
86
|
*/
|
|
83
87
|
setUser(email, userId) {
|
|
84
88
|
var _a;
|
|
89
|
+
const wasLoggedIn = !!this.config.userId;
|
|
85
90
|
this.config.userEmail = email;
|
|
86
91
|
if (userId) {
|
|
87
92
|
this.config.userId = userId;
|
|
88
93
|
}
|
|
94
|
+
if (!wasLoggedIn && this.config.userId && this.config.enableAnalytics) {
|
|
95
|
+
this.trackEvent("user_login", {
|
|
96
|
+
userId: this.config.userId,
|
|
97
|
+
...this.config.userEmail ? { userEmail: this.config.userEmail } : {}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
89
100
|
if ((_a = this.siteConfig) == null ? void 0 : _a.maintenance.enabled) {
|
|
90
101
|
this.checkMaintenanceMode();
|
|
91
102
|
}
|
|
@@ -364,6 +375,74 @@ var _SiteManager = class _SiteManager {
|
|
|
364
375
|
}
|
|
365
376
|
}
|
|
366
377
|
}
|
|
378
|
+
setupPageTracking() {
|
|
379
|
+
if (this.pageTrackingSetup) return;
|
|
380
|
+
this.pageTrackingSetup = true;
|
|
381
|
+
this.trackEvent("session_start", {
|
|
382
|
+
referrer: document.referrer || "",
|
|
383
|
+
language: navigator.language,
|
|
384
|
+
screenWidth: window.screen.width,
|
|
385
|
+
screenHeight: window.screen.height,
|
|
386
|
+
isLoggedIn: !!this.config.userId
|
|
387
|
+
});
|
|
388
|
+
this.currentPagePath = window.location.pathname;
|
|
389
|
+
this.pageEntryTime = Date.now();
|
|
390
|
+
this.trackPageView();
|
|
391
|
+
const originalPushState = history.pushState.bind(history);
|
|
392
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
393
|
+
const self = this;
|
|
394
|
+
history.pushState = function(...args) {
|
|
395
|
+
originalPushState(...args);
|
|
396
|
+
self.onNavigate();
|
|
397
|
+
};
|
|
398
|
+
history.replaceState = function(...args) {
|
|
399
|
+
originalReplaceState(...args);
|
|
400
|
+
if (window.location.pathname !== self.currentPagePath) {
|
|
401
|
+
self.onNavigate();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
window.addEventListener("popstate", () => this.onNavigate());
|
|
405
|
+
window.addEventListener("visibilitychange", () => {
|
|
406
|
+
if (document.visibilityState === "hidden") {
|
|
407
|
+
const duration = Math.round((Date.now() - this.pageEntryTime) / 1e3);
|
|
408
|
+
if (duration > 0) {
|
|
409
|
+
this.trackEvent("page_exit", {
|
|
410
|
+
path: this.currentPagePath,
|
|
411
|
+
durationSeconds: duration
|
|
412
|
+
});
|
|
413
|
+
this.flushAnalytics();
|
|
414
|
+
}
|
|
415
|
+
} else if (document.visibilityState === "visible") {
|
|
416
|
+
this.pageEntryTime = Date.now();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
trackPageView() {
|
|
421
|
+
const props = {
|
|
422
|
+
path: this.currentPagePath,
|
|
423
|
+
title: document.title,
|
|
424
|
+
referrer: document.referrer || "",
|
|
425
|
+
isLoggedIn: !!this.config.userId
|
|
426
|
+
};
|
|
427
|
+
if (this.config.userId) props.userId = this.config.userId;
|
|
428
|
+
if (this.config.userEmail) props.userEmail = this.config.userEmail;
|
|
429
|
+
this.trackEvent("page_view", props);
|
|
430
|
+
}
|
|
431
|
+
onNavigate() {
|
|
432
|
+
const newPath = window.location.pathname;
|
|
433
|
+
if (this.currentPagePath && this.pageEntryTime > 0) {
|
|
434
|
+
const duration = Math.round((Date.now() - this.pageEntryTime) / 1e3);
|
|
435
|
+
if (duration > 0) {
|
|
436
|
+
this.trackEvent("page_exit", {
|
|
437
|
+
path: this.currentPagePath,
|
|
438
|
+
durationSeconds: duration
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
this.currentPagePath = newPath;
|
|
443
|
+
this.pageEntryTime = Date.now();
|
|
444
|
+
setTimeout(() => this.trackPageView(), 100);
|
|
445
|
+
}
|
|
367
446
|
generateSessionId() {
|
|
368
447
|
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
369
448
|
const array = new Uint8Array(16);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "featurely-site-manager",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Complete site management SDK for maintenance mode, status messages, feature flags, version checking, and analytics",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|