@we8/astro 0.1.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 +157 -0
- package/components/ConsentBanner.astro +105 -0
- package/components/VisitBeacon.astro +58 -0
- package/components/We8Form.astro +94 -0
- package/dist/beacon.d.ts +32 -0
- package/dist/beacon.d.ts.map +1 -0
- package/dist/beacon.js +38 -0
- package/dist/beacon.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/consent.d.ts +61 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/consent.js +121 -0
- package/dist/consent.js.map +1 -0
- package/dist/content.d.ts +36 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +58 -0
- package/dist/content.js.map +1 -0
- package/dist/form.d.ts +22 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +39 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +40 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +37 -0
- package/dist/integration.js.map +1 -0
- package/dist/seo.d.ts +51 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +63 -0
- package/dist/seo.js.map +1 -0
- package/dist/session.d.ts +17 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +46 -0
- package/dist/session.js.map +1 -0
- package/dist/sitemap.d.ts +19 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +17 -0
- package/dist/sitemap.js.map +1 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @we8/astro
|
|
2
|
+
|
|
3
|
+
The Astro integration for we8. It wraps [`@we8/client`](https://www.npmjs.com/package/@we8/client) (itself a
|
|
4
|
+
thin layer over the OpenAPI-documented `/v1` API) with the pieces an Astro site
|
|
5
|
+
needs: build-time content fetching, a code-first SEO merge, sitemap merging, a
|
|
6
|
+
consent-gated visit beacon, and a progressive-enhancement form. The API is the
|
|
7
|
+
product; this package is the ergonomic way to build a site on it.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Astro 7 or newer in the consuming site (declared as an optional peer: the
|
|
12
|
+
helpers run without Astro, the `.astro` components need it).
|
|
13
|
+
- Node 22.12 or newer, which is Astro 7's own floor.
|
|
14
|
+
- `@we8/client` comes with this package as a regular dependency; npm installs
|
|
15
|
+
it for you.
|
|
16
|
+
- A we8 CMS URL and a publishable key (`WE8_API_URL`, `WE8_PUBLISHABLE_KEY`).
|
|
17
|
+
Both absent is fine: `tryResolveConfig` returns `null` so a site can fall
|
|
18
|
+
back to its own data source.
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @we8/astro astro
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`astro` is an optional peer dependency: the helpers typecheck and run without
|
|
26
|
+
it, and the `.astro` components need it in the consuming project (which an Astro
|
|
27
|
+
site already has).
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
// astro.config.mjs
|
|
33
|
+
import { defineConfig } from 'astro/config';
|
|
34
|
+
import { we8 } from '@we8/astro';
|
|
35
|
+
|
|
36
|
+
export default defineConfig({
|
|
37
|
+
site: process.env.SITE, // your public origin, for canonical URLs and sitemap
|
|
38
|
+
integrations: [we8()], // warns at build start when the env is unset
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```astro
|
|
43
|
+
---
|
|
44
|
+
// Any page or layout frontmatter (runs in Node during `astro build`).
|
|
45
|
+
import { getPosts, getSiteConfig, mergeSeo, postUrl } from '@we8/astro';
|
|
46
|
+
|
|
47
|
+
const site = await getSiteConfig();
|
|
48
|
+
const { items } = await getPosts({ page: 1, pageSize: 10 });
|
|
49
|
+
const seo = mergeSeo({ title: 'Blog' }, site); // page values win, site fills gaps
|
|
50
|
+
---
|
|
51
|
+
<title>{seo.title}</title>
|
|
52
|
+
{items.map((post) => <a href={postUrl(post, site)}>{post.title}</a>)}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What you get
|
|
56
|
+
|
|
57
|
+
- Config: `resolveConfig(env?)` (throws when unconfigured),
|
|
58
|
+
`tryResolveConfig(env?)` (returns `null` instead, for a template that falls
|
|
59
|
+
back to its own data source), `createBuildClient(config?, fetch?)`.
|
|
60
|
+
- Build-time content: `getPosts`, `getPost`, `getPostSlugs`, `getSiteConfig`,
|
|
61
|
+
`getDocument` (design.md, llms.txt). These run in Node during `astro build`
|
|
62
|
+
and need `WE8_API_URL`.
|
|
63
|
+
- SEO: `mergeSeo(page, site)` (code-first merge), `postUrl(post, site)`,
|
|
64
|
+
`robotsTxt(site, { sitemapUrl? })`.
|
|
65
|
+
- Sitemap: `we8SitemapUrl()` and `mergeSitemap({ map })` to feed
|
|
66
|
+
`@astrojs/sitemap`'s `customPages` with your post URLs.
|
|
67
|
+
- Consent: `readConsent`, `writeConsent`, `recordConsent(client, choice)`,
|
|
68
|
+
`analyticsAllowed`, `onAnalyticsConsent(handler)`, plus `CONSENT_STORAGE_KEY`
|
|
69
|
+
and `CONSENT_EVENT`.
|
|
70
|
+
- Browser: `getSessionId()`, `sendVisit(client)`, `trackVisit(client)` (the
|
|
71
|
+
consent-gated beacon), `enhanceForm(form, client, opts)`.
|
|
72
|
+
- Re-exports `createClient`, `We8ApiError`, `isWe8ApiError` and the key types
|
|
73
|
+
from `@we8/client`, so a template needs only this one dependency.
|
|
74
|
+
|
|
75
|
+
## Components
|
|
76
|
+
|
|
77
|
+
Import the ready-made components by subpath. Each takes the publishable key as a
|
|
78
|
+
prop and falls back to `PUBLIC_WE8_PUBLISHABLE_KEY`; each degrades rather than
|
|
79
|
+
crashes when no key is available.
|
|
80
|
+
|
|
81
|
+
```astro
|
|
82
|
+
---
|
|
83
|
+
import ConsentBanner from '@we8/astro/ConsentBanner.astro';
|
|
84
|
+
import VisitBeacon from '@we8/astro/VisitBeacon.astro';
|
|
85
|
+
import We8Form from '@we8/astro/We8Form.astro';
|
|
86
|
+
|
|
87
|
+
const key = import.meta.env.WE8_PUBLISHABLE_KEY;
|
|
88
|
+
const apiUrl = import.meta.env.WE8_API_URL;
|
|
89
|
+
---
|
|
90
|
+
<ConsentBanner key={key} apiUrl={apiUrl} />
|
|
91
|
+
<VisitBeacon key={key} apiUrl={apiUrl} />
|
|
92
|
+
|
|
93
|
+
<We8Form formKey="contact" key={key} apiUrl={apiUrl}>
|
|
94
|
+
<input name="email" type="email" required />
|
|
95
|
+
<textarea name="body"></textarea>
|
|
96
|
+
<button type="submit">Send</button>
|
|
97
|
+
</We8Form>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`<We8Form>` progressively enhances a native `<form>`: it POSTs through the
|
|
101
|
+
client and lifts a Cloudflare Turnstile `cf-turnstile-response` field
|
|
102
|
+
automatically. Without a key it stays inert and says so, instead of failing
|
|
103
|
+
silently.
|
|
104
|
+
|
|
105
|
+
`<ConsentBanner>` ships markup only, no styling: three named decisions, a slot
|
|
106
|
+
for your copy, and a `data-we8-consent-*` hook on every element. It stays hidden
|
|
107
|
+
for a visitor who has already answered.
|
|
108
|
+
|
|
109
|
+
`<VisitBeacon>` waits for analytics consent by default. The banner's write
|
|
110
|
+
dispatches `we8:consent` and the beacon fires on that event, with no page
|
|
111
|
+
reload. Pass `requireConsent={false}` only if measuring before consent is lawful
|
|
112
|
+
where your visitors are.
|
|
113
|
+
|
|
114
|
+
## Consent, end to end
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { recordConsent, readConsent, createClient } from '@we8/astro';
|
|
118
|
+
|
|
119
|
+
const client = createClient({ key, baseUrl: apiUrl });
|
|
120
|
+
if (readConsent() === null) showBanner();
|
|
121
|
+
|
|
122
|
+
// On "Accept all":
|
|
123
|
+
await recordConsent(client, { decision: 'accepted', analytics: true, performance: true });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`recordConsent` persists the decision locally FIRST, announces it, and only then
|
|
127
|
+
reports it to the API. It never rejects: a failed report leaves the visitor's
|
|
128
|
+
choice fully in effect.
|
|
129
|
+
|
|
130
|
+
## Environment contract
|
|
131
|
+
|
|
132
|
+
Two variables, both safe to expose:
|
|
133
|
+
|
|
134
|
+
- `WE8_API_URL`: the absolute API base. For a self-hosted we8 CMS that is its
|
|
135
|
+
origin with no path on the end, for example `https://cms.example.com`, or
|
|
136
|
+
`http://localhost:8787` in development. The CMS serves `/v1` at its own root,
|
|
137
|
+
so a trailing `/api` produces 404s.
|
|
138
|
+
- `WE8_PUBLISHABLE_KEY`: the publishable (`pk_`) key.
|
|
139
|
+
|
|
140
|
+
Astro only puts `PUBLIC_`-prefixed variables on `import.meta.env` by default. The
|
|
141
|
+
starter template widens `envPrefix` to accept `WE8_` as well, so one variable
|
|
142
|
+
serves both build-time fetches and the browser. If you would rather not, set
|
|
143
|
+
`PUBLIC_WE8_PUBLISHABLE_KEY` and let the components pick it up.
|
|
144
|
+
|
|
145
|
+
`SITE` is Astro's own `site` config (your public origin). It drives Astro's
|
|
146
|
+
canonical URLs and `@astrojs/sitemap`; it is distinct from the we8 site config's
|
|
147
|
+
`siteUrl` that `postUrl` reads.
|
|
148
|
+
|
|
149
|
+
A secret (`sk_`) key can be handed to `createBuildClient` directly for a
|
|
150
|
+
server-only build. Never let one reach the browser.
|
|
151
|
+
|
|
152
|
+
## The contract
|
|
153
|
+
|
|
154
|
+
The full, authoritative interface is the OpenAPI document your CMS serves at
|
|
155
|
+
`/v1/openapi.json`, keyless, and [the API doc](https://github.com/reveriext/we8-package/blob/main/docs/api.md) is the same
|
|
156
|
+
contract in prose. See [the frontend doc](https://github.com/reveriext/we8-package/blob/main/docs/frontend.md) for how this
|
|
157
|
+
package and the starter template fit together.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* <ConsentBanner /> - ask for analytics consent, record the answer, and get out
|
|
4
|
+
* of the way.
|
|
5
|
+
*
|
|
6
|
+
* The banner is hidden until the script confirms the visitor has NOT decided
|
|
7
|
+
* yet, so a returning visitor never sees a flash of it. Choosing writes the
|
|
8
|
+
* decision locally first, dispatches `we8:consent` (which releases anything
|
|
9
|
+
* gated on consent, such as <VisitBeacon />), and then reports the decision to
|
|
10
|
+
* the analytics API. A failed report never undoes the choice.
|
|
11
|
+
*
|
|
12
|
+
* Markup only, no styling: three named slots carry your copy, and every element
|
|
13
|
+
* has a `data-we8-consent-*` hook to style from your own stylesheet. Renders
|
|
14
|
+
* nothing without a key, so a build with no backend has no dead banner.
|
|
15
|
+
*
|
|
16
|
+
* The three decisions map to the API's vocabulary: `accepted` (analytics and
|
|
17
|
+
* performance), `partial` (analytics only), `declined` (neither).
|
|
18
|
+
*/
|
|
19
|
+
interface Props {
|
|
20
|
+
/** The publishable key. Defaults to `PUBLIC_WE8_PUBLISHABLE_KEY`. */
|
|
21
|
+
key?: string | undefined;
|
|
22
|
+
/** Absolute API base. Defaults to same-origin `/api`. */
|
|
23
|
+
apiUrl?: string | undefined;
|
|
24
|
+
acceptLabel?: string;
|
|
25
|
+
essentialLabel?: string;
|
|
26
|
+
declineLabel?: string;
|
|
27
|
+
/** Accessible name for the region. */
|
|
28
|
+
label?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
key = import.meta.env.PUBLIC_WE8_PUBLISHABLE_KEY,
|
|
33
|
+
apiUrl,
|
|
34
|
+
acceptLabel = 'Accept analytics',
|
|
35
|
+
essentialLabel = 'Analytics only',
|
|
36
|
+
declineLabel = 'Decline',
|
|
37
|
+
label = 'Cookie choices',
|
|
38
|
+
} = Astro.props;
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
{
|
|
42
|
+
key && (
|
|
43
|
+
<div
|
|
44
|
+
data-we8-consent
|
|
45
|
+
data-we8-key={key}
|
|
46
|
+
data-we8-api={apiUrl ?? ''}
|
|
47
|
+
role="region"
|
|
48
|
+
aria-label={label}
|
|
49
|
+
hidden
|
|
50
|
+
>
|
|
51
|
+
<div data-we8-consent-body>
|
|
52
|
+
<slot>
|
|
53
|
+
<p>
|
|
54
|
+
We count page views to see which pages are useful. Nothing is sold, and no
|
|
55
|
+
profile is built. You can decline and the site works exactly the same.
|
|
56
|
+
</p>
|
|
57
|
+
</slot>
|
|
58
|
+
</div>
|
|
59
|
+
<div data-we8-consent-actions>
|
|
60
|
+
<button type="button" data-we8-consent-choice="accepted">
|
|
61
|
+
{acceptLabel}
|
|
62
|
+
</button>
|
|
63
|
+
<button type="button" data-we8-consent-choice="partial">
|
|
64
|
+
{essentialLabel}
|
|
65
|
+
</button>
|
|
66
|
+
<button type="button" data-we8-consent-choice="declined">
|
|
67
|
+
{declineLabel}
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
import { createClient } from '@we8/client';
|
|
76
|
+
import { readConsent, recordConsent } from '@we8/astro';
|
|
77
|
+
|
|
78
|
+
const banner = document.querySelector<HTMLElement>('[data-we8-consent]');
|
|
79
|
+
const key = banner?.dataset.we8Key;
|
|
80
|
+
if (banner && key && !banner.dataset.we8Bound) {
|
|
81
|
+
banner.dataset.we8Bound = 'true';
|
|
82
|
+
|
|
83
|
+
// Only ask visitors who have not answered. A stored decision of any kind,
|
|
84
|
+
// including a decline, is an answer.
|
|
85
|
+
if (readConsent() === null) {
|
|
86
|
+
banner.hidden = false;
|
|
87
|
+
const apiUrl = banner.dataset.we8Api;
|
|
88
|
+
const client = createClient(apiUrl ? { key, baseUrl: apiUrl } : { key });
|
|
89
|
+
|
|
90
|
+
for (const el of banner.querySelectorAll('[data-we8-consent-choice]')) {
|
|
91
|
+
el.addEventListener('click', () => {
|
|
92
|
+
const choice = (el as HTMLElement).dataset.we8ConsentChoice;
|
|
93
|
+
const decision =
|
|
94
|
+
choice === 'accepted' ? 'accepted' : choice === 'partial' ? 'partial' : 'declined';
|
|
95
|
+
banner.hidden = true;
|
|
96
|
+
void recordConsent(client, {
|
|
97
|
+
decision,
|
|
98
|
+
analytics: decision !== 'declined',
|
|
99
|
+
performance: decision === 'accepted',
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* <VisitBeacon /> - fire a single page-visit beacon after the page loads.
|
|
4
|
+
*
|
|
5
|
+
* The key is a publishable (`pk_`) key, safe to embed in a shipped frontend by
|
|
6
|
+
* design. Pass it explicitly from your own config (recommended, and what the
|
|
7
|
+
* starter template does) or let it fall back to `PUBLIC_WE8_PUBLISHABLE_KEY`,
|
|
8
|
+
* which Astro exposes to the browser. It travels to the script through a data
|
|
9
|
+
* attribute, so no build-time env plumbing is needed in the consuming project.
|
|
10
|
+
*
|
|
11
|
+
* By default the beacon waits for analytics consent (see <ConsentBanner> in the
|
|
12
|
+
* starter, or `recordConsent` from this package). Set `requireConsent={false}`
|
|
13
|
+
* only if your jurisdiction and your privacy policy allow measuring first.
|
|
14
|
+
*
|
|
15
|
+
* Renders nothing when no key is available, so a misconfigured build degrades
|
|
16
|
+
* to "no analytics" rather than a runtime error.
|
|
17
|
+
*/
|
|
18
|
+
interface Props {
|
|
19
|
+
/** The publishable key. Defaults to `PUBLIC_WE8_PUBLISHABLE_KEY`. */
|
|
20
|
+
key?: string | undefined;
|
|
21
|
+
/** Absolute API base. Defaults to same-origin `/api`. */
|
|
22
|
+
apiUrl?: string | undefined;
|
|
23
|
+
/** Wait for analytics consent before sending. Default true. */
|
|
24
|
+
requireConsent?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
key = import.meta.env.PUBLIC_WE8_PUBLISHABLE_KEY,
|
|
29
|
+
apiUrl,
|
|
30
|
+
requireConsent = true,
|
|
31
|
+
} = Astro.props;
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
key && (
|
|
36
|
+
<span
|
|
37
|
+
data-we8-beacon
|
|
38
|
+
data-we8-key={key}
|
|
39
|
+
data-we8-api={apiUrl ?? ''}
|
|
40
|
+
data-we8-require-consent={requireConsent ? 'true' : 'false'}
|
|
41
|
+
hidden
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
import { createClient } from '@we8/client';
|
|
48
|
+
import { trackVisit } from '@we8/astro';
|
|
49
|
+
|
|
50
|
+
const el = document.querySelector<HTMLElement>('[data-we8-beacon]');
|
|
51
|
+
const key = el?.dataset.we8Key;
|
|
52
|
+
if (el && key && !el.dataset.we8Bound) {
|
|
53
|
+
el.dataset.we8Bound = 'true';
|
|
54
|
+
const apiUrl = el.dataset.we8Api;
|
|
55
|
+
const client = createClient(apiUrl ? { key, baseUrl: apiUrl } : { key });
|
|
56
|
+
trackVisit(client, { requireConsent: el.dataset.we8RequireConsent !== 'false' });
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* <We8Form formKey="contact"> ... form fields ... </We8Form>
|
|
4
|
+
*
|
|
5
|
+
* A progressively-enhanced form. The child fields go in the default slot; on
|
|
6
|
+
* submit, the JS handler POSTs them through the we8 client to
|
|
7
|
+
* `/v1/forms/{formKey}/submissions` and shows a status line. A Cloudflare
|
|
8
|
+
* Turnstile widget's `cf-turnstile-response` field is lifted automatically.
|
|
9
|
+
*
|
|
10
|
+
* The submission endpoint takes JSON, so JS is required to submit; without it
|
|
11
|
+
* the markup is still a valid, accessible form (put a mailto or other fallback
|
|
12
|
+
* in the slot if a no-JS path matters).
|
|
13
|
+
*
|
|
14
|
+
* The key is a publishable (`pk_`) key. Pass it explicitly from your own config
|
|
15
|
+
* (what the starter template does) or let it fall back to
|
|
16
|
+
* `PUBLIC_WE8_PUBLISHABLE_KEY`. With no key at all the form still renders and
|
|
17
|
+
* simply reports that submissions are not configured, so a fixtures-only build
|
|
18
|
+
* shows the real markup instead of crashing.
|
|
19
|
+
*/
|
|
20
|
+
interface Props {
|
|
21
|
+
formKey: string;
|
|
22
|
+
/** The publishable key. Defaults to `PUBLIC_WE8_PUBLISHABLE_KEY`. */
|
|
23
|
+
key?: string | undefined;
|
|
24
|
+
/** Absolute API base. Defaults to same-origin `/api`. */
|
|
25
|
+
apiUrl?: string | undefined;
|
|
26
|
+
class?: string | undefined;
|
|
27
|
+
successMessage?: string;
|
|
28
|
+
errorMessage?: string;
|
|
29
|
+
offlineMessage?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
formKey,
|
|
34
|
+
key = import.meta.env.PUBLIC_WE8_PUBLISHABLE_KEY,
|
|
35
|
+
apiUrl,
|
|
36
|
+
class: className,
|
|
37
|
+
successMessage = 'Thanks. Your message is on its way.',
|
|
38
|
+
errorMessage = 'Something went wrong. Please try again.',
|
|
39
|
+
offlineMessage = 'This form is not connected to a backend yet.',
|
|
40
|
+
} = Astro.props;
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<form
|
|
44
|
+
class={className}
|
|
45
|
+
data-we8-form-key={formKey}
|
|
46
|
+
data-we8-key={key ?? ''}
|
|
47
|
+
data-we8-api={apiUrl ?? ''}
|
|
48
|
+
data-we8-success={successMessage}
|
|
49
|
+
data-we8-error={errorMessage}
|
|
50
|
+
data-we8-offline={offlineMessage}
|
|
51
|
+
>
|
|
52
|
+
<slot />
|
|
53
|
+
<p data-we8-status role="status" aria-live="polite" hidden></p>
|
|
54
|
+
</form>
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
import { createClient } from '@we8/client';
|
|
58
|
+
import { enhanceForm } from '@we8/astro';
|
|
59
|
+
|
|
60
|
+
for (const el of document.querySelectorAll('form[data-we8-form-key]')) {
|
|
61
|
+
const form = el as HTMLFormElement;
|
|
62
|
+
// Guard against binding the same form twice if this module ever runs again.
|
|
63
|
+
if (form.dataset.we8Bound) continue;
|
|
64
|
+
form.dataset.we8Bound = 'true';
|
|
65
|
+
|
|
66
|
+
const formKey = form.dataset.we8FormKey;
|
|
67
|
+
if (!formKey) continue;
|
|
68
|
+
|
|
69
|
+
const status = form.querySelector('[data-we8-status]') as HTMLElement | null;
|
|
70
|
+
const show = (message: string) => {
|
|
71
|
+
if (!status) return;
|
|
72
|
+
status.hidden = false;
|
|
73
|
+
status.textContent = message;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const key = form.dataset.we8Key;
|
|
77
|
+
if (!key) {
|
|
78
|
+
// No key: keep the form inert but honest rather than silently dead.
|
|
79
|
+
form.addEventListener('submit', (event) => {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
show(form.dataset.we8Offline ?? 'This form is not connected to a backend yet.');
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const apiUrl = form.dataset.we8Api;
|
|
87
|
+
const client = createClient(apiUrl ? { key, baseUrl: apiUrl } : { key });
|
|
88
|
+
enhanceForm(form, client, {
|
|
89
|
+
formKey,
|
|
90
|
+
onSuccess: () => show(form.dataset.we8Success ?? 'Thanks.'),
|
|
91
|
+
onError: () => show(form.dataset.we8Error ?? 'Something went wrong.'),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
</script>
|
package/dist/beacon.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { We8Client } from '@we8/client';
|
|
2
|
+
import { type EventTargetLike } from './consent.js';
|
|
3
|
+
import { type StorageLike } from './session.js';
|
|
4
|
+
export interface BeaconOptions {
|
|
5
|
+
/** Defaults to `location.pathname`. */
|
|
6
|
+
pathname?: string;
|
|
7
|
+
/** Defaults to `document.referrer`. */
|
|
8
|
+
referrer?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Send a page-visit beacon for the current page, tagged with the persisted
|
|
12
|
+
* session id. Errors are swallowed: analytics must never break the page it
|
|
13
|
+
* measures.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sendVisit(client: We8Client, options?: BeaconOptions): Promise<void>;
|
|
16
|
+
export interface TrackVisitOptions extends BeaconOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Hold the beacon until the visitor has granted analytics consent. Default
|
|
19
|
+
* `true`: measuring before consent is the thing a consent banner exists to
|
|
20
|
+
* prevent, so opting out of the gate has to be deliberate.
|
|
21
|
+
*/
|
|
22
|
+
requireConsent?: boolean;
|
|
23
|
+
storage?: StorageLike | null;
|
|
24
|
+
target?: EventTargetLike | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Send the visit beacon, gated on analytics consent: now if consent already
|
|
28
|
+
* exists, otherwise the moment the visitor grants it. Returns a cleanup
|
|
29
|
+
* function that cancels a still-waiting beacon.
|
|
30
|
+
*/
|
|
31
|
+
export declare function trackVisit(client: We8Client, options?: TrackVisitOptions): () => void;
|
|
32
|
+
//# sourceMappingURL=beacon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beacon.d.ts","sourceRoot":"","sources":["../src/beacon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa7F;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,IAAI,CAUzF"}
|
package/dist/beacon.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { onAnalyticsConsent } from './consent.js';
|
|
2
|
+
import { getSessionId } from './session.js';
|
|
3
|
+
/**
|
|
4
|
+
* Send a page-visit beacon for the current page, tagged with the persisted
|
|
5
|
+
* session id. Errors are swallowed: analytics must never break the page it
|
|
6
|
+
* measures.
|
|
7
|
+
*/
|
|
8
|
+
export async function sendVisit(client, options = {}) {
|
|
9
|
+
try {
|
|
10
|
+
const pathname = options.pathname ?? (typeof location !== 'undefined' ? location.pathname : '/');
|
|
11
|
+
const referrer = options.referrer ?? (typeof document !== 'undefined' ? document.referrer : '');
|
|
12
|
+
await client.events.visit({
|
|
13
|
+
pathname,
|
|
14
|
+
sessionId: getSessionId(),
|
|
15
|
+
...(referrer ? { referrer } : {}),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Intentionally silent: a dropped beacon is invisible to the visitor.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Send the visit beacon, gated on analytics consent: now if consent already
|
|
24
|
+
* exists, otherwise the moment the visitor grants it. Returns a cleanup
|
|
25
|
+
* function that cancels a still-waiting beacon.
|
|
26
|
+
*/
|
|
27
|
+
export function trackVisit(client, options = {}) {
|
|
28
|
+
const { requireConsent = true, storage, target, ...beacon } = options;
|
|
29
|
+
if (!requireConsent) {
|
|
30
|
+
void sendVisit(client, beacon);
|
|
31
|
+
return () => undefined;
|
|
32
|
+
}
|
|
33
|
+
return onAnalyticsConsent(() => void sendVisit(client, beacon), {
|
|
34
|
+
...(storage === undefined ? {} : { storage }),
|
|
35
|
+
...(target === undefined ? {} : { target }),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=beacon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beacon.js","sourceRoot":"","sources":["../src/beacon.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAwB,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,YAAY,EAAoB,MAAM,cAAc,CAAC;AAS9D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAiB,EAAE,UAAyB,EAAE;IAC5E,IAAI,CAAC;QACH,MAAM,QAAQ,GACZ,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACxB,QAAQ;YACR,SAAS,EAAE,YAAY,EAAE;YACzB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;IACxE,CAAC;AACH,CAAC;AAaD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB,EAAE,UAA6B,EAAE;IAC3E,MAAM,EAAE,cAAc,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IACtE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,KAAK,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IACzB,CAAC;IACD,OAAO,kBAAkB,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;QAC9D,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5C,CAAC,CAAC;AACL,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type We8Client } from '@we8/client';
|
|
2
|
+
export interface We8AstroConfig {
|
|
3
|
+
/**
|
|
4
|
+
* The site API key. Use a publishable key (`pk_...`) for anything that ends
|
|
5
|
+
* up in the browser (the beacon, the consent banner, the form); a secret key
|
|
6
|
+
* (`sk_...`) is only for build-time server fetches and must never be shipped
|
|
7
|
+
* to a client.
|
|
8
|
+
*/
|
|
9
|
+
key: string;
|
|
10
|
+
/**
|
|
11
|
+
* Absolute API base URL for build-time (Node) fetches. For a self-hosted
|
|
12
|
+
* we8 CMS that is its origin with no path on the end, for example
|
|
13
|
+
* `https://cms.example.com`, since the CMS serves `/v1` at its own root.
|
|
14
|
+
* Required by `getPosts`/`getPost`, which run during `astro build` where
|
|
15
|
+
* there is no same-origin to default to.
|
|
16
|
+
*/
|
|
17
|
+
apiUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
export type EnvLike = Record<string, string | undefined>;
|
|
20
|
+
/** The environment variable holding the publishable key. */
|
|
21
|
+
export declare const KEY_ENV = "WE8_PUBLISHABLE_KEY";
|
|
22
|
+
/** The environment variable holding the absolute API base URL. */
|
|
23
|
+
export declare const API_URL_ENV = "WE8_API_URL";
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the we8 config from an env record (defaults to `process.env`).
|
|
26
|
+
* `WE8_PUBLISHABLE_KEY` is required; `WE8_API_URL` is optional here and only
|
|
27
|
+
* needed for build-time content fetches, which check it themselves.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveConfig(env?: EnvLike): We8AstroConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Like `resolveConfig`, but returns `null` instead of throwing when the
|
|
32
|
+
* environment is not configured. A template uses this to fall back to its own
|
|
33
|
+
* data source (fixtures, a different backend) rather than failing the build.
|
|
34
|
+
*/
|
|
35
|
+
export declare function tryResolveConfig(env?: EnvLike): We8AstroConfig | null;
|
|
36
|
+
/**
|
|
37
|
+
* Build a `@we8/client` for BUILD-TIME (Node) use. Unlike the browser default
|
|
38
|
+
* (same-origin `/api`), this needs an absolute `apiUrl`, since `astro build`
|
|
39
|
+
* runs off-origin. Pass `fetchImpl` in tests.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createBuildClient(config?: We8AstroConfig, fetchImpl?: typeof fetch): We8Client;
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEzD,4DAA4D;AAC5D,eAAO,MAAM,OAAO,wBAAwB,CAAC;AAC7C,kEAAkE;AAClE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAYzC;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,OAA0B,GAAG,cAAc,CAW7E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,OAA0B,GAAG,cAAc,GAAG,IAAI,CAKvF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,cAAgC,EACxC,SAAS,CAAC,EAAE,OAAO,KAAK,GACvB,SAAS,CAYX"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createClient } from '@we8/client';
|
|
2
|
+
/** The environment variable holding the publishable key. */
|
|
3
|
+
export const KEY_ENV = 'WE8_PUBLISHABLE_KEY';
|
|
4
|
+
/** The environment variable holding the absolute API base URL. */
|
|
5
|
+
export const API_URL_ENV = 'WE8_API_URL';
|
|
6
|
+
/**
|
|
7
|
+
* Read the default env record without depending on `@types/node`: reach for a
|
|
8
|
+
* `process.env` on `globalThis` if the runtime has one (Node during `astro
|
|
9
|
+
* build`), otherwise an empty record.
|
|
10
|
+
*/
|
|
11
|
+
function readDefaultEnv() {
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
return g.process?.env ?? {};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the we8 config from an env record (defaults to `process.env`).
|
|
17
|
+
* `WE8_PUBLISHABLE_KEY` is required; `WE8_API_URL` is optional here and only
|
|
18
|
+
* needed for build-time content fetches, which check it themselves.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveConfig(env = readDefaultEnv()) {
|
|
21
|
+
const key = env[KEY_ENV];
|
|
22
|
+
if (!key) {
|
|
23
|
+
throw new Error(`@we8/astro: ${KEY_ENV} is not set. Add your site's publishable key to the environment (for example in .env).`);
|
|
24
|
+
}
|
|
25
|
+
const apiUrl = env[API_URL_ENV];
|
|
26
|
+
// Build the object with the optional field present only when set, to stay
|
|
27
|
+
// clean under exactOptionalPropertyTypes.
|
|
28
|
+
return apiUrl ? { key, apiUrl } : { key };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Like `resolveConfig`, but returns `null` instead of throwing when the
|
|
32
|
+
* environment is not configured. A template uses this to fall back to its own
|
|
33
|
+
* data source (fixtures, a different backend) rather than failing the build.
|
|
34
|
+
*/
|
|
35
|
+
export function tryResolveConfig(env = readDefaultEnv()) {
|
|
36
|
+
const key = env[KEY_ENV];
|
|
37
|
+
const apiUrl = env[API_URL_ENV];
|
|
38
|
+
if (!key || !apiUrl)
|
|
39
|
+
return null;
|
|
40
|
+
return { key, apiUrl };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a `@we8/client` for BUILD-TIME (Node) use. Unlike the browser default
|
|
44
|
+
* (same-origin `/api`), this needs an absolute `apiUrl`, since `astro build`
|
|
45
|
+
* runs off-origin. Pass `fetchImpl` in tests.
|
|
46
|
+
*/
|
|
47
|
+
export function createBuildClient(config = resolveConfig(), fetchImpl) {
|
|
48
|
+
if (!config.apiUrl) {
|
|
49
|
+
throw new Error(`@we8/astro: ${API_URL_ENV} is required for build-time fetch (getPosts/getPost). ` +
|
|
50
|
+
'Set it to your CMS origin, for example https://cms.example.com.');
|
|
51
|
+
}
|
|
52
|
+
return createClient({
|
|
53
|
+
key: config.key,
|
|
54
|
+
baseUrl: config.apiUrl,
|
|
55
|
+
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAsB3D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAC7C,kEAAkE;AAClE,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAEzC;;;;GAIG;AACH,SAAS,cAAc;IACrB,MAAM,CAAC,GAAG,UAA6C,CAAC;IACxD,OAAO,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAe,cAAc,EAAE;IAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,eAAe,OAAO,wFAAwF,CAC/G,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,0EAA0E;IAC1E,0CAA0C;IAC1C,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAe,cAAc,EAAE;IAC9D,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAyB,aAAa,EAAE,EACxC,SAAwB;IAExB,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,eAAe,WAAW,wDAAwD;YAChF,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,YAAY,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,OAAO,EAAE,MAAM,CAAC,MAAM;QACtB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3C,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ConsentDecision, We8Client } from '@we8/client';
|
|
2
|
+
import type { StorageLike } from './session.js';
|
|
3
|
+
/** Where a visitor's consent decision is persisted in the browser. */
|
|
4
|
+
export declare const CONSENT_STORAGE_KEY = "we8:consent";
|
|
5
|
+
/**
|
|
6
|
+
* The DOM event dispatched (and listened for) when a decision is recorded, so
|
|
7
|
+
* anything gated on consent (the visit beacon, an embedded map, a third-party
|
|
8
|
+
* script) can start the moment the visitor says yes, without a page reload.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CONSENT_EVENT = "we8:consent";
|
|
11
|
+
/** A visitor's stored consent decision. */
|
|
12
|
+
export interface ConsentState {
|
|
13
|
+
decision: ConsentDecision;
|
|
14
|
+
analytics: boolean;
|
|
15
|
+
performance: boolean;
|
|
16
|
+
/** ISO timestamp of when the visitor decided. */
|
|
17
|
+
decidedAt: string;
|
|
18
|
+
}
|
|
19
|
+
/** The minimal event-target surface `recordConsent` and `onConsent` need; `window` satisfies it. */
|
|
20
|
+
export interface EventTargetLike {
|
|
21
|
+
addEventListener(type: string, listener: () => void): void;
|
|
22
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
23
|
+
dispatchEvent?(event: Event): boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read the stored decision, or `null` when the visitor has not chosen yet (so
|
|
27
|
+
* a banner knows to show itself). A corrupt or unreadable store also reads as
|
|
28
|
+
* `null`: an unparseable decision is not a decision.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readConsent(storage?: StorageLike | null): ConsentState | null;
|
|
31
|
+
/** True when the visitor has granted analytics consent. Convenience for gating a beacon. */
|
|
32
|
+
export declare function analyticsAllowed(storage?: StorageLike | null): boolean;
|
|
33
|
+
/** Persist a decision locally and announce it. Storage failures are non-fatal. */
|
|
34
|
+
export declare function writeConsent(state: ConsentState, storage?: StorageLike | null, target?: EventTargetLike | null): void;
|
|
35
|
+
export interface RecordConsentOptions {
|
|
36
|
+
storage?: StorageLike | null;
|
|
37
|
+
target?: EventTargetLike | null;
|
|
38
|
+
/** Override the session id (defaults to the persisted visitor session id). */
|
|
39
|
+
sessionId?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Record a visitor's consent decision: persist it locally FIRST (so the
|
|
43
|
+
* decision is honored even if the network is down), announce it, then report
|
|
44
|
+
* it to the analytics API. Never rejects; a failed report is invisible to the
|
|
45
|
+
* visitor and does not weaken the decision.
|
|
46
|
+
*/
|
|
47
|
+
export declare function recordConsent(client: We8Client, choice: {
|
|
48
|
+
decision: ConsentDecision;
|
|
49
|
+
analytics: boolean;
|
|
50
|
+
performance: boolean;
|
|
51
|
+
}, options?: RecordConsentOptions): Promise<ConsentState>;
|
|
52
|
+
/**
|
|
53
|
+
* Run `handler` once analytics consent exists: immediately if it already does,
|
|
54
|
+
* otherwise on the next consent decision that grants it. Returns a cleanup
|
|
55
|
+
* function that detaches the listener.
|
|
56
|
+
*/
|
|
57
|
+
export declare function onAnalyticsConsent(handler: () => void, options?: {
|
|
58
|
+
storage?: StorageLike | null;
|
|
59
|
+
target?: EventTargetLike | null;
|
|
60
|
+
}): () => void;
|
|
61
|
+
//# sourceMappingURL=consent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC3D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC9D,aAAa,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC;CACvC;AAmBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,WAAW,GAAG,IAAuB,GAAG,YAAY,GAAG,IAAI,CAqB/F;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,WAAW,GAAG,IAAuB,GAAG,OAAO,CAExF;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,WAAW,GAAG,IAAuB,EAC9C,MAAM,GAAE,eAAe,GAAG,IAAsB,GAC/C,IAAI,CAYN;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,EAC/E,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,YAAY,CAAC,CAkBvB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;CAAO,GAC9E,MAAM,IAAI,CAgBZ"}
|