@c15t/scripts 2.2.0-canary-20260727202135 → 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/dist/engine/runtime.cjs +3 -0
- package/dist/engine/runtime.js +3 -0
- package/dist/vendors/analytics/posthog.cjs +52 -20
- package/dist/vendors/analytics/posthog.js +52 -20
- package/dist-types/types.d.ts +4 -0
- package/dist-types/vendors/analytics/posthog.d.ts +33 -15
- package/docs/frameworks/javascript/script-loader.md +2 -0
- package/docs/frameworks/next/script-loader.md +3 -1
- package/docs/frameworks/react/script-loader.md +2 -0
- package/docs/integrations/posthog.md +2 -2
- package/docs/shared/react/guides/script-loader.md +2 -0
- package/package.json +2 -2
package/dist/engine/runtime.cjs
CHANGED
|
@@ -144,6 +144,8 @@ function executeStep(step) {
|
|
|
144
144
|
}
|
|
145
145
|
case 'setGlobalPath':
|
|
146
146
|
{
|
|
147
|
+
const rootGlobal = step.path[0];
|
|
148
|
+
if (step.ifGlobalIsQueue && (!rootGlobal || !Array.isArray(win[rootGlobal]))) break;
|
|
147
149
|
const pathTarget = getPathTarget(win, step.path);
|
|
148
150
|
if (!pathTarget) break;
|
|
149
151
|
pathTarget.target[pathTarget.key] = cloneStepValue(step.value);
|
|
@@ -214,6 +216,7 @@ function executeStep(step) {
|
|
|
214
216
|
{
|
|
215
217
|
const target = win[step.target];
|
|
216
218
|
if (null === target || 'object' != typeof target && 'function' != typeof target) break;
|
|
219
|
+
if (step.ifGlobalIsQueue && !Array.isArray(target)) break;
|
|
217
220
|
const targetRecord = target;
|
|
218
221
|
for (const method of step.methods)targetRecord[method.name] = 'return' === method.behavior ? ()=>cloneStepValue(method.value) : ()=>{};
|
|
219
222
|
break;
|
package/dist/engine/runtime.js
CHANGED
|
@@ -112,6 +112,8 @@ function executeStep(step) {
|
|
|
112
112
|
}
|
|
113
113
|
case 'setGlobalPath':
|
|
114
114
|
{
|
|
115
|
+
const rootGlobal = step.path[0];
|
|
116
|
+
if (step.ifGlobalIsQueue && (!rootGlobal || !Array.isArray(win[rootGlobal]))) break;
|
|
115
117
|
const pathTarget = getPathTarget(win, step.path);
|
|
116
118
|
if (!pathTarget) break;
|
|
117
119
|
pathTarget.target[pathTarget.key] = cloneStepValue(step.value);
|
|
@@ -182,6 +184,7 @@ function executeStep(step) {
|
|
|
182
184
|
{
|
|
183
185
|
const target = win[step.target];
|
|
184
186
|
if (null === target || 'object' != typeof target && 'function' != typeof target) break;
|
|
187
|
+
if (step.ifGlobalIsQueue && !Array.isArray(target)) break;
|
|
185
188
|
const targetRecord = target;
|
|
186
189
|
for (const method of step.methods)targetRecord[method.name] = 'return' === method.behavior ? ()=>cloneStepValue(method.value) : ()=>{};
|
|
187
190
|
break;
|
|
@@ -87,29 +87,51 @@ const posthogManifest = {
|
|
|
87
87
|
{
|
|
88
88
|
type: 'setGlobal',
|
|
89
89
|
name: 'posthog',
|
|
90
|
-
value:
|
|
90
|
+
value: [],
|
|
91
91
|
ifUndefined: true
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
type: 'setGlobalPath',
|
|
95
|
+
path: [
|
|
96
|
+
'posthog',
|
|
97
|
+
'_i'
|
|
98
|
+
],
|
|
99
|
+
value: [
|
|
100
|
+
[
|
|
101
|
+
'{{id}}',
|
|
102
|
+
'{{initOptions}}',
|
|
103
|
+
'posthog'
|
|
104
|
+
]
|
|
105
|
+
],
|
|
106
|
+
ifGlobalIsQueue: true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: 'setGlobalPath',
|
|
110
|
+
path: [
|
|
111
|
+
'posthog',
|
|
112
|
+
'people'
|
|
113
|
+
],
|
|
114
|
+
value: [],
|
|
115
|
+
ifGlobalIsQueue: true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'defineQueueMethods',
|
|
119
|
+
target: 'posthog',
|
|
120
|
+
methods: [
|
|
121
|
+
'capture',
|
|
122
|
+
'opt_in_capturing',
|
|
123
|
+
'opt_out_capturing'
|
|
124
|
+
]
|
|
125
|
+
},
|
|
93
126
|
{
|
|
94
127
|
type: 'defineGlobalMethods',
|
|
95
128
|
target: 'posthog',
|
|
129
|
+
ifGlobalIsQueue: true,
|
|
96
130
|
methods: [
|
|
97
131
|
{
|
|
98
132
|
name: 'init',
|
|
99
133
|
behavior: 'noop'
|
|
100
134
|
},
|
|
101
|
-
{
|
|
102
|
-
name: 'capture',
|
|
103
|
-
behavior: 'noop'
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: 'opt_in_capturing',
|
|
107
|
-
behavior: 'noop'
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: 'opt_out_capturing',
|
|
111
|
-
behavior: 'noop'
|
|
112
|
-
},
|
|
113
135
|
{
|
|
114
136
|
name: 'get_explicit_consent_status',
|
|
115
137
|
behavior: 'return',
|
|
@@ -130,14 +152,24 @@ const posthogManifest = {
|
|
|
130
152
|
}
|
|
131
153
|
}
|
|
132
154
|
],
|
|
133
|
-
|
|
155
|
+
onBeforeLoadGranted: [
|
|
134
156
|
{
|
|
135
|
-
type: '
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
157
|
+
type: 'pushToQueue',
|
|
158
|
+
queue: 'posthog',
|
|
159
|
+
value: [
|
|
160
|
+
'opt_in_capturing',
|
|
161
|
+
{
|
|
162
|
+
captureEventName: null
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
onBeforeLoadDenied: [
|
|
168
|
+
{
|
|
169
|
+
type: 'pushToQueue',
|
|
170
|
+
queue: 'posthog',
|
|
171
|
+
value: [
|
|
172
|
+
'opt_out_capturing'
|
|
141
173
|
]
|
|
142
174
|
}
|
|
143
175
|
],
|
|
@@ -54,29 +54,51 @@ const posthogManifest = {
|
|
|
54
54
|
{
|
|
55
55
|
type: 'setGlobal',
|
|
56
56
|
name: 'posthog',
|
|
57
|
-
value:
|
|
57
|
+
value: [],
|
|
58
58
|
ifUndefined: true
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
type: 'setGlobalPath',
|
|
62
|
+
path: [
|
|
63
|
+
'posthog',
|
|
64
|
+
'_i'
|
|
65
|
+
],
|
|
66
|
+
value: [
|
|
67
|
+
[
|
|
68
|
+
'{{id}}',
|
|
69
|
+
'{{initOptions}}',
|
|
70
|
+
'posthog'
|
|
71
|
+
]
|
|
72
|
+
],
|
|
73
|
+
ifGlobalIsQueue: true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'setGlobalPath',
|
|
77
|
+
path: [
|
|
78
|
+
'posthog',
|
|
79
|
+
'people'
|
|
80
|
+
],
|
|
81
|
+
value: [],
|
|
82
|
+
ifGlobalIsQueue: true
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'defineQueueMethods',
|
|
86
|
+
target: 'posthog',
|
|
87
|
+
methods: [
|
|
88
|
+
'capture',
|
|
89
|
+
'opt_in_capturing',
|
|
90
|
+
'opt_out_capturing'
|
|
91
|
+
]
|
|
92
|
+
},
|
|
60
93
|
{
|
|
61
94
|
type: 'defineGlobalMethods',
|
|
62
95
|
target: 'posthog',
|
|
96
|
+
ifGlobalIsQueue: true,
|
|
63
97
|
methods: [
|
|
64
98
|
{
|
|
65
99
|
name: 'init',
|
|
66
100
|
behavior: 'noop'
|
|
67
101
|
},
|
|
68
|
-
{
|
|
69
|
-
name: 'capture',
|
|
70
|
-
behavior: 'noop'
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'opt_in_capturing',
|
|
74
|
-
behavior: 'noop'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'opt_out_capturing',
|
|
78
|
-
behavior: 'noop'
|
|
79
|
-
},
|
|
80
102
|
{
|
|
81
103
|
name: 'get_explicit_consent_status',
|
|
82
104
|
behavior: 'return',
|
|
@@ -97,14 +119,24 @@ const posthogManifest = {
|
|
|
97
119
|
}
|
|
98
120
|
}
|
|
99
121
|
],
|
|
100
|
-
|
|
122
|
+
onBeforeLoadGranted: [
|
|
101
123
|
{
|
|
102
|
-
type: '
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
type: 'pushToQueue',
|
|
125
|
+
queue: 'posthog',
|
|
126
|
+
value: [
|
|
127
|
+
'opt_in_capturing',
|
|
128
|
+
{
|
|
129
|
+
captureEventName: null
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
onBeforeLoadDenied: [
|
|
135
|
+
{
|
|
136
|
+
type: 'pushToQueue',
|
|
137
|
+
queue: 'posthog',
|
|
138
|
+
value: [
|
|
139
|
+
'opt_out_capturing'
|
|
108
140
|
]
|
|
109
141
|
}
|
|
110
142
|
],
|
package/dist-types/types.d.ts
CHANGED
|
@@ -119,6 +119,8 @@ export interface SetGlobalPathStep {
|
|
|
119
119
|
path: string[];
|
|
120
120
|
/** Value to assign at the target path */
|
|
121
121
|
value: unknown;
|
|
122
|
+
/** Only assign while the root global is still a snippet queue array. */
|
|
123
|
+
ifGlobalIsQueue?: boolean;
|
|
122
124
|
}
|
|
123
125
|
export interface DefineQueueMethodsStep {
|
|
124
126
|
type: 'defineQueueMethods';
|
|
@@ -182,6 +184,8 @@ export interface DefineGlobalMethodsStep {
|
|
|
182
184
|
/** Global object name to receive the methods */
|
|
183
185
|
target: string;
|
|
184
186
|
methods: Array<GlobalMethodBehavior>;
|
|
187
|
+
/** Only define methods while the target is still a snippet queue array. */
|
|
188
|
+
ifGlobalIsQueue?: boolean;
|
|
185
189
|
}
|
|
186
190
|
export interface ConstructGlobalStep {
|
|
187
191
|
type: 'constructGlobal';
|
|
@@ -12,6 +12,12 @@ declare global {
|
|
|
12
12
|
opt_out_capturing: () => void;
|
|
13
13
|
get_explicit_consent_status: () => string;
|
|
14
14
|
capture: (event: string, properties?: Record<string, unknown>) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Pending `[token, config, instanceName]` init tuples, as seeded by
|
|
17
|
+
* the official PostHog snippet. `array.js` only installs its runtime
|
|
18
|
+
* over an existing `window.posthog` when this is an array.
|
|
19
|
+
*/
|
|
20
|
+
_i?: unknown[][];
|
|
15
21
|
};
|
|
16
22
|
}
|
|
17
23
|
}
|
|
@@ -33,23 +39,29 @@ export declare const posthogManifest: {
|
|
|
33
39
|
readonly bootstrap: [{
|
|
34
40
|
readonly type: 'setGlobal';
|
|
35
41
|
readonly name: 'posthog';
|
|
36
|
-
readonly value:
|
|
42
|
+
readonly value: readonly [];
|
|
37
43
|
readonly ifUndefined: true;
|
|
44
|
+
}, {
|
|
45
|
+
readonly type: 'setGlobalPath';
|
|
46
|
+
readonly path: ["posthog", "_i"];
|
|
47
|
+
readonly value: readonly [readonly ["{{id}}", "{{initOptions}}", "posthog"]];
|
|
48
|
+
readonly ifGlobalIsQueue: true;
|
|
49
|
+
}, {
|
|
50
|
+
readonly type: 'setGlobalPath';
|
|
51
|
+
readonly path: ["posthog", "people"];
|
|
52
|
+
readonly value: readonly [];
|
|
53
|
+
readonly ifGlobalIsQueue: true;
|
|
54
|
+
}, {
|
|
55
|
+
readonly type: 'defineQueueMethods';
|
|
56
|
+
readonly target: 'posthog';
|
|
57
|
+
readonly methods: ["capture", "opt_in_capturing", "opt_out_capturing"];
|
|
38
58
|
}, {
|
|
39
59
|
readonly type: 'defineGlobalMethods';
|
|
40
60
|
readonly target: 'posthog';
|
|
61
|
+
readonly ifGlobalIsQueue: true;
|
|
41
62
|
readonly methods: [{
|
|
42
63
|
readonly name: 'init';
|
|
43
64
|
readonly behavior: 'noop';
|
|
44
|
-
}, {
|
|
45
|
-
readonly name: 'capture';
|
|
46
|
-
readonly behavior: 'noop';
|
|
47
|
-
}, {
|
|
48
|
-
readonly name: 'opt_in_capturing';
|
|
49
|
-
readonly behavior: 'noop';
|
|
50
|
-
}, {
|
|
51
|
-
readonly name: 'opt_out_capturing';
|
|
52
|
-
readonly behavior: 'noop';
|
|
53
65
|
}, {
|
|
54
66
|
readonly name: 'get_explicit_consent_status';
|
|
55
67
|
readonly behavior: 'return';
|
|
@@ -66,11 +78,17 @@ export declare const posthogManifest: {
|
|
|
66
78
|
readonly 'data-ui-host': '{{uiHost}}';
|
|
67
79
|
};
|
|
68
80
|
}];
|
|
69
|
-
readonly
|
|
70
|
-
readonly type: '
|
|
71
|
-
readonly
|
|
72
|
-
readonly
|
|
73
|
-
|
|
81
|
+
readonly onBeforeLoadGranted: [{
|
|
82
|
+
readonly type: 'pushToQueue';
|
|
83
|
+
readonly queue: 'posthog';
|
|
84
|
+
readonly value: readonly ["opt_in_capturing", {
|
|
85
|
+
readonly captureEventName: null;
|
|
86
|
+
}];
|
|
87
|
+
}];
|
|
88
|
+
readonly onBeforeLoadDenied: [{
|
|
89
|
+
readonly type: 'pushToQueue';
|
|
90
|
+
readonly queue: 'posthog';
|
|
91
|
+
readonly value: readonly ["opt_out_capturing"];
|
|
74
92
|
}];
|
|
75
93
|
readonly onLoadGranted: [{
|
|
76
94
|
readonly type: 'callGlobal';
|
|
@@ -252,6 +252,8 @@ Control where the script is injected and whether the element id is anonymized:
|
|
|
252
252
|
|
|
253
253
|
Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
|
|
254
254
|
|
|
255
|
+
You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
|
|
256
|
+
|
|
255
257
|
## Dynamic Management
|
|
256
258
|
|
|
257
259
|
Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
|
|
@@ -298,6 +298,8 @@ Control where the script is injected and whether the element id is anonymized:
|
|
|
298
298
|
|
|
299
299
|
Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
|
|
300
300
|
|
|
301
|
+
You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
|
|
302
|
+
|
|
301
303
|
## Dynamic Management
|
|
302
304
|
|
|
303
305
|
Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
|
|
@@ -487,7 +489,7 @@ For iframe-only embeds, use the [iframe blocking](/docs/frameworks/next/iframe-b
|
|
|
487
489
|
* The provider and any component that calls `useConsentManager()` must be client components.
|
|
488
490
|
* Keep vendor ids out of static examples when they differ per environment — read them from `process.env.NEXT_PUBLIC_*` or runtime config.
|
|
489
491
|
* If a script must be available before a page becomes interactive, prefer a built-in helper that models denied-consent defaults rather than adding a separate `next/script` tag.
|
|
490
|
-
* If you use CSP nonces,
|
|
492
|
+
* If you use CSP nonces, set `nonce` on the provider options so c15t applies it to every injected script element and the theme stylesheet. See [Content Security Policy](/docs/frameworks/next/components/consent-manager-provider#content-security-policy) for reading the nonce in the App Router.
|
|
491
493
|
|
|
492
494
|
## API Reference
|
|
493
495
|
|
|
@@ -296,6 +296,8 @@ Control where the script is injected and whether the element id is anonymized:
|
|
|
296
296
|
|
|
297
297
|
Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
|
|
298
298
|
|
|
299
|
+
You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
|
|
300
|
+
|
|
299
301
|
## Dynamic Management
|
|
300
302
|
|
|
301
303
|
Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
|
|
@@ -83,7 +83,7 @@ This is the recommended approach if you're using the PostHog JS SDK; it's common
|
|
|
83
83
|
|
|
84
84
|
If you want to load PostHog via a script tag, it's recommended to use this approach.
|
|
85
85
|
|
|
86
|
-
1. **Choose a region and loading mode** The c15t helper
|
|
86
|
+
1. **Choose a region and loading mode** The c15t helper seeds PostHog's initialization queue, loads the bootstrap script, and synchronizes consent through `posthog.opt_in_capturing()` / `posthog.opt_out_capturing()`. You do not need to call `posthog.init()` separately.
|
|
87
87
|
|
|
88
88
|
Use region to keep PostHog's API, UI, and bootstrap script hosts aligned. c15t defaults to region: 'eu'; set region: 'us' for PostHog Cloud US. You can still pass apiHost, uiHost, or scriptUrl for self-hosted or proxied setups.
|
|
89
89
|
|
|
@@ -221,7 +221,7 @@ posthog({
|
|
|
221
221
|
The behavior depends on which pattern you chose:
|
|
222
222
|
|
|
223
223
|
* **SDK Implementation** — your app loaded `posthog-js` itself, so `posthog.capture(...)` is available once your SDK setup has run. c15t calls `opt_in_capturing()` / `opt_out_capturing()` for you. Pending events before c15t syncs consent may be dropped; after denial, PostHog captures cookieless events.
|
|
224
|
-
* **Script Implementation with `loadMode: 'always'`** — `window.posthog` is defined early.
|
|
224
|
+
* **Script Implementation with `loadMode: 'always'`** — `window.posthog` is defined early as a queue. Calls to `posthog.capture(...)` before the bootstrap finishes are replayed after the SDK installs instead of being dropped. c15t queues the current consent decision ahead of those calls, then keeps calling `opt_in_capturing()` / `opt_out_capturing()` as consent changes. After denial, PostHog captures cookieless events when your PostHog project supports cookieless mode.
|
|
225
225
|
* **Script Implementation with `loadMode: 'after-consent'`** — PostHog is unavailable until measurement consent is granted. Guard `posthog.capture(...)` calls or call them only after consent.
|
|
226
226
|
|
|
227
227
|
> ⚠️ **Warning:**
|
|
@@ -229,6 +229,8 @@ Control where the script is injected and whether the element id is anonymized:
|
|
|
229
229
|
|
|
230
230
|
Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
|
|
231
231
|
|
|
232
|
+
You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
|
|
233
|
+
|
|
232
234
|
## Dynamic Management
|
|
233
235
|
|
|
234
236
|
Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c15t/scripts",
|
|
3
|
-
"version": "2.2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Consent-aware script integrations for Google Tag Manager, Google Consent Mode v2, GA4, Google Ads, Meta Pixel, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"c15t",
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
"devDependencies": {
|
|
254
254
|
"@c15t/typescript-config": "0.0.1",
|
|
255
255
|
"@c15t/vitest-config": "1.0.0",
|
|
256
|
-
"c15t": "2.2.0
|
|
256
|
+
"c15t": "2.2.0",
|
|
257
257
|
"playwright": "1.58.2"
|
|
258
258
|
},
|
|
259
259
|
"publishConfig": {
|