@convex-internal/web-analytics 2.0.1 → 2.2.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 +36 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/posthog.d.ts +1 -0
- package/dist/posthog.js +10 -0
- package/dist/react/ConsentBanner.js +13 -2
- package/dist/svelte/WebAnalytics.svelte +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,15 +29,35 @@ capture("www-login", undefined, {
|
|
|
29
29
|
});
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
###
|
|
32
|
+
### `getDistinctId`
|
|
33
|
+
|
|
34
|
+
PostHog's identifier for the current visitor. After consent is granted it is
|
|
35
|
+
stored in a cookie on `.convex.dev`, so every Convex site in the browser uses
|
|
36
|
+
the same ID.
|
|
37
|
+
|
|
38
|
+
When this helper returns a value, consent has been granted. Pass that ID to a
|
|
39
|
+
third-party tool so you can match that tool's events or sessions, such as a
|
|
40
|
+
support chat, back to the same visitor in PostHog.
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`{ decision: "accepted" | "declined" }` just before the choice is saved.
|
|
42
|
+
It's expected that a call on first load is always `undefined`, so call again
|
|
43
|
+
after consent is granted.
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
It returns one of:
|
|
46
|
+
|
|
47
|
+
- **`undefined`**. PostHog is not initialized (localhost, non-production, or
|
|
48
|
+
still loading), or consent has not been granted. Before consent, PostHog
|
|
49
|
+
still has a cookieless ID, but it is not stable across sites or even page
|
|
50
|
+
reloads, so returning it would create noise rather than being useful.
|
|
51
|
+
- **Anonymous UUID**, such as `"019a7d8f-9190-7844-a5c0-73d461a3a5d1"`.
|
|
52
|
+
Consent has been granted but this browser has not logged into the
|
|
53
|
+
dashboard where the user is identified.
|
|
54
|
+
- **Convex profile ID**, such as `"4281"`. Consent has been granted and this
|
|
55
|
+
browser has logged into the dashboard.
|
|
56
|
+
|
|
57
|
+
### Consent banner
|
|
58
|
+
|
|
59
|
+
The banner is visible when `consent` is `"undecided"`. It is hidden while
|
|
60
|
+
`consent` is `"unknown"`, `"consented"`, or `"declined"`.
|
|
41
61
|
|
|
42
62
|
Theme with `--wa-banner-*` variables on `.wa-banner`:
|
|
43
63
|
|
|
@@ -66,6 +86,15 @@ Theme with `--wa-banner-*` variables on `.wa-banner`:
|
|
|
66
86
|
|
|
67
87
|
Buttons default to 80% opacity and go to 100% on hover or keyboard focus.
|
|
68
88
|
|
|
89
|
+
### Consent events
|
|
90
|
+
|
|
91
|
+
`cookie_consent_shown` is recorded when the banner appears.
|
|
92
|
+
`cookie_consent_decided` is recorded with
|
|
93
|
+
`{ decision: "accepted" | "declined", source: "banner" }` just before the
|
|
94
|
+
choice is saved, and with `{ decision: "declined", source: "gpc" }` when
|
|
95
|
+
the GPC preference is applied. Historic events without `source` are banner
|
|
96
|
+
decisions.
|
|
97
|
+
|
|
69
98
|
### Cookie behavior
|
|
70
99
|
|
|
71
100
|
Once the cookie has been read, `consent` is resolved in this order:
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/posthog.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ConsentStatus } from "./consent.js";
|
|
2
2
|
export declare const POSTHOG_PUBLIC_PROJECT_TOKEN = "phc_JDNTRxeh2li2sQTRO0IcOYMJcp8fPs5nTK9TU751nQK";
|
|
3
3
|
export declare function initPostHog(): void;
|
|
4
|
+
export declare function getDistinctId(): string | undefined;
|
|
4
5
|
export declare function syncPostHogConsent(consent: ConsentStatus): void;
|
package/dist/posthog.js
CHANGED
|
@@ -32,6 +32,16 @@ export function initPostHog() {
|
|
|
32
32
|
person_profiles: "identified_only",
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
export function getDistinctId() {
|
|
36
|
+
if (!posthog.__loaded) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
// Cookieless IDs change across sites and reloads, so aren't useful to return.
|
|
40
|
+
if (posthog.get_explicit_consent_status() !== "granted") {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return posthog.get_distinct_id();
|
|
44
|
+
}
|
|
35
45
|
export function syncPostHogConsent(consent) {
|
|
36
46
|
if (!posthog.__loaded) {
|
|
37
47
|
return;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef } from "react";
|
|
4
|
-
import { bannerCopy } from "../strings.js";
|
|
5
4
|
import { capture } from "../capture.js";
|
|
5
|
+
import { hasGPC } from "../consent.js";
|
|
6
|
+
import { bannerCopy } from "../strings.js";
|
|
6
7
|
import { useConsent } from "./useConsent.js";
|
|
7
8
|
import "../banner.css";
|
|
8
9
|
export function ConsentBanner() {
|
|
9
10
|
const { consent, setConsent } = useConsent();
|
|
10
11
|
const shouldShow = consent === "undecided";
|
|
11
12
|
const wasShowing = useRef(false);
|
|
13
|
+
const recordedGPCDecision = useRef(false);
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
if (shouldShow && !wasShowing.current) {
|
|
14
16
|
// Captures once when the banner becomes visible, not on later renders.
|
|
@@ -16,13 +18,22 @@ export function ConsentBanner() {
|
|
|
16
18
|
}
|
|
17
19
|
wasShowing.current = shouldShow;
|
|
18
20
|
}, [shouldShow]);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (consent === "declined" && hasGPC() && !recordedGPCDecision.current) {
|
|
23
|
+
recordedGPCDecision.current = true;
|
|
24
|
+
capture("cookie_consent_decided", {
|
|
25
|
+
decision: "declined",
|
|
26
|
+
source: "gpc",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}, [consent]);
|
|
19
30
|
if (!shouldShow) {
|
|
20
31
|
return null;
|
|
21
32
|
}
|
|
22
33
|
const decide = (decision) => {
|
|
23
34
|
try {
|
|
24
35
|
// Captures before the cookie write so decline is captured cookieless.
|
|
25
|
-
capture("cookie_consent_decided", { decision });
|
|
36
|
+
capture("cookie_consent_decided", { decision, source: "banner" });
|
|
26
37
|
}
|
|
27
38
|
finally {
|
|
28
39
|
setConsent(decision === "accepted" ? "consented" : "declined");
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
let isReady = $state(false);
|
|
11
11
|
let cookieValue = $state<boolean | undefined>(undefined);
|
|
12
|
+
let recordedGPCDecision = false;
|
|
12
13
|
|
|
13
14
|
const consent = $derived(resolveConsent(isReady, cookieValue, hasGPC()));
|
|
14
15
|
const shouldShowBanner = $derived(consent === "undecided");
|
|
@@ -60,6 +61,17 @@
|
|
|
60
61
|
wasShowingBanner = shouldShowBanner;
|
|
61
62
|
});
|
|
62
63
|
|
|
64
|
+
$effect(() => {
|
|
65
|
+
if (consent === "declined" && hasGPC() && !recordedGPCDecision) {
|
|
66
|
+
// GPC hides the banner, so record the decline here instead of a click.
|
|
67
|
+
recordedGPCDecision = true;
|
|
68
|
+
capture("cookie_consent_decided", {
|
|
69
|
+
decision: "declined",
|
|
70
|
+
source: "gpc",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
63
75
|
function setConsent(status: "consented" | "declined") {
|
|
64
76
|
setCookie(COOKIE_NAME, status === "consented" ? "true" : "false");
|
|
65
77
|
cookieValue = status === "consented";
|
|
@@ -68,7 +80,7 @@
|
|
|
68
80
|
function decide(decision: "accepted" | "declined") {
|
|
69
81
|
try {
|
|
70
82
|
// Captures before the cookie write so decline is captured cookieless.
|
|
71
|
-
capture("cookie_consent_decided", { decision });
|
|
83
|
+
capture("cookie_consent_decided", { decision, source: "banner" });
|
|
72
84
|
} finally {
|
|
73
85
|
setConsent(decision === "accepted" ? "consented" : "declined");
|
|
74
86
|
}
|