@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.
@@ -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;
@@ -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
- afterLoad: [
155
+ onBeforeLoadGranted: [
134
156
  {
135
- type: 'callGlobal',
136
- global: 'posthog',
137
- method: 'init',
138
- args: [
139
- '{{id}}',
140
- '{{initOptions}}'
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
- afterLoad: [
122
+ onBeforeLoadGranted: [
101
123
  {
102
- type: 'callGlobal',
103
- global: 'posthog',
104
- method: 'init',
105
- args: [
106
- '{{id}}',
107
- '{{initOptions}}'
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
  ],
@@ -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 afterLoad: [{
70
- readonly type: 'callGlobal';
71
- readonly global: 'posthog';
72
- readonly method: 'init';
73
- readonly args: ["{{id}}", "{{initOptions}}"];
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, pass the nonce through the `Script` object so c15t applies it to the generated script element.
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 loads PostHog's bootstrap script, calls `posthog.init()` for you, and then synchronizes consent through `posthog.opt_in_capturing()` / `posthog.opt_out_capturing()`. You do not need to call `posthog.init()` separately.
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. c15t calls `opt_in_capturing()` / `opt_out_capturing()` based on consent. Pending events before the PostHog bootstrap finishes may be dropped; after denial, PostHog captures cookieless events when your PostHog project supports cookieless mode.
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-canary-20260727202135",
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-canary-20260727202135",
256
+ "c15t": "2.2.0",
257
257
  "playwright": "1.58.2"
258
258
  },
259
259
  "publishConfig": {