@stacksjs/analytics 0.74.32 → 0.74.34

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.
@@ -1 +1,19 @@
1
- export declare const fathomWip: 1;
1
+ import type { AnalyticsHeadTag } from './shared';
2
+ /**
3
+ * Build the Fathom head tags.
4
+ *
5
+ * Values are raw; escaping happens when the tags are rendered.
6
+ */
7
+ export declare function getFathomAnalyticsHead(config: FathomConfig): AnalyticsHeadTag[];
8
+ /**
9
+ * Generate the Fathom tracking script tag.
10
+ */
11
+ export declare function generateFathomScript(config: FathomConfig): string;
12
+ /** Fathom's hosted script, used when `scriptUrl` is not set. */
13
+ export declare const FATHOM_DEFAULT_SCRIPT_URL: 'https://cdn.usefathom.com/script.js';
14
+ export declare interface FathomConfig {
15
+ siteId: string
16
+ scriptUrl?: string
17
+ honorDnt?: boolean
18
+ spa?: boolean
19
+ }
@@ -1 +1,2 @@
1
- export const fathomWip=1;
1
+ import{renderHeadTags}from"./shared";export const FATHOM_DEFAULT_SCRIPT_URL="https://cdn.usefathom.com/script.js";export function getFathomAnalyticsHead(config){if(!config.siteId)return[];const attrs={src:config.scriptUrl||FATHOM_DEFAULT_SCRIPT_URL,"data-site":config.siteId,defer:""};if(config.honorDnt)attrs["data-honor-dnt"]="true";if(config.spa)attrs["data-spa"]="auto";return[["script",attrs]]}export function generateFathomScript(config){const tags=getFathomAnalyticsHead(config);if(!tags.length)return"";return`<!-- Stacks Fathom Analytics -->
2
+ ${renderHeadTags(tags)}`}
@@ -0,0 +1,17 @@
1
+ import type { AnalyticsHeadTag } from './shared';
2
+ /**
3
+ * Build the Google Analytics head tags.
4
+ *
5
+ * Values are raw; escaping happens when the tags are rendered.
6
+ */
7
+ export declare function getGoogleAnalyticsHead(config: GoogleAnalyticsConfig): AnalyticsHeadTag[];
8
+ /**
9
+ * Generate the Google Analytics tracking script tags.
10
+ */
11
+ export declare function generateGoogleAnalyticsScript(config: GoogleAnalyticsConfig): string;
12
+ /** The gtag.js loader, used to build the tag `src`. */
13
+ export declare const GOOGLE_ANALYTICS_SCRIPT_URL: 'https://www.googletagmanager.com/gtag/js';
14
+ export declare interface GoogleAnalyticsConfig {
15
+ trackingId: string
16
+ debug?: boolean
17
+ }
@@ -0,0 +1,2 @@
1
+ import{jsString,renderHeadTags}from"./shared";export const GOOGLE_ANALYTICS_SCRIPT_URL="https://www.googletagmanager.com/gtag/js";export function getGoogleAnalyticsHead(config){if(!config.trackingId)return[];const id=jsString(config.trackingId),options=config.debug?",{debug_mode:true}":"";return[["script",{src:`${GOOGLE_ANALYTICS_SCRIPT_URL}?id=${encodeURIComponent(config.trackingId)}`,async:""}],["script",{innerHTML:`window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config',${id}${options});`}]]}export function generateGoogleAnalyticsScript(config){const tags=getGoogleAnalyticsHead(config);if(!tags.length)return"";return`<!-- Stacks Google Analytics -->
2
+ ${renderHeadTags(tags)}`}
@@ -1,2 +1,5 @@
1
1
  export * from './fathom';
2
+ export * from './google-analytics';
3
+ export * from './plausible';
2
4
  export * from './self-hosted';
