helm-analytics 1.0.0 → 5.0.0
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 +25 -2
- package/index.js +85 -13
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ const HelmAnalytics = require('helm-analytics');
|
|
|
17
17
|
const app = express();
|
|
18
18
|
const helm = new HelmAnalytics({ siteId: 'YOUR_SITE_ID' });
|
|
19
19
|
|
|
20
|
-
// Use as middleware
|
|
21
|
-
app.use(helm.middleware());
|
|
20
|
+
// Use as middleware (with optional Shield filtering)
|
|
21
|
+
app.use(helm.middleware({ shield: true })); // Set to true to block requests from bad actors
|
|
22
22
|
|
|
23
23
|
app.get('/', (req, res) => {
|
|
24
24
|
res.send('Hello World');
|
|
@@ -36,3 +36,26 @@ app.post('/purchase', (req, res) => {
|
|
|
36
36
|
res.json({ success: true });
|
|
37
37
|
});
|
|
38
38
|
```
|
|
39
|
+
|
|
40
|
+
## Custom Event Tracking (NEW v5)
|
|
41
|
+
|
|
42
|
+
Track specific business events with custom properties:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
app.post('/signup', (req, res) => {
|
|
46
|
+
// ... logic ...
|
|
47
|
+
helm.trackEvent(req, 'signup_success', { plan: 'growth', method: 'google' });
|
|
48
|
+
res.json({ status: 'ok' });
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Session Stitching
|
|
53
|
+
|
|
54
|
+
To link server-side events back to the browser session, ensure you pass the `sessionId` (retrieved from `sessionStorage.getItem('helm_session_id')`) in the `X-Helm-Session-Id` header of your frontend API calls.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Non-blocking**: Uses non-blocking fetch calls for event ingestion.
|
|
59
|
+
- **Shield Mode**: Active defense against bots and malicious traffic.
|
|
60
|
+
- **Custom Events**: Track business milestones beyond pageviews.
|
|
61
|
+
- **Session Stitching**: Maintain user journey continuity via headers.
|
package/index.js
CHANGED
|
@@ -3,20 +3,52 @@ const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch
|
|
|
3
3
|
class HelmAnalytics {
|
|
4
4
|
constructor(options = {}) {
|
|
5
5
|
this.siteId = options.siteId || process.env.HELM_SITE_ID;
|
|
6
|
-
this.apiUrl = options.apiUrl || 'https://api-sentinel.getmusterup.com
|
|
6
|
+
this.apiUrl = (options.apiUrl || 'https://api-sentinel.getmusterup.com').replace(/\/$/, '').replace(/\/track$/, '');
|
|
7
7
|
|
|
8
8
|
if (!this.siteId) {
|
|
9
9
|
console.warn('HelmAnalytics: No Site ID provided. Tracking will be disabled.');
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// Check if request should be blocked
|
|
14
|
+
async checkShield(payload) {
|
|
15
|
+
if (!this.siteId) return { allowed: true };
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const checkPayload = {
|
|
19
|
+
siteId: payload.siteId,
|
|
20
|
+
ip: payload.clientIp,
|
|
21
|
+
userAgent: payload.userAgent,
|
|
22
|
+
url: payload.url
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const res = await fetch(`${this.apiUrl}/api/shield/decision`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
body: JSON.stringify(checkPayload),
|
|
28
|
+
headers: { 'Content-Type': 'application/json' }
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
const decision = await res.json();
|
|
33
|
+
if (decision.action === 'block') {
|
|
34
|
+
return { allowed: false, reason: decision.reason };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// Fail open
|
|
39
|
+
console.error('Helm Shield Error:', err);
|
|
40
|
+
}
|
|
41
|
+
return { allowed: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
// Generic track method
|
|
14
|
-
async track(req, eventType = 'pageview', metadata = {}) {
|
|
15
|
-
if (!this.siteId) return;
|
|
45
|
+
async track(req, eventType = 'pageview', metadata = {}, shield = false) {
|
|
46
|
+
if (!this.siteId) return true;
|
|
16
47
|
|
|
17
48
|
try {
|
|
18
49
|
const payload = {
|
|
19
50
|
siteId: this.siteId,
|
|
51
|
+
sessionId: req.headers['x-helm-session-id'] || req.sessionId || '', // Support session stitching
|
|
20
52
|
url: req.originalUrl || req.url,
|
|
21
53
|
userAgent: req.headers['user-agent'] || '',
|
|
22
54
|
referrer: req.headers['referer'] || req.headers['referrer'] || '',
|
|
@@ -27,25 +59,65 @@ class HelmAnalytics {
|
|
|
27
59
|
...metadata
|
|
28
60
|
};
|
|
29
61
|
|
|
30
|
-
//
|
|
31
|
-
|
|
62
|
+
// Shield Checking (Blocking)
|
|
63
|
+
if (shield) {
|
|
64
|
+
const { allowed, reason } = await this.checkShield(payload);
|
|
65
|
+
if (!allowed) {
|
|
66
|
+
console.warn(`[Helm Shield] Blocked IP: ${payload.clientIp} Reason: ${reason}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fire and forget
|
|
72
|
+
fetch(`${this.apiUrl}/track`, {
|
|
32
73
|
method: 'POST',
|
|
33
74
|
body: JSON.stringify(payload),
|
|
34
75
|
headers: { 'Content-Type': 'application/json' }
|
|
35
|
-
}).catch(err => {
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
}).catch(err => {});
|
|
77
|
+
|
|
78
|
+
return true;
|
|
38
79
|
|
|
39
80
|
} catch (err) {
|
|
40
|
-
//
|
|
81
|
+
return true; // Fail open
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Custom event tracking
|
|
86
|
+
async trackEvent(req, eventName, properties = {}) {
|
|
87
|
+
if (!this.siteId) return true;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const payload = {
|
|
91
|
+
siteId: this.siteId,
|
|
92
|
+
sessionId: req.headers['x-helm-session-id'] || req.sessionId || '',
|
|
93
|
+
eventName: eventName,
|
|
94
|
+
properties: properties,
|
|
95
|
+
url: req.originalUrl || req.url,
|
|
96
|
+
referrer: req.headers['referer'] || req.headers['referrer'] || '',
|
|
97
|
+
isServerSide: true
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
fetch(`${this.apiUrl}/track/event`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
body: JSON.stringify(payload),
|
|
103
|
+
headers: { 'Content-Type': 'application/json' }
|
|
104
|
+
}).catch(err => {});
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return true;
|
|
41
109
|
}
|
|
42
110
|
}
|
|
43
111
|
|
|
44
112
|
// Express Middleware
|
|
45
|
-
middleware() {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
113
|
+
middleware(options = {}) {
|
|
114
|
+
const shield = options.shield || false;
|
|
115
|
+
|
|
116
|
+
return async (req, res, next) => {
|
|
117
|
+
const allowed = await this.track(req, 'pageview', {}, shield);
|
|
118
|
+
if (!allowed) {
|
|
119
|
+
return res.status(403).send('Forbidden by Helm Aegis');
|
|
120
|
+
}
|
|
49
121
|
next();
|
|
50
122
|
};
|
|
51
123
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helm-analytics",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Official Node.js
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Official Node.js SDK for Helm Analytics",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/
|
|
25
|
+
"url": "https://github.com/Helm-Analytics/sentinel-mvp/tree/main/sdk/node"
|
|
26
26
|
}
|
|
27
27
|
}
|