@vskstudio/takt-astro 0.6.2 → 0.8.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 +64 -5
- package/Takt.astro +7 -1
- package/TaktRoute.astro +4 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +12 -3
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Astro integration for [Takt](https://github.com/vskstudio/takt-core), privacy-fr
|
|
|
11
11
|
pnpm add @vskstudio/takt-astro @vskstudio/takt-core
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Both are peer dependencies: `astro` (`>=4`) and `@vskstudio/takt-core` (`>=0.
|
|
14
|
+
Both are peer dependencies: `astro` (`>=4`) and `@vskstudio/takt-core` (`>=0.10.0`).
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
@@ -62,8 +62,11 @@ Both the integration and the component accept the same options, with one excepti
|
|
|
62
62
|
| `trackQuery` | `boolean` | `false` | Keep the full query string and hash instead of stripping them. Wins over `queryParams`. |
|
|
63
63
|
| `queryParams` | `string[]` | – | Allowlist of query params to keep, applied only when `trackQuery` is off. |
|
|
64
64
|
| `exclude` | `string[]` | – | Path prefixes never tracked, e.g. `['/app', '/account']` (segment-bounded, checked at send time). |
|
|
65
|
-
| `
|
|
65
|
+
| `redactRoutes` | `string[]` | none | Route patterns sent in place of the real path, e.g. `['/verify/[token]']` sends `/verify/[token]` instead of `/verify/abc123`. See [Route redaction](#route-redaction). |
|
|
66
|
+
| `routeTemplates` | `boolean` | `false` | Send every page as its Astro route template (`/blog/[slug]`), read from `Astro.routePattern`. Needs Astro 5 and the route meta, see [Route redaction](#route-redaction). |
|
|
67
|
+
| `scrubUrl` | `(url: string) => string` | – | **Integration only.** Rewrite each URL before it is sent (page, referrer, and the `url` prop of outbound-link and file-download events), e.g. to strip a fragment or PII. See the note below. |
|
|
66
68
|
| `tagged` | `boolean` | `false` | Auto-track clicks on `[data-takt-event]` elements; `data-takt-prop-*` attributes become event props. |
|
|
69
|
+
| `debug` | `boolean` | `false` | Log each payload to the browser console before sending. |
|
|
67
70
|
|
|
68
71
|
> **`scrubUrl` note.** Unlike the other options, `scrubUrl` is a **function**, so it
|
|
69
72
|
> is supported **only via the integration**, not the `<Takt />` component. The
|
|
@@ -80,9 +83,45 @@ Both the integration and the component accept the same options, with one excepti
|
|
|
80
83
|
|
|
81
84
|
Astro's client router runs several history operations per navigation (scroll-restoration `replaceState` plus `pushState`), so Takt does **not** rely on core's history patch here — it would over-count. Instead the runtime fires one explicit initial pageview and then one per Astro `astro:after-swap` event (including View Transitions DOM swaps and back/forward), so each navigation is counted exactly once. On a plain MPA (no client router) the script re-runs per page load, which fires the initial pageview each time. The runtime is SSR/prerender safe — it only runs in the browser.
|
|
82
85
|
|
|
86
|
+
## Route redaction
|
|
87
|
+
|
|
88
|
+
Query strings are stripped by default, but path segments are sent as they are, so `/verify/abc123` leaks the token. List the sensitive routes in `redactRoutes` and every matching path is sent as its pattern, while the other pages keep their real path:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
takt({ redactRoutes: ['/verify/[token]', '/invoices/[id].pdf'] })
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The rule covers the page URL, same-origin referrers, outbound and download link destinations, and 404 paths. Patterns use Astro's syntax (`[param]`, `[...rest]`) and also accept `:param` and `*`.
|
|
95
|
+
|
|
96
|
+
To send every page as its route template instead (`/blog/hello` becomes `/blog/[slug]`), turn on `routeTemplates`. Astro knows the template on the server (`Astro.routePattern`), so it travels in a `<meta name="takt:route">` tag that the runtime reads at each pageview, including after a ClientRouter navigation. With the integration, render that tag with `<TaktRoute />` in the `<head>` of your layout:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
// astro.config.mjs
|
|
100
|
+
export default defineConfig({
|
|
101
|
+
integrations: [takt({ routeTemplates: true })],
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```astro
|
|
106
|
+
---
|
|
107
|
+
import TaktRoute from '@vskstudio/takt-astro/TaktRoute.astro'
|
|
108
|
+
---
|
|
109
|
+
<head>
|
|
110
|
+
<TaktRoute />
|
|
111
|
+
</head>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `<Takt />` component renders the tag itself when `routeTemplates` is set:
|
|
115
|
+
|
|
116
|
+
```astro
|
|
117
|
+
<Takt domain="example.com" routeTemplates />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`Astro.routePattern` exists from Astro 5. On Astro 4 no tag is rendered, so `redactRoutes` still applies and the real path is sent otherwise. This mode merges every article of a public blog into one row, so it suits private areas better; `redactRoutes` is usually the right tool for a public site.
|
|
121
|
+
|
|
83
122
|
## Custom events
|
|
84
123
|
|
|
85
|
-
`track`, `pageview`, `optOut` and `
|
|
124
|
+
`track`, `pageview`, `optOut`, `optIn` and `isOptedOut` are re-exported from core for convenience. `track` and `pageview` act on core's default instance, which only exists in the browser once the injected runtime has booted, so call them from a client-side `<script>`, not from `.astro` frontmatter (where they are silent no-ops):
|
|
86
125
|
|
|
87
126
|
```astro
|
|
88
127
|
<button id="signup">Sign up</button>
|
|
@@ -94,6 +133,26 @@ Astro's client router runs several history operations per navigation (scroll-res
|
|
|
94
133
|
</script>
|
|
95
134
|
```
|
|
96
135
|
|
|
136
|
+
## Consent
|
|
137
|
+
|
|
138
|
+
`optOut`, `optIn` and `isOptedOut` do not wait for the runtime to boot: they read and write the stored choice directly. A consent banner can therefore run before the injected runtime, and the instance it boots later honours the choice. Call them from a client-side `<script>`, since the choice lives in the browser.
|
|
139
|
+
|
|
140
|
+
```astro
|
|
141
|
+
<button id="analytics-toggle"></button>
|
|
142
|
+
<script>
|
|
143
|
+
import { isOptedOut, optIn, optOut } from '@vskstudio/takt-astro'
|
|
144
|
+
const button = document.getElementById('analytics-toggle')
|
|
145
|
+
const render = () => {
|
|
146
|
+
if (button) button.textContent = isOptedOut() ? 'Enable analytics' : 'Disable analytics'
|
|
147
|
+
}
|
|
148
|
+
button?.addEventListener('click', () => {
|
|
149
|
+
isOptedOut() ? optIn() : optOut()
|
|
150
|
+
render()
|
|
151
|
+
})
|
|
152
|
+
render()
|
|
153
|
+
</script>
|
|
154
|
+
```
|
|
155
|
+
|
|
97
156
|
## Widgets
|
|
98
157
|
|
|
99
158
|
Thin wrappers around Takt's server-rendered badge SVG and embed iframe.
|
|
@@ -137,10 +196,10 @@ From `@vskstudio/takt-astro`:
|
|
|
137
196
|
|
|
138
197
|
- `default` / `takt` — the integration factory (same function under both names)
|
|
139
198
|
- `resolveOptions`, `assertNoScrubUrl`, `buildRuntime` — the internals the `.astro` components build on, exported so you can drive the runtime yourself
|
|
140
|
-
- Re-exported from core: `track`, `pageview`, `optOut`, `optIn`, `createStats`, `PublicApiError`, `badgeUrl`, `embedUrl`
|
|
199
|
+
- Re-exported from core: `track`, `pageview`, `optOut`, `optIn`, `isOptedOut`, `createStats`, `PublicApiError`, `badgeUrl`, `embedUrl`
|
|
141
200
|
- Types: `TaktOptions`, plus `InitOptions`, `TrackOptions`, `BadgeOptions`, `EmbedOptions`, `BadgeVariant`, `BadgeGlyph`, `EmbedTheme`, `WidgetLang`, `StatsClient`, `StatsClientOptions`, `StatsParams`, `StatsPeriod`, `StatsDimension`, `StatsMetrics`, `StatsSummary`, `StatsPoint`, `StatsTimeseries`, `StatsBreakdownRow`, `StatsBreakdown`, `StatsRealtime` re-exported from core
|
|
142
201
|
|
|
143
|
-
Component subpaths: `@vskstudio/takt-astro/Takt.astro`, `/Badge.astro`, `/Embed.astro`.
|
|
202
|
+
Component subpaths: `@vskstudio/takt-astro/Takt.astro`, `/TaktRoute.astro`, `/Badge.astro`, `/Embed.astro`.
|
|
144
203
|
|
|
145
204
|
## License
|
|
146
205
|
|
package/Takt.astro
CHANGED
|
@@ -17,7 +17,10 @@ const config = JSON.stringify(resolveOptions(Astro.props))
|
|
|
17
17
|
.replace(/</g, '\\u003c')
|
|
18
18
|
.replace(/>/g, '\\u003e')
|
|
19
19
|
.replace(/&/g, '\\u0026')
|
|
20
|
+
|
|
21
|
+
const route = Astro.props.routeTemplates ? Astro.routePattern : undefined
|
|
20
22
|
---
|
|
23
|
+
{route && <meta name="takt:route" content={route} />}
|
|
21
24
|
<!-- Pick ONE install path — this component OR the integration — not both. -->
|
|
22
25
|
<script type="application/json" data-takt-config set:html={config}></script>
|
|
23
26
|
<!-- A bundled (non-inline) module so Astro resolves the bare core import; an
|
|
@@ -29,9 +32,12 @@ const config = JSON.stringify(resolveOptions(Astro.props))
|
|
|
29
32
|
if (el && !w.__takt) {
|
|
30
33
|
const o = JSON.parse(el.textContent || '{}')
|
|
31
34
|
w.__takt = true
|
|
35
|
+
const routeTemplate = o.routeTemplates
|
|
36
|
+
? () => document.querySelector('meta[name="takt:route"]')?.getAttribute('content') ?? null
|
|
37
|
+
: undefined
|
|
32
38
|
// auto forced off: Astro's ClientRouter runs several history ops per nav, so
|
|
33
39
|
// core's history patch would over-count. Drive pageviews from after-swap.
|
|
34
|
-
init({ ...o, auto: false })
|
|
40
|
+
init({ ...o, auto: false, routeTemplate })
|
|
35
41
|
pageview()
|
|
36
42
|
if (o.auto !== false) document.addEventListener('astro:after-swap', () => pageview())
|
|
37
43
|
}
|
package/TaktRoute.astro
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroIntegration } from 'astro';
|
|
2
2
|
import { InitOptions } from '@vskstudio/takt-core';
|
|
3
|
-
export { BadgeGlyph, BadgeOptions, BadgeVariant, EmbedOptions, EmbedTheme, InitOptions, PublicApiError, StatsBreakdown, StatsBreakdownRow, StatsClient, StatsClientOptions, StatsDimension, StatsMetrics, StatsParams, StatsPeriod, StatsPoint, StatsRealtime, StatsSummary, StatsTimeseries, TrackOptions, WidgetLang, badgeUrl, createStats, embedUrl, optIn, optOut, pageview, track } from '@vskstudio/takt-core';
|
|
3
|
+
export { BadgeGlyph, BadgeOptions, BadgeVariant, EmbedOptions, EmbedTheme, InitOptions, PublicApiError, StatsBreakdown, StatsBreakdownRow, StatsClient, StatsClientOptions, StatsDimension, StatsMetrics, StatsParams, StatsPeriod, StatsPoint, StatsRealtime, StatsSummary, StatsTimeseries, TrackOptions, WidgetLang, badgeUrl, createStats, embedUrl, isOptedOut, optIn, optOut, pageview, track } from '@vskstudio/takt-core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Options for the Takt integration and `<Takt />` component. Mirrors core's
|
|
@@ -41,6 +41,8 @@ interface TaktOptions {
|
|
|
41
41
|
queryParams?: string[];
|
|
42
42
|
/** Path prefixes never tracked, e.g. `['/app','/account']`. Segment-bounded: `'/app'` matches `'/app'` and `'/app/…'` but not `'/application'`. */
|
|
43
43
|
exclude?: string[];
|
|
44
|
+
redactRoutes?: string[];
|
|
45
|
+
routeTemplates?: boolean;
|
|
44
46
|
/**
|
|
45
47
|
* Rewrite each URL before it is sent (e.g. strip a fragment or PII).
|
|
46
48
|
* MUST be a self-contained function: it is stringified at build time and
|
|
@@ -54,6 +56,7 @@ interface TaktOptions {
|
|
|
54
56
|
scrubUrl?: (url: string) => string;
|
|
55
57
|
/** Auto-track elements marked with `data-takt-event`. */
|
|
56
58
|
tagged?: boolean;
|
|
59
|
+
debug?: boolean;
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
62
|
* Guard the `<Takt />` component path against `scrubUrl`. The component
|
package/dist/index.js
CHANGED
|
@@ -22,15 +22,23 @@ function resolveOptions(options = {}) {
|
|
|
22
22
|
trackQuery: options.trackQuery,
|
|
23
23
|
queryParams: options.queryParams,
|
|
24
24
|
exclude: options.exclude,
|
|
25
|
-
|
|
25
|
+
redactRoutes: options.redactRoutes,
|
|
26
|
+
routeTemplates: options.routeTemplates,
|
|
27
|
+
tagged: options.tagged,
|
|
28
|
+
debug: options.debug
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
// src/runtime.ts
|
|
33
|
+
var ROUTE_TEMPLATE_RESOLVER = `() => document.querySelector('meta[name="takt:route"]')?.getAttribute('content') ?? null`;
|
|
30
34
|
function buildRuntime(options, scrubUrl) {
|
|
31
35
|
const spa = options.auto !== false;
|
|
32
36
|
const json = JSON.stringify({ ...options, auto: false }).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
33
|
-
const
|
|
37
|
+
const functions = [
|
|
38
|
+
...scrubUrl ? [`scrubUrl: ${scrubUrl.toString()}`] : [],
|
|
39
|
+
...options.routeTemplates ? [`routeTemplate: ${ROUTE_TEMPLATE_RESOLVER}`] : []
|
|
40
|
+
];
|
|
41
|
+
const config = functions.length ? `Object.assign(${json}, { ${functions.join(", ")} })` : json;
|
|
34
42
|
return [
|
|
35
43
|
`import { init, pageview } from '@vskstudio/takt-core';`,
|
|
36
44
|
// SSR/prerender guard: this module is evaluated under Node when Astro
|
|
@@ -65,7 +73,7 @@ function takt(options = {}) {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
// src/index.ts
|
|
68
|
-
import { track, pageview, optOut, optIn } from "@vskstudio/takt-core";
|
|
76
|
+
import { track, pageview, optOut, optIn, isOptedOut } from "@vskstudio/takt-core";
|
|
69
77
|
import { createStats, PublicApiError, badgeUrl, embedUrl } from "@vskstudio/takt-core";
|
|
70
78
|
export {
|
|
71
79
|
PublicApiError,
|
|
@@ -75,6 +83,7 @@ export {
|
|
|
75
83
|
createStats,
|
|
76
84
|
takt as default,
|
|
77
85
|
embedUrl,
|
|
86
|
+
isOptedOut,
|
|
78
87
|
optIn,
|
|
79
88
|
optOut,
|
|
80
89
|
pageview,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vskstudio/takt-astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Astro integration for Takt privacy-friendly analytics",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "VSK Studio",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"default": "./dist/index.js"
|
|
33
33
|
},
|
|
34
34
|
"./Takt.astro": "./Takt.astro",
|
|
35
|
+
"./TaktRoute.astro": "./TaktRoute.astro",
|
|
35
36
|
"./Badge.astro": "./Badge.astro",
|
|
36
37
|
"./Embed.astro": "./Embed.astro",
|
|
37
38
|
"./package.json": "./package.json"
|
|
@@ -39,31 +40,22 @@
|
|
|
39
40
|
"files": [
|
|
40
41
|
"dist",
|
|
41
42
|
"Takt.astro",
|
|
43
|
+
"TaktRoute.astro",
|
|
42
44
|
"Badge.astro",
|
|
43
45
|
"Embed.astro"
|
|
44
46
|
],
|
|
45
47
|
"engines": {
|
|
46
48
|
"node": ">=18"
|
|
47
49
|
},
|
|
48
|
-
"scripts": {
|
|
49
|
-
"build": "tsup",
|
|
50
|
-
"typecheck": "tsc --noEmit",
|
|
51
|
-
"lint": "eslint .",
|
|
52
|
-
"test": "vitest run",
|
|
53
|
-
"e2e": "playwright test",
|
|
54
|
-
"size": "size-limit",
|
|
55
|
-
"prepublishOnly": "pnpm build",
|
|
56
|
-
"release": "pnpm build && changeset publish"
|
|
57
|
-
},
|
|
58
50
|
"peerDependencies": {
|
|
59
|
-
"@vskstudio/takt-core": ">=0.
|
|
51
|
+
"@vskstudio/takt-core": ">=0.10.0",
|
|
60
52
|
"astro": ">=4"
|
|
61
53
|
},
|
|
62
54
|
"devDependencies": {
|
|
63
55
|
"@changesets/cli": "^2.31.0",
|
|
64
56
|
"@playwright/test": "^1.50.0",
|
|
65
57
|
"@size-limit/preset-small-lib": "^11.1.6",
|
|
66
|
-
"@vskstudio/takt-core": "^0.
|
|
58
|
+
"@vskstudio/takt-core": "^0.10.0",
|
|
67
59
|
"astro": "^5.0.0",
|
|
68
60
|
"eslint": "^9.0.0",
|
|
69
61
|
"size-limit": "^11.1.6",
|
|
@@ -72,5 +64,13 @@
|
|
|
72
64
|
"typescript-eslint": "^8.0.0",
|
|
73
65
|
"vitest": "^2.1.0"
|
|
74
66
|
},
|
|
75
|
-
"
|
|
76
|
-
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsup",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"lint": "eslint .",
|
|
71
|
+
"test": "vitest run",
|
|
72
|
+
"e2e": "playwright test",
|
|
73
|
+
"size": "size-limit",
|
|
74
|
+
"release": "pnpm build && changeset publish"
|
|
75
|
+
}
|
|
76
|
+
}
|