5
+ export * from './shared';
@@ -1 +1 @@
1
- export*from"./fathom";export*from"./self-hosted";
1
+ export*from"./fathom";export*from"./google-analytics";export*from"./plausible";export*from"./self-hosted";export*from"./shared";
@@ -0,0 +1,23 @@
1
+ import type { AnalyticsHeadTag } from './shared';
2
+ /**
3
+ * Build the URL of the hosted script variant matching the config.
4
+ */
5
+ export declare function plausibleScriptUrl(config: PlausibleConfig): string;
6
+ /**
7
+ * Build the Plausible head tags.
8
+ *
9
+ * Values are raw; escaping happens when the tags are rendered.
10
+ */
11
+ export declare function getPlausibleAnalyticsHead(config: PlausibleConfig): AnalyticsHeadTag[];
12
+ /**
13
+ * Generate the Plausible tracking script tag.
14
+ */
15
+ export declare function generatePlausibleScript(config: PlausibleConfig): string;
16
+ /** Plausible's hosted script host, used when `scriptUrl` is not set. */
17
+ export declare const PLAUSIBLE_DEFAULT_HOST: 'https://plausible.io';
18
+ export declare interface PlausibleConfig {
19
+ domain: string
20
+ scriptUrl?: string
21
+ trackLocalhost?: boolean
22
+ hashMode?: boolean
23
+ }
@@ -0,0 +1,2 @@
1
+ import{renderHeadTags}from"./shared";export const PLAUSIBLE_DEFAULT_HOST="https://plausible.io";export function plausibleScriptUrl(config){if(config.scriptUrl)return config.scriptUrl;const extensions=[config.hashMode?"hash":"",config.trackLocalhost?"local":""].filter(Boolean);return`${PLAUSIBLE_DEFAULT_HOST}/js/script${extensions.map((e)=>`.${e}`).join("")}.js`}export function getPlausibleAnalyticsHead(config){if(!config.domain)return[];return[["script",{src:plausibleScriptUrl(config),"data-domain":config.domain,defer:""}]]}export function generatePlausibleScript(config){const tags=getPlausibleAnalyticsHead(config);if(!tags.length)return"";return`<!-- Stacks Plausible Analytics -->
2
+ ${renderHeadTags(tags)}`}
@@ -1,27 +1,15 @@
1
+ import type { AnalyticsHeadTag } from './shared';
1
2
  /**
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
3
+ * Build the self-hosted head tags.
4
+ *
5
+ * Compatible with the Stacks/BunPress docs `head` array format. Values are raw;
6
+ * escaping happens when the tags are rendered.
8
7
  */
9
- export declare function getSelfHostedAnalyticsHead(config: SelfHostedConfig): [string, Record<string, string>][];
8
+ export declare function getSelfHostedAnalyticsHead(config: SelfHostedConfig): AnalyticsHeadTag[];
10
9
  /**
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)
10
+ * Generate the self-hosted analytics tracking script
24
11
  */
12
+ export declare function generateSelfHostedScript(config: SelfHostedConfig): string;
25
13
  export declare interface SelfHostedConfig {
26
14
  siteId: string
27
15
  apiEndpoint: string
@@ -1,51 +1,17 @@
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(){
1
+ import{renderHeadTags}from"./shared";export function getSelfHostedAnalyticsHead(config){if(!config.siteId||!config.apiEndpoint)return[];return[["script",{"data-site":config.siteId,"data-api":config.apiEndpoint,defer:"",innerHTML:generateInlineScript(config)}]]}export function generateSelfHostedScript(config){const tags=getSelfHostedAnalyticsHead(config);if(!tags.length)return"";return`<!-- Stacks Self-Hosted Analytics -->
2
+ ${renderHeadTags(tags)}`}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
3
  'use strict';
40
4
  var d=document,w=window,n=navigator,s=d.currentScript;
41
5
  var site=s.dataset.site,api=s.dataset.api;
42
6
  ${honorDnt}
43
- var q=[],sid=Math.random().toString(36).slice(2);
7
+ var sid=Math.random().toString(36).slice(2);
44
8
  function t(e,p){
45
9
  var x=new XMLHttpRequest();
46
10
  x.open('POST',api+'/collect',true);
47
11
  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}));
12
+ var safeUrl=location.origin+location.pathname+location.hash;
13
+ var safeRef=d.referrer?d.referrer.split('?')[0]:'';
14
+ 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
15
  }
50
16
  function pv(){t('pageview');}
51
17
  ${hashTracking}
@@ -53,4 +19,4 @@ ${outboundTracking}
53
19
  if(d.readyState==='complete')pv();
54
20
  else w.addEventListener('load',pv);
55
21
  w.stacksAnalytics={track:function(n,v){t('event',{name:n,value:v});}};
56
- })();`}function escapeAttr(str){return str.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
22
+ })();`}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Escape a value for safe insertion into an HTML attribute.
3
+ */
4
+ export declare function escapeAttr(str: string): string;
5
+ /**
6
+ * Serialize a value as a JavaScript string literal for inline script bodies.
7
+ *
8
+ * `JSON.stringify` handles quoting and control characters; the `<` and `>`
9
+ * replacements stop a value containing `</script>` from closing the tag early.
10
+ */
11
+ export declare function jsString(value: string): string;
12
+ /**
13
+ * Render an `[tag, attributes]` pair as HTML.
14
+ *
15
+ * Head tags carry raw values; escaping happens here, so a driver never escapes
16
+ * twice. An `innerHTML` attribute becomes the tag body instead of an attribute,
17
+ * and an empty value renders as a bare attribute (`defer`, `async`).
18
+ */
19
+ export declare function renderHeadTag([tag, attrs]: AnalyticsHeadTag): string;
20
+ /**
21
+ * Render a driver's head tags as an HTML block.
22
+ */
23
+ export declare function renderHeadTags(tags: AnalyticsHeadTag[]): string;
24
+ /**
25
+ * Shared helpers for the analytics drivers.
26
+ *
27
+ * Every driver emits a `<script>` tag, either as raw HTML or as the
28
+ * `[tag, attributes]` pair the Stacks/BunPress docs `head` array expects, so the
29
+ * tag shape and the escaping live here rather than once per driver.
30
+ */
31
+ /** A single head entry: the tag name plus its attributes. */
32
+ export type AnalyticsHeadTag = [string, Record<string, string>];
@@ -0,0 +1,2 @@
1
+ export function escapeAttr(str){return str.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}export function jsString(value){return JSON.stringify(value).replace(/</g,"\\u003C").replace(/>/g,"\\u003E")}export function renderHeadTag([tag,attrs]){const body=attrs.innerHTML??"",rendered=Object.entries(attrs).filter(([key])=>key!=="innerHTML").map(([key,value])=>value===""?` ${key}`:` ${key}="${escapeAttr(value)}"`).join("");return`<${tag}${rendered}>${body}</${tag}>`}export function renderHeadTags(tags){return tags.map(renderHeadTag).join(`
2
+ `)}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { classifyAgent, isPageviewRequest, recordPageview, referrerHost } from './capture';
2
2
  export * from './drivers/index';
