@stacksjs/analytics 0.71.1 → 0.71.10
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/dist/capture.d.ts +12 -0
- package/dist/capture.js +1 -0
- package/dist/drivers/fathom.d.ts +1 -0
- package/dist/drivers/fathom.js +1 -0
- package/dist/drivers/index.d.ts +2 -0
- package/dist/drivers/index.js +1 -0
- package/dist/drivers/self-hosted.d.ts +31 -0
- package/dist/drivers/self-hosted.js +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -57
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Should this request count as a pageview? Pure, testable. */
|
|
2
|
+
export declare function isPageviewRequest(method: string, pathname: string, headers: Headers): boolean;
|
|
3
|
+
/** Coarse classification - enough for the devices/browsers dashboards, nothing identifying. */
|
|
4
|
+
export declare function classifyAgent(userAgent: string): { device: string, browser: string };
|
|
5
|
+
/** The referrer's host, or null for direct/self traffic. */
|
|
6
|
+
export declare function referrerHost(referrer: string | null, ownHost: string): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Record one pageview. Fire-and-forget from the serving hot path - callers
|
|
9
|
+
* do `void recordPageview(...)`; a failed insert loses one statistic, never
|
|
10
|
+
* a page render.
|
|
11
|
+
*/
|
|
12
|
+
export declare function recordPageview(req: Request): Promise<void>;
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{db,sqlDateTime}from"@stacksjs/database";const ASSET_EXT=/\.(?:css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|txt|xml|json|webmanifest|mp[34]|webm|pdf|zip)$/i;export function isPageviewRequest(method,pathname,headers){if(method!=="GET")return!1;if(ASSET_EXT.test(pathname))return!1;if(pathname.startsWith("/api/")||pathname.startsWith("/__")||pathname.startsWith("/_"))return!1;const accept=headers.get("accept")??"";if(accept&&!accept.includes("text/html")&&!accept.includes("*/*"))return!1;const agent=(headers.get("user-agent")??"").toLowerCase();if(!agent)return!1;if(/bot|crawler|spider|preview|monitor|curl|wget|python-requests|headless/.test(agent))return!1;return!0}export function classifyAgent(userAgent){const agent=userAgent.toLowerCase(),device=/ipad|tablet/.test(agent)?"tablet":/mobi|iphone|android/.test(agent)?"mobile":"desktop",browser=agent.includes("edg/")?"edge":agent.includes("opr/")||agent.includes("opera")?"opera":agent.includes("firefox/")?"firefox":agent.includes("chrome/")||agent.includes("crios/")?"chrome":agent.includes("safari/")?"safari":"other";return{device,browser}}export function referrerHost(referrer,ownHost){if(!referrer)return null;try{const host=new URL(referrer).host.toLowerCase();return host&&host!==ownHost.toLowerCase()?host:null}catch{return null}}export async function recordPageview(req){try{const url=new URL(req.url);if(!isPageviewRequest(req.method,url.pathname,req.headers))return;const agent=req.headers.get("user-agent")??"",{device,browser}=classifyAgent(agent),now=sqlDateTime(new Date);await db.insertInto("analytics_events").values({name:"pageview",category:"web",path:url.pathname,properties:JSON.stringify({host:req.headers.get("x-forwarded-host")?.split(",")[0]?.trim()||url.host,referrer:referrerHost(req.headers.get("referer"),url.host),device,browser}),created_at:now,updated_at:now}).execute()}catch{}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fathomWip: 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const fathomWip=1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./fathom";export*from"./self-hosted";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the self-hosted analytics tracking script
|
|
3
|
+
*/
|
|
4
|
+
export declare function generateSelfHostedScript(config: SelfHostedConfig): string;
|
|
5
|
+
/**
|
|
6
|
+
* Generate the head tag for including self-hosted analytics
|
|
7
|
+
* Compatible with Stacks docs config.ts head array format
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSelfHostedAnalyticsHead(config: SelfHostedConfig): [string, Record<string, string>][];
|
|
10
|
+
/**
|
|
11
|
+
* Self-Hosted Analytics Driver
|
|
12
|
+
*
|
|
13
|
+
* This driver generates a minimal, privacy-focused tracking script
|
|
14
|
+
* that sends analytics data to your own API endpoint (powered by dynamodb-tooling analytics).
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - No cookies required
|
|
18
|
+
* - Privacy-focused (hashed visitor IDs)
|
|
19
|
+
* - Do Not Track support
|
|
20
|
+
* - Page view tracking
|
|
21
|
+
* - Custom event tracking
|
|
22
|
+
* - Outbound link tracking (optional)
|
|
23
|
+
* - Hash-based routing support (optional)
|
|
24
|
+
*/
|
|
25
|
+
export declare interface SelfHostedConfig {
|
|
26
|
+
siteId: string
|
|
27
|
+
apiEndpoint: string
|
|
28
|
+
honorDnt?: boolean
|
|
29
|
+
trackHashChanges?: boolean
|
|
30
|
+
trackOutboundLinks?: boolean
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function generateSelfHostedScript(config){if(!config.siteId||!config.apiEndpoint)return"";const siteId=escapeAttr(config.siteId),apiEndpoint=escapeAttr(config.apiEndpoint),honorDnt=config.honorDnt?'if(n.doNotTrack==="1")return;':"",hashTracking=config.trackHashChanges?"w.addEventListener('hashchange',pv);":"",outboundTracking=config.trackOutboundLinks?`
|
|
2
|
+
d.addEventListener('click',function(e){
|
|
3
|
+
var a=e.target.closest('a');
|
|
4
|
+
if(a&&a.hostname!==location.hostname){
|
|
5
|
+
t('outbound',{url:a.href});
|
|
6
|
+
}
|
|
7
|
+
});`:"";return`<!-- Stacks Self-Hosted Analytics -->
|
|
8
|
+
<script data-site="${siteId}" data-api="${apiEndpoint}" defer>
|
|
9
|
+
(function(){
|
|
10
|
+
'use strict';
|
|
11
|
+
var d=document,w=window,n=navigator,s=d.currentScript;
|
|
12
|
+
var site=s.dataset.site,api=s.dataset.api;
|
|
13
|
+
${honorDnt}
|
|
14
|
+
var q=[],sid=Math.random().toString(36).slice(2);
|
|
15
|
+
function t(e,p){
|
|
16
|
+
var x=new XMLHttpRequest();
|
|
17
|
+
x.open('POST',api+'/collect',true);
|
|
18
|
+
x.setRequestHeader('Content-Type','application/json');
|
|
19
|
+
// Strip the URL's query string before sending. location.href includes
|
|
20
|
+
// tokens / session IDs / search terms that shouldn't reach the
|
|
21
|
+
// analytics backend. Same goes for document.referrer when it points
|
|
22
|
+
// back to our own domain (path is fine, query is not).
|
|
23
|
+
var safeUrl=location.origin+location.pathname+location.hash;
|
|
24
|
+
var safeRef=d.referrer?d.referrer.split('?')[0]:'';
|
|
25
|
+
x.send(JSON.stringify({
|
|
26
|
+
s:site,sid:sid,e:e,p:p||{},
|
|
27
|
+
u:safeUrl,r:safeRef,t:d.title,
|
|
28
|
+
sw:screen.width,sh:screen.height
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
function pv(){t('pageview');}
|
|
32
|
+
${hashTracking}
|
|
33
|
+
${outboundTracking}
|
|
34
|
+
if(d.readyState==='complete')pv();
|
|
35
|
+
else w.addEventListener('load',pv);
|
|
36
|
+
w.stacksAnalytics={track:function(n,v){t('event',{name:n,value:v});}};
|
|
37
|
+
})();
|
|
38
|
+
</script>`}export function getSelfHostedAnalyticsHead(config){if(!config.siteId||!config.apiEndpoint)return[];return[["script",{"data-site":config.siteId,"data-api":config.apiEndpoint,defer:"",innerHTML:generateInlineScript(config)}]]}function generateInlineScript(config){const honorDnt=config.honorDnt?'if(n.doNotTrack==="1")return;':"",hashTracking=config.trackHashChanges?"w.addEventListener('hashchange',pv);":"",outboundTracking=config.trackOutboundLinks?"d.addEventListener('click',function(e){var a=e.target.closest('a');if(a&&a.hostname!==location.hostname){t('outbound',{url:a.href});}});":"";return`(function(){
|
|
39
|
+
'use strict';
|
|
40
|
+
var d=document,w=window,n=navigator,s=d.currentScript;
|
|
41
|
+
var site=s.dataset.site,api=s.dataset.api;
|
|
42
|
+
${honorDnt}
|
|
43
|
+
var q=[],sid=Math.random().toString(36).slice(2);
|
|
44
|
+
function t(e,p){
|
|
45
|
+
var x=new XMLHttpRequest();
|
|
46
|
+
x.open('POST',api+'/collect',true);
|
|
47
|
+
x.setRequestHeader('Content-Type','application/json');
|
|
48
|
+
var safeUrl=location.origin+location.pathname+location.hash;var safeRef=d.referrer?d.referrer.split('?')[0]:'';x.send(JSON.stringify({s:site,sid:sid,e:e,p:p||{},u:safeUrl,r:safeRef,t:d.title,sw:screen.width,sh:screen.height}));
|
|
49
|
+
}
|
|
50
|
+
function pv(){t('pageview');}
|
|
51
|
+
${hashTracking}
|
|
52
|
+
${outboundTracking}
|
|
53
|
+
if(d.readyState==='complete')pv();
|
|
54
|
+
else w.addEventListener('load',pv);
|
|
55
|
+
w.stacksAnalytics={track:function(n,v){t('event',{name:n,value:v});}};
|
|
56
|
+
})();`}function escapeAttr(str){return str.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">")}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,57 +1 @@
|
|
|
1
|
-
|
|
2
|
-
var W=1;function j(m){if(!m.siteId||!m.apiEndpoint)return"";let x=q(m.siteId),w=q(m.apiEndpoint),P=m.honorDnt?'if(n.doNotTrack==="1")return;':"",v=m.trackHashChanges?"w.addEventListener('hashchange',pv);":"",y=m.trackOutboundLinks?`
|
|
3
|
-
d.addEventListener('click',function(e){
|
|
4
|
-
var a=e.target.closest('a');
|
|
5
|
-
if(a&&a.hostname!==location.hostname){
|
|
6
|
-
t('outbound',{url:a.href});
|
|
7
|
-
}
|
|
8
|
-
});`:"";return`<!-- Stacks Self-Hosted Analytics -->
|
|
9
|
-
<script data-site="${x}" data-api="${w}" defer>
|
|
10
|
-
(function(){
|
|
11
|
-
'use strict';
|
|
12
|
-
var d=document,w=window,n=navigator,s=d.currentScript;
|
|
13
|
-
var site=s.dataset.site,api=s.dataset.api;
|
|
14
|
-
${P}
|
|
15
|
-
var q=[],sid=Math.random().toString(36).slice(2);
|
|
16
|
-
function t(e,p){
|
|
17
|
-
var x=new XMLHttpRequest();
|
|
18
|
-
x.open('POST',api+'/collect',true);
|
|
19
|
-
x.setRequestHeader('Content-Type','application/json');
|
|
20
|
-
// Strip the URL's query string before sending. location.href includes
|
|
21
|
-
// tokens / session IDs / search terms that shouldn't reach the
|
|
22
|
-
// analytics backend. Same goes for document.referrer when it points
|
|
23
|
-
// back to our own domain (path is fine, query is not).
|
|
24
|
-
var safeUrl=location.origin+location.pathname+location.hash;
|
|
25
|
-
var safeRef=d.referrer?d.referrer.split('?')[0]:'';
|
|
26
|
-
x.send(JSON.stringify({
|
|
27
|
-
s:site,sid:sid,e:e,p:p||{},
|
|
28
|
-
u:safeUrl,r:safeRef,t:d.title,
|
|
29
|
-
sw:screen.width,sh:screen.height
|
|
30
|
-
}));
|
|
31
|
-
}
|
|
32
|
-
function pv(){t('pageview');}
|
|
33
|
-
${v}
|
|
34
|
-
${y}
|
|
35
|
-
if(d.readyState==='complete')pv();
|
|
36
|
-
else w.addEventListener('load',pv);
|
|
37
|
-
w.stacksAnalytics={track:function(n,v){t('event',{name:n,value:v});}};
|
|
38
|
-
})();
|
|
39
|
-
</script>`}function z(m){if(!m.siteId||!m.apiEndpoint)return[];return[["script",{"data-site":m.siteId,"data-api":m.apiEndpoint,defer:"",innerHTML:R(m)}]]}function R(m){let x=m.honorDnt?'if(n.doNotTrack==="1")return;':"",w=m.trackHashChanges?"w.addEventListener('hashchange',pv);":"",P=m.trackOutboundLinks?"d.addEventListener('click',function(e){var a=e.target.closest('a');if(a&&a.hostname!==location.hostname){t('outbound',{url:a.href});}});":"";return`(function(){
|
|
40
|
-
'use strict';
|
|
41
|
-
var d=document,w=window,n=navigator,s=d.currentScript;
|
|
42
|
-
var site=s.dataset.site,api=s.dataset.api;
|
|
43
|
-
${x}
|
|
44
|
-
var q=[],sid=Math.random().toString(36).slice(2);
|
|
45
|
-
function t(e,p){
|
|
46
|
-
var x=new XMLHttpRequest();
|
|
47
|
-
x.open('POST',api+'/collect',true);
|
|
48
|
-
x.setRequestHeader('Content-Type','application/json');
|
|
49
|
-
var safeUrl=location.origin+location.pathname+location.hash;var safeRef=d.referrer?d.referrer.split('?')[0]:'';x.send(JSON.stringify({s:site,sid:sid,e:e,p:p||{},u:safeUrl,r:safeRef,t:d.title,sw:screen.width,sh:screen.height}));
|
|
50
|
-
}
|
|
51
|
-
function pv(){t('pageview');}
|
|
52
|
-
${w}
|
|
53
|
-
${P}
|
|
54
|
-
if(d.readyState==='complete')pv();
|
|
55
|
-
else w.addEventListener('load',pv);
|
|
56
|
-
w.stacksAnalytics={track:function(n,v){t('event',{name:n,value:v});}};
|
|
57
|
-
})();`}function q(m){return m.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">")}export{K as referrerHost,J as recordPageview,H as isPageviewRequest,z as getSelfHostedAnalyticsHead,j as generateSelfHostedScript,W as fathomWip,G as classifyAgent};
|
|
1
|
+
export{classifyAgent,isPageviewRequest,recordPageview,referrerHost}from"./capture";export*from"./drivers";
|