3
+ export * from './registry';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{classifyAgent,isPageviewRequest,recordPageview,referrerHost}from"./capture";export*from"./drivers";
1
+ export{classifyAgent,isPageviewRequest,recordPageview,referrerHost}from"./capture";export*from"./drivers";export*from"./registry";
@@ -0,0 +1,21 @@
1
+ import type { AnalyticsConfig, AnalyticsOptions } from '@stacksjs/types';
2
+ import type { AnalyticsHeadTag } from './drivers/shared';
3
+ /**
4
+ * Is this a driver name Stacks implements?
5
+ */
6
+ export declare function isAnalyticsDriver(value: unknown): value is AnalyticsDriverName;
7
+ /**
8
+ * Resolve the configured driver's head tags.
9
+ *
10
+ * Returns an empty array when no driver is configured at all. Throws when a
11
+ * driver is named that Stacks does not implement, or when the named driver is
12
+ * missing the config it needs.
13
+ */
14
+ export declare function getAnalyticsHead(config: AnalyticsConfig): AnalyticsHeadTag[];
15
+ /**
16
+ * Render the configured driver's tracking script as HTML.
17
+ */
18
+ export declare function generateAnalyticsScript(config: AnalyticsConfig): string;
19
+ /** Every driver name that has an implementation behind it. */
20
+ export declare const ANALYTICS_DRIVERS: readonly AnalyticsDriverName[];
21
+ export type AnalyticsDriverName = AnalyticsOptions['driver'];
@@ -0,0 +1 @@
1
+ import{getFathomAnalyticsHead}from"./drivers/fathom";import{getGoogleAnalyticsHead}from"./drivers/google-analytics";import{getPlausibleAnalyticsHead}from"./drivers/plausible";import{getSelfHostedAnalyticsHead}from"./drivers/self-hosted";import{renderHeadTags}from"./drivers/shared";export const ANALYTICS_DRIVERS=["google-analytics","fathom","plausible","self-hosted"];export function isAnalyticsDriver(value){return typeof value==="string"&&ANALYTICS_DRIVERS.includes(value)}function misconfigured(driver,key,field){return Error(`Analytics driver "${driver}" is selected but not configured: set \`drivers.${key}.${field}\` in config/analytics.ts, or change \`driver\`.`)}export function getAnalyticsHead(config){const driver=config.driver;if(!driver)return[];if(!isAnalyticsDriver(driver))throw Error(`Unknown analytics driver "${String(driver)}". Available drivers: ${ANALYTICS_DRIVERS.join(", ")}.`);switch(driver){case"google-analytics":{const options=config.drivers?.googleAnalytics;if(!options?.trackingId)throw misconfigured(driver,"googleAnalytics","trackingId");return getGoogleAnalyticsHead(options)}case"fathom":{const options=config.drivers?.fathom;if(!options?.siteId)throw misconfigured(driver,"fathom","siteId");return getFathomAnalyticsHead(options)}case"plausible":{const options=config.drivers?.plausible;if(!options?.domain)throw misconfigured(driver,"plausible","domain");return getPlausibleAnalyticsHead(options)}case"self-hosted":{const options=config.drivers?.selfHosted;if(!options?.siteId)throw misconfigured(driver,"selfHosted","siteId");if(!options.apiEndpoint)throw misconfigured(driver,"selfHosted","apiEndpoint");return getSelfHostedAnalyticsHead(options)}default:throw Error(`Analytics driver "${String(driver)}" has no implementation.`)}}export function generateAnalyticsScript(config){const tags=getAnalyticsHead(config);if(!tags.length)return"";return renderHeadTags(tags)}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/analytics",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.32",
5
+ "version": "0.74.34",
6
6
  "description": "Stacks Analytics. Privacy-friendly.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -51,7 +51,8 @@
51
51
  "prepublishOnly": "bun run build"
52
52
  },
53
53
  "dependencies": {
54
- "@stacksjs/database": "0.74.32"
54
+ "@stacksjs/database": "0.74.34",
55
+ "@stacksjs/types": "0.74.34"
55
56
  },
56
57
  "devDependencies": {
57
58
  "better-dx": "^0.2.24"