consentlayer 0.0.0 → 1.0.0-rc.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.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -1
  3. package/dist/adapters/google-consent-mode.d.ts +20 -0
  4. package/dist/adapters/google-consent-mode.js +18 -0
  5. package/dist/adapters/google-consent-mode.js.map +1 -0
  6. package/dist/adapters/shopify.d.ts +49 -0
  7. package/dist/adapters/shopify.js +85 -0
  8. package/dist/adapters/shopify.js.map +1 -0
  9. package/dist/adapters/tcf.d.ts +20 -0
  10. package/dist/adapters/tcf.js +64 -0
  11. package/dist/adapters/tcf.js.map +1 -0
  12. package/dist/analytics-provider-07mxUgIK.d.ts +36 -0
  13. package/dist/angular.d.ts +56 -0
  14. package/dist/angular.js +120 -0
  15. package/dist/angular.js.map +1 -0
  16. package/dist/auto-tracking.d.ts +11 -0
  17. package/dist/auto-tracking.js +72 -0
  18. package/dist/auto-tracking.js.map +1 -0
  19. package/dist/chunk-7XLGT6U3.js +34 -0
  20. package/dist/chunk-7XLGT6U3.js.map +1 -0
  21. package/dist/chunk-BWPWDN4G.js +61 -0
  22. package/dist/chunk-BWPWDN4G.js.map +1 -0
  23. package/dist/chunk-DK45XPEY.js +77 -0
  24. package/dist/chunk-DK45XPEY.js.map +1 -0
  25. package/dist/chunk-ETU64ZV3.js +98 -0
  26. package/dist/chunk-ETU64ZV3.js.map +1 -0
  27. package/dist/chunk-FDJMIJFV.js +367 -0
  28. package/dist/chunk-FDJMIJFV.js.map +1 -0
  29. package/dist/chunk-HJGBVDPM.js +47 -0
  30. package/dist/chunk-HJGBVDPM.js.map +1 -0
  31. package/dist/chunk-J3KUPF4R.js +69 -0
  32. package/dist/chunk-J3KUPF4R.js.map +1 -0
  33. package/dist/chunk-LBEIEAJX.js +55 -0
  34. package/dist/chunk-LBEIEAJX.js.map +1 -0
  35. package/dist/chunk-LEAQJXJU.js +582 -0
  36. package/dist/chunk-LEAQJXJU.js.map +1 -0
  37. package/dist/chunk-R6I7OYQB.js +69 -0
  38. package/dist/chunk-R6I7OYQB.js.map +1 -0
  39. package/dist/chunk-RK2M57GW.js +54 -0
  40. package/dist/chunk-RK2M57GW.js.map +1 -0
  41. package/dist/chunk-RZ4TYRNY.js +128 -0
  42. package/dist/chunk-RZ4TYRNY.js.map +1 -0
  43. package/dist/chunk-TVSZJLRC.js +112 -0
  44. package/dist/chunk-TVSZJLRC.js.map +1 -0
  45. package/dist/chunk-TYOQYV45.js +79 -0
  46. package/dist/chunk-TYOQYV45.js.map +1 -0
  47. package/dist/chunk-UAIZU5WD.js +52 -0
  48. package/dist/chunk-UAIZU5WD.js.map +1 -0
  49. package/dist/chunk-VIIGBALD.js +14 -0
  50. package/dist/chunk-VIIGBALD.js.map +1 -0
  51. package/dist/consent-controller-DyHPNfyT.d.ts +47 -0
  52. package/dist/consentlayer-banner.global.js +20 -0
  53. package/dist/consentlayer.global.js +9 -0
  54. package/dist/create-analytics-Bzvm1UpE.d.ts +44 -0
  55. package/dist/ecommerce.d.ts +41 -0
  56. package/dist/ecommerce.js +43 -0
  57. package/dist/ecommerce.js.map +1 -0
  58. package/dist/index.d.ts +58 -0
  59. package/dist/index.js +42 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/next.d.ts +16 -0
  62. package/dist/next.js +27 -0
  63. package/dist/next.js.map +1 -0
  64. package/dist/nuxt/config.d.ts +7 -0
  65. package/dist/nuxt/config.js +6 -0
  66. package/dist/nuxt/config.js.map +1 -0
  67. package/dist/nuxt/index.d.ts +11 -0
  68. package/dist/nuxt/index.js +35 -0
  69. package/dist/nuxt/index.js.map +1 -0
  70. package/dist/nuxt/plugin.d.ts +5 -0
  71. package/dist/nuxt/plugin.js +24 -0
  72. package/dist/nuxt/plugin.js.map +1 -0
  73. package/dist/providers/contentsquare.d.ts +27 -0
  74. package/dist/providers/contentsquare.js +58 -0
  75. package/dist/providers/contentsquare.js.map +1 -0
  76. package/dist/providers/ga4.d.ts +46 -0
  77. package/dist/providers/ga4.js +3 -0
  78. package/dist/providers/ga4.js.map +1 -0
  79. package/dist/providers/gtm.d.ts +28 -0
  80. package/dist/providers/gtm.js +83 -0
  81. package/dist/providers/gtm.js.map +1 -0
  82. package/dist/providers/hotjar.d.ts +38 -0
  83. package/dist/providers/hotjar.js +68 -0
  84. package/dist/providers/hotjar.js.map +1 -0
  85. package/dist/providers/meta.d.ts +42 -0
  86. package/dist/providers/meta.js +65 -0
  87. package/dist/providers/meta.js.map +1 -0
  88. package/dist/providers/posthog.d.ts +42 -0
  89. package/dist/providers/posthog.js +91 -0
  90. package/dist/providers/posthog.js.map +1 -0
  91. package/dist/providers/sentry.d.ts +38 -0
  92. package/dist/providers/sentry.js +82 -0
  93. package/dist/providers/sentry.js.map +1 -0
  94. package/dist/providers/tiktok.d.ts +46 -0
  95. package/dist/providers/tiktok.js +72 -0
  96. package/dist/providers/tiktok.js.map +1 -0
  97. package/dist/providers/vercel.d.ts +14 -0
  98. package/dist/providers/vercel.js +17 -0
  99. package/dist/providers/vercel.js.map +1 -0
  100. package/dist/react/index.d.ts +78 -0
  101. package/dist/react/index.js +79 -0
  102. package/dist/react/index.js.map +1 -0
  103. package/dist/sentry/next.d.ts +1 -0
  104. package/dist/sentry/next.js +3 -0
  105. package/dist/sentry/next.js.map +1 -0
  106. package/dist/sentry/server.d.ts +24 -0
  107. package/dist/sentry/server.js +28 -0
  108. package/dist/sentry/server.js.map +1 -0
  109. package/dist/sentry-replay-HSFVEFCM.js +3 -0
  110. package/dist/sentry-replay-HSFVEFCM.js.map +1 -0
  111. package/dist/sentry-sdk-YQOGKGWD.js +3 -0
  112. package/dist/sentry-sdk-YQOGKGWD.js.map +1 -0
  113. package/dist/sentry-tracing-KWT7ITHF.js +3 -0
  114. package/dist/sentry-tracing-KWT7ITHF.js.map +1 -0
  115. package/dist/server/ga4.d.ts +15 -0
  116. package/dist/server/ga4.js +29 -0
  117. package/dist/server/ga4.js.map +1 -0
  118. package/dist/server/index.d.ts +116 -0
  119. package/dist/server/index.js +206 -0
  120. package/dist/server/index.js.map +1 -0
  121. package/dist/server/meta.d.ts +14 -0
  122. package/dist/server/meta.js +63 -0
  123. package/dist/server/meta.js.map +1 -0
  124. package/dist/server/sgtm.d.ts +18 -0
  125. package/dist/server/sgtm.js +34 -0
  126. package/dist/server/sgtm.js.map +1 -0
  127. package/dist/server/tiktok.d.ts +15 -0
  128. package/dist/server/tiktok.js +64 -0
  129. package/dist/server/tiktok.js.map +1 -0
  130. package/dist/solid.d.ts +59 -0
  131. package/dist/solid.js +3 -0
  132. package/dist/solid.js.map +1 -0
  133. package/dist/solidstart.d.ts +16 -0
  134. package/dist/solidstart.js +22 -0
  135. package/dist/solidstart.js.map +1 -0
  136. package/dist/svelte/GtmNoscript.svelte +18 -0
  137. package/dist/svelte/MetaPixelNoscript.svelte +18 -0
  138. package/dist/svelte.d.ts +55 -0
  139. package/dist/svelte.js +3 -0
  140. package/dist/svelte.js.map +1 -0
  141. package/dist/sveltekit.d.ts +18 -0
  142. package/dist/sveltekit.js +16 -0
  143. package/dist/sveltekit.js.map +1 -0
  144. package/dist/testing.d.ts +11 -0
  145. package/dist/testing.js +50 -0
  146. package/dist/testing.js.map +1 -0
  147. package/dist/types-BV_AFZKQ.d.ts +407 -0
  148. package/dist/vue.d.ts +92 -0
  149. package/dist/vue.js +97 -0
  150. package/dist/vue.js.map +1 -0
  151. package/package.json +278 -3
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Your own consent categories. Declare them here and pass them to `createAnalytics({ categories })`:
3
+ * `declare module 'consentlayer' { interface ConsentCategories { functional: true } }`
4
+ */
5
+ interface ConsentCategories {
6
+ }
7
+ /** A category the visitor can toggle: the built-in ones plus yours. */
8
+ type ConsentCategory = keyof ({
9
+ preferences: true;
10
+ analytics: true;
11
+ marketing: true;
12
+ } & ConsentCategories);
13
+ /** Consent per category. Necessary cookies are always allowed, so they have no flag. */
14
+ type ConsentState = Record<ConsentCategory, boolean> & {
15
+ sale_of_data?: boolean;
16
+ };
17
+ /** What a plugin needs before it runs: a category, `'none'`, or a custom check. */
18
+ type ProviderConsentRequirement = keyof ConsentState | 'none' | ((consent: ConsentState | null) => boolean);
19
+ /** Who the visitor is, once known. Stored only with analytics or marketing consent. */
20
+ type UserIdentity = {
21
+ userId?: string;
22
+ email?: string;
23
+ phone?: string;
24
+ };
25
+ /** Extra traits passed to `identify`; `email` and `phone` also update the identity. */
26
+ type UserTraits = Record<string, unknown> & {
27
+ email?: string;
28
+ phone?: string;
29
+ };
30
+ /** Where a consent change came from. */
31
+ type ConsentChangeSource = 'bootstrap' | 'storage' | 'adapter' | 'imperative' | 'unknown';
32
+ /** DOM event names and window globals that announce consent changes to other scripts. */
33
+ type ConsentBroadcastConfig = {
34
+ eventName?: string | string[];
35
+ globalKey?: string | string[];
36
+ document?: Document;
37
+ window?: Window;
38
+ };
39
+ /** Where consent is kept. Defaults to localStorage. */
40
+ /** A stored choice: the consent, when it was made (epoch ms) and under which `policyVersion`. */
41
+ type StoredConsent = {
42
+ consent: ConsentState;
43
+ updatedAt?: number;
44
+ policyVersion?: string;
45
+ };
46
+ interface ConsentStorageAdapter {
47
+ read(): StoredConsent | null;
48
+ write(record: StoredConsent | null): void;
49
+ subscribe?(listener: () => void): () => void;
50
+ clear?(): void;
51
+ }
52
+ /** Where the visitor identity is kept. Defaults to localStorage. */
53
+ interface IdentityStorageAdapter {
54
+ read(): UserIdentity;
55
+ write(identity: Partial<UserIdentity>): void;
56
+ clear?(): void;
57
+ }
58
+ /** Syncs consent with an outside consent platform: reads its choice at boot, pushes changes to it, and listens for its updates. */
59
+ interface ConsentIntegrationAdapter {
60
+ id: string;
61
+ /** Its bootstrap answer replaces the stored choice, which is used only when it answers `null` or fails. */
62
+ authoritative?: boolean;
63
+ bootstrap?(): Promise<ConsentState | null> | ConsentState | null;
64
+ apply?(consent: ConsentState): Promise<void> | void;
65
+ subscribe?(onConsent: (consent: ConsentState) => void): (() => void) | void;
66
+ /** Runs when the engine is created and after every consent change, whatever its source. */
67
+ observe?(consent: ConsentState | null): void;
68
+ }
69
+ type ConsentEngineOptions = {
70
+ storage?: ConsentStorageAdapter;
71
+ adapters?: ConsentIntegrationAdapter[];
72
+ initialConsent?: ConsentState | null;
73
+ /** Your own categories, on top of preferences, analytics and marketing. */
74
+ categories?: (keyof ConsentCategories)[];
75
+ broadcast?: ConsentBroadcastConfig;
76
+ /** A stored choice older than this many days is stale. Unset, never. */
77
+ maxAgeDays?: number;
78
+ /** A stored choice made under another major version (the part before the first `.`), or none, is stale. Unset, never. */
79
+ policyVersion?: string;
80
+ /** `'discard'` treats a stale choice as no choice; `'keep'` keeps it in force until the visitor chooses again. Either way the banner shows. */
81
+ onStale?: 'discard' | 'keep';
82
+ /** Honor the browser's Global Privacy Control signal: "accept all" leaves marketing off while it is on. Defaults to `true`. */
83
+ gpc?: boolean;
84
+ debug?: boolean | DebugOptions;
85
+ onError?: AnalyticsErrorHandler;
86
+ };
87
+ /** What consent subscribers receive. */
88
+ type ConsentEngineSnapshot = {
89
+ consent: ConsentState | null;
90
+ };
91
+ interface ConsentEngine {
92
+ /** Every category the visitor can toggle, built-in first. */
93
+ categories: readonly ConsentCategory[];
94
+ /** Whether the browser sends the Global Privacy Control signal and the `gpc` option honors it. */
95
+ gpc: boolean;
96
+ bootstrap: () => Promise<ConsentState | null>;
97
+ getConsent: () => ConsentState | null;
98
+ /** Whether the stored choice is stale and the visitor should be asked again. */
99
+ isStale: () => boolean;
100
+ setConsent: (consent: ConsentState, source?: ConsentChangeSource) => Promise<void>;
101
+ subscribe: (listener: (snapshot: ConsentEngineSnapshot) => void) => () => void;
102
+ destroy: () => void;
103
+ }
104
+ /** When providers load: right away, when the browser is idle, or when you call `analytics.boot()`. */
105
+ type LazyBootMode = 'immediate' | 'idle' | 'manual';
106
+ /** When providers load, and how long to wait for idle time. */
107
+ type LazyBootConfig = {
108
+ mode?: LazyBootMode;
109
+ timeoutMs?: number;
110
+ };
111
+ /** `'all'` tracks clicks on elements matching `selectors`; `'attribute'` only elements with the tracking attribute. */
112
+ type AutoTrackingClickMode = 'all' | 'attribute';
113
+ /** Which clicks are tracked automatically. */
114
+ type AutoTrackingClickConfig = {
115
+ mode?: AutoTrackingClickMode;
116
+ attribute?: string;
117
+ selectors?: string[];
118
+ };
119
+ /** Scroll depths (percent) that send an event. */
120
+ type AutoTrackingScrollConfig = {
121
+ thresholds?: number[];
122
+ };
123
+ /** Seconds on page that send an event. */
124
+ type AutoTrackingTimeConfig = {
125
+ intervals?: number[];
126
+ };
127
+ /** Events tracked without code. `true` uses the defaults for that kind. */
128
+ type AutoTrackingConfig = {
129
+ clicks?: boolean | AutoTrackingClickConfig;
130
+ scroll?: boolean | AutoTrackingScrollConfig;
131
+ timeOnPage?: boolean | AutoTrackingTimeConfig;
132
+ pageViews?: boolean;
133
+ /** Send auto-tracked events only to these provider ids. Defaults to all. */
134
+ only?: string[];
135
+ };
136
+ /** What `autoTracking()` returns: started once with `track` and a signal aborted on `destroy()`, it returns the function that starts a page session. */
137
+ type AutoTracking = (context: {
138
+ track: (name: string, params: Record<string, unknown>, options: TrackEventOptions) => Promise<void>;
139
+ signal: AbortSignal;
140
+ }) => () => void;
141
+ /** Page view details; read from `location` and `document.title` when omitted. */
142
+ type PageViewPayload = {
143
+ url?: string;
144
+ path?: string;
145
+ title?: string;
146
+ };
147
+ /** Per-event options for `track`. */
148
+ type TrackEventOptions = {
149
+ requireAnalyticsConsent?: boolean;
150
+ requireMarketingConsent?: boolean;
151
+ /** Send only to these provider ids. */
152
+ only?: string[];
153
+ /** Shared by browser and server so providers can dedupe. Generated if omitted. */
154
+ eventId?: string;
155
+ /** Extra params merged into what a single provider sends, keyed by provider id. */
156
+ providers?: Record<string, Record<string, unknown>>;
157
+ };
158
+ /** An event as plugins receive it. */
159
+ type AnalyticsEventPayload = {
160
+ id: string;
161
+ name: string;
162
+ params: Record<string, unknown>;
163
+ options: TrackEventOptions;
164
+ };
165
+ /** A product line in GA4 item format. */
166
+ type EcommerceItem = {
167
+ item_id: string;
168
+ item_name?: string;
169
+ item_variant?: string;
170
+ item_brand?: string;
171
+ item_category?: string;
172
+ price?: number;
173
+ quantity?: number;
174
+ discount?: number;
175
+ coupon?: string;
176
+ };
177
+ type Money = {
178
+ value?: number;
179
+ currency?: string;
180
+ };
181
+ type EcommerceParams = Money & {
182
+ items?: EcommerceItem[];
183
+ };
184
+ /** Standard events every provider translates, named after GA4's. Any other name is sent as a custom event. */
185
+ interface StandardEvents {
186
+ sign_up: {
187
+ method?: string;
188
+ };
189
+ login: {
190
+ method?: string;
191
+ };
192
+ search: {
193
+ search_term: string;
194
+ };
195
+ lead: Money;
196
+ share: {
197
+ method?: string;
198
+ content_type?: string;
199
+ item_id?: string;
200
+ };
201
+ contact: {
202
+ method?: string;
203
+ };
204
+ view_item: EcommerceParams;
205
+ view_item_list: EcommerceParams & {
206
+ item_list_id?: string;
207
+ item_list_name?: string;
208
+ };
209
+ add_to_cart: EcommerceParams;
210
+ remove_from_cart: EcommerceParams;
211
+ add_to_wishlist: EcommerceParams;
212
+ begin_checkout: EcommerceParams & {
213
+ coupon?: string;
214
+ };
215
+ add_payment_info: EcommerceParams & {
216
+ payment_type?: string;
217
+ };
218
+ add_shipping_info: EcommerceParams & {
219
+ shipping_tier?: string;
220
+ };
221
+ purchase: EcommerceParams & {
222
+ transaction_id: string;
223
+ tax?: number;
224
+ shipping?: number;
225
+ coupon?: string;
226
+ };
227
+ refund: EcommerceParams & {
228
+ transaction_id: string;
229
+ };
230
+ trial_started: Money & {
231
+ plan?: string;
232
+ };
233
+ subscription_started: Money & {
234
+ plan?: string;
235
+ };
236
+ subscription_cancelled: {
237
+ plan?: string;
238
+ reason?: string;
239
+ };
240
+ }
241
+ /** Params for an event: typed for the standard events, free-form for the rest. */
242
+ type EventParams<Name extends string> = Name extends keyof StandardEvents ? StandardEvents[Name] & Record<string, unknown> : Record<string, unknown>;
243
+ /** What `analytics.subscribe` listeners receive. */
244
+ type AnalyticsManagerStateSnapshot = {
245
+ booted: boolean;
246
+ consent: ConsentState | null;
247
+ activeProviders: string[];
248
+ };
249
+ /** Which step failed. */
250
+ type AnalyticsErrorScope = 'provider-setup' | 'provider-load' | 'provider-consent' | 'provider-page' | 'provider-track' | 'provider-identify' | 'provider-error' | 'provider-teardown' | 'provider-script-load' | 'server-forward' | 'consent-adapter' | 'auto-tracking' | 'queue-full';
251
+ /** A failure inside a provider, the server forward or a consent adapter, or a full call queue. Never thrown; passed to `onError`. */
252
+ type AnalyticsError = {
253
+ scope: AnalyticsErrorScope;
254
+ providerId?: string;
255
+ message: string;
256
+ cause?: unknown;
257
+ };
258
+ /** `consent`: changes and stale choices. `events`: page views, `track`, `identify`, `group` and `reset`, per provider. `providers`: boot and each provider's setup, consent, load and teardown. `errors`: `AnalyticsError`s and `captureError` deliveries. */
259
+ type DebugTopic = 'consent' | 'events' | 'providers' | 'errors';
260
+ type DebugEntry = {
261
+ topic: DebugTopic;
262
+ provider?: string;
263
+ message: string;
264
+ data: unknown[];
265
+ };
266
+ /** Topics set to `true` are logged; with none set to `true`, every topic not set to `false` is. */
267
+ type DebugOptions = Partial<Record<DebugTopic, boolean>> & {
268
+ /** Drops entries about other providers. Entries about no provider are kept. */
269
+ only?: string[];
270
+ /** Receives each entry instead of `console.debug`. */
271
+ logger?: (entry: DebugEntry) => void;
272
+ };
273
+ /** Receives every `AnalyticsError`. */
274
+ type AnalyticsErrorHandler = (error: AnalyticsError) => void;
275
+ type AnalyticsManagerOptions = {
276
+ providers?: AnalyticsPlugin[];
277
+ consent?: ConsentState | null;
278
+ consentEngine?: ConsentEngine;
279
+ identityStorage?: IdentityStorageAdapter;
280
+ enabled?: boolean | (() => boolean);
281
+ debug?: boolean | DebugOptions;
282
+ /** `autoTracking()` from `consentlayer/auto-tracking`. Starts once analytics consent is granted; off by default. */
283
+ autoTracking?: AutoTracking;
284
+ onError?: AnalyticsErrorHandler;
285
+ /**
286
+ * `captureError` and the window `error` / `unhandledrejection` handlers.
287
+ * `false` turns both off; `{ globalHandlers: false }` keeps only `captureError`.
288
+ */
289
+ errors?: boolean | {
290
+ globalHandlers?: boolean;
291
+ };
292
+ /** HTTPS URLs or paths starting with "/". */
293
+ server?: {
294
+ /** Also send each event to this route (see `createAnalyticsHandler`). */
295
+ endpoint?: string;
296
+ /** Post each consent choice to this route (see `createConsentHandler`). */
297
+ consentEndpoint?: string;
298
+ };
299
+ };
300
+ /** What the browser sends to the server, and what `sendServerEvent` takes. */
301
+ type ServerEvent = {
302
+ /** Shared with the browser event so destinations can dedupe. */
303
+ id?: string;
304
+ name: string;
305
+ params?: Record<string, unknown>;
306
+ /** The consent the event was collected under. */
307
+ consent: ConsentState;
308
+ identity?: UserIdentity;
309
+ url?: string;
310
+ /** GA4 client ID; read from the `_ga` cookie when omitted. */
311
+ clientId?: string;
312
+ only?: string[];
313
+ providers?: Record<string, Record<string, unknown>>;
314
+ };
315
+ /** What plugins receive on each call. */
316
+ type PluginContext = {
317
+ consent: ConsentState | null;
318
+ identity: UserIdentity;
319
+ /** Whether the `debug` option is on. */
320
+ debug: boolean;
321
+ /** Logs under the `providers` debug topic, attributed to this plugin. */
322
+ log: (message: string, ...data: unknown[]) => void;
323
+ emitError: AnalyticsErrorHandler;
324
+ };
325
+ /** A page view as plugins receive it. */
326
+ type PageEvent = PageViewPayload & {
327
+ /** Shared by browser and server so providers can dedupe. */
328
+ id: string;
329
+ };
330
+ /** Extra details for `captureError`. */
331
+ type ErrorContext = {
332
+ level?: 'fatal' | 'error' | 'warning';
333
+ tags?: Record<string, string>;
334
+ extra?: Record<string, unknown>;
335
+ /** Where it was caught. */
336
+ source?: 'manual' | 'global' | 'boundary';
337
+ };
338
+ /**
339
+ * When `load` runs once consent allows it: right away (default), when the
340
+ * browser is idle, on the first interaction, or after a delay in ms. Calls made
341
+ * meanwhile are held (up to 100) and delivered after `load`.
342
+ */
343
+ type LoadTiming = false | 'idle' | 'interaction' | number;
344
+ type Hook<Args extends unknown[] = []> = (...args: [...Args, PluginContext]) => Promise<void> | void;
345
+ /**
346
+ * A provider. The manager decides when each hook runs:
347
+ * `setup` when the instance is created in the browser, or else at boot;
348
+ * `load` once, when `requiredConsent` is granted (at boot
349
+ * with `loadBeforeConsent`, or when there is no `load`); then `consent` on
350
+ * every consent change. `page`, `track`, `identify` and `group` run only
351
+ * while consent is granted; a withdrawal runs `teardown`. Errors go to
352
+ * `onError` and never reach other plugins.
353
+ */
354
+ interface AnalyticsPlugin {
355
+ id: string;
356
+ /** The consent the provider needs. `'none'` runs it without consent. */
357
+ requiredConsent: ProviderConsentRequirement;
358
+ /** Load before consent and rely on the provider's own consent mode. */
359
+ loadBeforeConsent?: boolean;
360
+ defer?: LoadTiming;
361
+ /** No network, no cookies. */
362
+ setup?: Hook;
363
+ /** Inject scripts. Runs once. */
364
+ load?: Hook;
365
+ /** Apply the current consent state. */
366
+ consent?: Hook;
367
+ page?: Hook<[page: PageEvent]>;
368
+ track?: Hook<[event: AnalyticsEventPayload]>;
369
+ identify?: Hook<[identity: UserIdentity, traits: UserTraits]>;
370
+ reset?: Hook;
371
+ group?: Hook<[groupId: string, traits: Record<string, unknown>]>;
372
+ /** Receives errors from `captureError` and the global handlers. */
373
+ error?: Hook<[error: unknown, context: ErrorContext]>;
374
+ /** Stop sending; remove cookies where possible. */
375
+ teardown?: Hook;
376
+ }
377
+ interface AnalyticsManager {
378
+ boot: () => Promise<void>;
379
+ registerProvider: (provider: AnalyticsPlugin) => () => void;
380
+ updateConsent: (consent: ConsentState | null) => Promise<void>;
381
+ getConsent: () => ConsentState | null;
382
+ /** `only` limits which providers and server destinations receive it; `server: true` ignores it for the server, `false` skips the server. */
383
+ page: (payload?: PageViewPayload, options?: {
384
+ only?: string[];
385
+ server?: boolean;
386
+ }) => Promise<void>;
387
+ track: <Name extends string>(name: Name, params?: EventParams<Name>, options?: TrackEventOptions) => Promise<void>;
388
+ identify: (userId?: string, traits?: UserTraits) => Promise<void>;
389
+ reset: () => Promise<void>;
390
+ group: (groupId: string, traits?: Record<string, unknown>) => Promise<void>;
391
+ captureError: (error: unknown, context?: ErrorContext) => Promise<void>;
392
+ newPageSession: () => void;
393
+ getSnapshot: () => AnalyticsManagerStateSnapshot;
394
+ subscribe: (listener: (snapshot: AnalyticsManagerStateSnapshot) => void) => () => void;
395
+ destroy: () => void;
396
+ }
397
+ /** Reads and changes consent: `analytics.consent`. */
398
+ type ConsentApi = Omit<ConsentEngine, 'destroy'> & {
399
+ /** Merges into the current consent; categories left out stay as they are (denied if unset). */
400
+ update(partial: Partial<ConsentState>): Promise<void>;
401
+ };
402
+ /** The instance `createAnalytics` returns. */
403
+ type Analytics = Omit<AnalyticsManager, 'updateConsent' | 'getConsent'> & {
404
+ consent: ConsentApi;
405
+ };
406
+
407
+ export type { AutoTrackingConfig as A, ConsentChangeSource as B, ConsentIntegrationAdapter as C, ConsentEngineSnapshot as D, EcommerceItem as E, DebugEntry as F, DebugOptions as G, DebugTopic as H, IdentityStorageAdapter as I, EventParams as J, LazyBootMode as K, LoadTiming as L, ServerEvent as M, PluginContext as P, StandardEvents as S, TrackEventOptions as T, UserIdentity as U, AutoTracking as a, AutoTrackingClickConfig as b, AutoTrackingClickMode as c, AutoTrackingScrollConfig as d, AutoTrackingTimeConfig as e, AnalyticsPlugin as f, PageEvent as g, AnalyticsEventPayload as h, UserTraits as i, ProviderConsentRequirement as j, ConsentCategory as k, ErrorContext as l, Analytics as m, PageViewPayload as n, LazyBootConfig as o, ConsentState as p, ConsentApi as q, AnalyticsManagerOptions as r, ConsentEngineOptions as s, ConsentStorageAdapter as t, AnalyticsError as u, AnalyticsErrorHandler as v, AnalyticsErrorScope as w, AnalyticsManagerStateSnapshot as x, ConsentBroadcastConfig as y, ConsentCategories as z };
package/dist/vue.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ import * as vue from 'vue';
2
+ import { Ref, Plugin } from 'vue';
3
+ import { C as ConsentControllerState, a as ConsentControllerActions, b as ConsentControllerOptions } from './consent-controller-DyHPNfyT.js';
4
+ import { m as Analytics, n as PageViewPayload, p as ConsentState, q as ConsentApi } from './types-BV_AFZKQ.js';
5
+
6
+ type RouteLike = {
7
+ fullPath: string;
8
+ };
9
+ /** The part of a Vue Router instance ConsentLayer uses. */
10
+ type RouterLike = {
11
+ currentRoute: {
12
+ value: RouteLike;
13
+ };
14
+ isReady: () => Promise<void>;
15
+ afterEach: (guard: (to: RouteLike, from: RouteLike, failure?: unknown) => unknown) => () => void;
16
+ };
17
+ /** Options for `app.use(consentLayer, options)`. */
18
+ type ConsentLayerOptions = ConsentControllerOptions & {
19
+ analytics: Analytics;
20
+ /** Sends a page view on every navigation. Without it, one page view is sent for the page load. */
21
+ router?: RouterLike;
22
+ page?: PageViewPayload;
23
+ /** Sends errors from `app.config.errorHandler` through `captureError`. Defaults to `true`. */
24
+ captureErrors?: boolean;
25
+ };
26
+ /** What `useConsentController()` returns: the UI state as refs, and the actions. */
27
+ type ConsentController = {
28
+ readonly [Key in keyof ConsentControllerState]: Readonly<Ref<ConsentControllerState[Key]>>;
29
+ } & ConsentControllerActions;
30
+ /** Boots analytics, sends page views on navigation and provides the state for your consent UI. */
31
+ declare const consentLayer: Plugin<[ConsentLayerOptions]>;
32
+ /** The instance from `app.use(consentLayer)`, else the one `createAnalytics()` made. */
33
+ declare function useAnalytics(): Analytics;
34
+ /** Current consent as a ref, `null` before a choice. */
35
+ declare function useConsent(): {
36
+ consent: Readonly<Ref<ConsentState | null>>;
37
+ engine: ConsentApi;
38
+ setConsent: (consent: ConsentState) => Promise<void>;
39
+ update: (partial: Partial<ConsentState>) => Promise<void>;
40
+ };
41
+ /** State and actions for building your own consent UI. */
42
+ declare function useConsentController(): ConsentController;
43
+ /**
44
+ * Meta's `<noscript>` PageView pixel. It fires only for visitors without
45
+ * JavaScript, who never see the consent banner, so it tracks them without
46
+ * consent. Render it only where that is lawful for you. Renders nothing without
47
+ * `enabled` or while `pixelId` is unset. Other attributes go to the `<img>`.
48
+ */
49
+ declare const MetaPixelNoscript: vue.DefineComponent<vue.ExtractPropTypes<{
50
+ pixelId: {
51
+ type: StringConstructor;
52
+ default: undefined;
53
+ };
54
+ enabled: BooleanConstructor;
55
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
56
+ [key: string]: any;
57
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
58
+ pixelId: {
59
+ type: StringConstructor;
60
+ default: undefined;
61
+ };
62
+ enabled: BooleanConstructor;
63
+ }>> & Readonly<{}>, {
64
+ enabled: boolean;
65
+ pixelId: string;
66
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
67
+ /**
68
+ * Google Tag Manager's `<noscript>` iframe. It loads the container only for
69
+ * visitors without JavaScript, who never see the consent banner, so its tags
70
+ * run without consent. Render it only where that is lawful for you. Renders
71
+ * nothing without `enabled` or while `containerId` is unset. Other attributes go to the `<iframe>`.
72
+ */
73
+ declare const GtmNoscript: vue.DefineComponent<vue.ExtractPropTypes<{
74
+ containerId: {
75
+ type: StringConstructor;
76
+ default: undefined;
77
+ };
78
+ enabled: BooleanConstructor;
79
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
80
+ [key: string]: any;
81
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
82
+ containerId: {
83
+ type: StringConstructor;
84
+ default: undefined;
85
+ };
86
+ enabled: BooleanConstructor;
87
+ }>> & Readonly<{}>, {
88
+ enabled: boolean;
89
+ containerId: string;
90
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
91
+
92
+ export { type ConsentController, type ConsentLayerOptions, GtmNoscript, MetaPixelNoscript, type RouterLike, consentLayer, useAnalytics, useConsent, useConsentController };
package/dist/vue.js ADDED
@@ -0,0 +1,97 @@
1
+ import { metaNoscriptImg, gtmNoscriptIframe } from './chunk-VIIGBALD.js';
2
+ import { createConsentController, latestAnalytics } from './chunk-FDJMIJFV.js';
3
+ import { defineComponent, h, shallowRef, inject, getCurrentScope, onScopeDispose, computed } from 'vue';
4
+
5
+ var AnalyticsKey = /* @__PURE__ */ Symbol("consentlayer.analytics");
6
+ var ControllerKey = /* @__PURE__ */ Symbol("consentlayer.controller");
7
+ var routeKey = (route) => route.fullPath.replace(/#.*$/, "");
8
+ var consentLayer = {
9
+ install(app, { analytics, router, page, captureErrors = true, ...options }) {
10
+ const controller = createConsentController(analytics, options);
11
+ const state = shallowRef(controller.getSnapshot());
12
+ const field = (key) => computed(() => state.value[key]);
13
+ app.provide(AnalyticsKey, analytics);
14
+ app.provide(ControllerKey, {
15
+ ready: field("ready"),
16
+ error: field("error"),
17
+ bannerVisible: field("bannerVisible"),
18
+ dialogVisible: field("dialogVisible"),
19
+ categories: field("categories"),
20
+ gpc: field("gpc"),
21
+ prefs: field("prefs"),
22
+ ...controller.actions
23
+ });
24
+ if (typeof window === "undefined") return;
25
+ if (captureErrors) {
26
+ const previous = app.config.errorHandler;
27
+ app.config.errorHandler = (error, instance, info) => {
28
+ void analytics.captureError(error, { source: "boundary", tags: { vue_info: info } });
29
+ if (previous) previous(error, instance, info);
30
+ else console.error(error);
31
+ };
32
+ }
33
+ const unsubscribe = controller.subscribe(() => {
34
+ state.value = controller.getSnapshot();
35
+ });
36
+ const stop = controller.start();
37
+ let stopRouter = () => {
38
+ };
39
+ if (router) {
40
+ stopRouter = router.afterEach((to, _from, failure) => {
41
+ if (!failure) controller.setRoute(routeKey(to), page);
42
+ });
43
+ void router.isReady().then(() => controller.setRoute(routeKey(router.currentRoute.value), page));
44
+ } else {
45
+ controller.setRoute("default", page);
46
+ }
47
+ app.onUnmount(() => {
48
+ stopRouter();
49
+ stop();
50
+ unsubscribe();
51
+ });
52
+ }
53
+ };
54
+ function useAnalytics() {
55
+ const analytics = inject(AnalyticsKey, void 0) ?? latestAnalytics();
56
+ if (!analytics) throw new Error("useAnalytics needs createAnalytics() to have run, or app.use(consentLayer)");
57
+ return analytics;
58
+ }
59
+ function useConsent() {
60
+ const analytics = useAnalytics();
61
+ const consent = shallowRef(analytics.consent.getConsent());
62
+ const unsubscribe = analytics.consent.subscribe(() => {
63
+ consent.value = analytics.consent.getConsent();
64
+ });
65
+ if (getCurrentScope()) onScopeDispose(unsubscribe);
66
+ return {
67
+ consent,
68
+ engine: analytics.consent,
69
+ setConsent: (next) => analytics.consent.setConsent(next),
70
+ update: (partial) => analytics.consent.update(partial)
71
+ };
72
+ }
73
+ function useConsentController() {
74
+ const controller = inject(ControllerKey, void 0);
75
+ if (!controller) throw new Error("useConsentController needs app.use(consentLayer)");
76
+ return controller;
77
+ }
78
+ var MetaPixelNoscript = defineComponent({
79
+ name: "MetaPixelNoscript",
80
+ inheritAttrs: false,
81
+ props: { pixelId: { type: String, default: void 0 }, enabled: Boolean },
82
+ setup(props, { attrs }) {
83
+ return () => props.enabled && props.pixelId ? h("noscript", { innerHTML: metaNoscriptImg(props.pixelId, attrs) }) : null;
84
+ }
85
+ });
86
+ var GtmNoscript = defineComponent({
87
+ name: "GtmNoscript",
88
+ inheritAttrs: false,
89
+ props: { containerId: { type: String, default: void 0 }, enabled: Boolean },
90
+ setup(props, { attrs }) {
91
+ return () => props.enabled && props.containerId ? h("noscript", { innerHTML: gtmNoscriptIframe(props.containerId, attrs) }) : null;
92
+ }
93
+ });
94
+
95
+ export { GtmNoscript, MetaPixelNoscript, consentLayer, useAnalytics, useConsent, useConsentController };
96
+ //# sourceMappingURL=vue.js.map
97
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vue.ts"],"names":[],"mappings":"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","file":"vue.js","sourcesContent":["import {\n computed,\n defineComponent,\n getCurrentScope,\n h,\n inject,\n onScopeDispose,\n shallowRef,\n type App,\n type InjectionKey,\n type Plugin,\n type Ref,\n} from 'vue'\nimport {\n createConsentController,\n type ConsentControllerActions,\n type ConsentControllerOptions,\n type ConsentControllerState,\n} from './core/consent-controller'\nimport { latestAnalytics } from './core/create-analytics'\nimport { gtmNoscriptIframe, metaNoscriptImg } from './core/noscript'\nimport type { Analytics, ConsentApi, ConsentState, PageViewPayload } from './core/types'\n\ntype RouteLike = { fullPath: string }\n\n/** The part of a Vue Router instance ConsentLayer uses. */\nexport type RouterLike = {\n currentRoute: { value: RouteLike }\n isReady: () => Promise<void>\n afterEach: (guard: (to: RouteLike, from: RouteLike, failure?: unknown) => unknown) => () => void\n}\n\n/** Options for `app.use(consentLayer, options)`. */\nexport type ConsentLayerOptions = ConsentControllerOptions & {\n analytics: Analytics\n /** Sends a page view on every navigation. Without it, one page view is sent for the page load. */\n router?: RouterLike\n page?: PageViewPayload\n /** Sends errors from `app.config.errorHandler` through `captureError`. Defaults to `true`. */\n captureErrors?: boolean\n}\n\n/** What `useConsentController()` returns: the UI state as refs, and the actions. */\nexport type ConsentController = {\n readonly [Key in keyof ConsentControllerState]: Readonly<Ref<ConsentControllerState[Key]>>\n} & ConsentControllerActions\n\nconst AnalyticsKey: InjectionKey<Analytics> = Symbol('consentlayer.analytics')\nconst ControllerKey: InjectionKey<ConsentController> = Symbol('consentlayer.controller')\n\nconst routeKey = (route: RouteLike) => route.fullPath.replace(/#.*$/, '')\n\n/** Boots analytics, sends page views on navigation and provides the state for your consent UI. */\nexport const consentLayer: Plugin<[ConsentLayerOptions]> = {\n install(app: App, { analytics, router, page, captureErrors = true, ...options }: ConsentLayerOptions) {\n const controller = createConsentController(analytics, options)\n const state = shallowRef(controller.getSnapshot())\n const field = <Key extends keyof ConsentControllerState>(key: Key) => computed(() => state.value[key])\n app.provide(AnalyticsKey, analytics)\n app.provide(ControllerKey, {\n ready: field('ready'),\n error: field('error'),\n bannerVisible: field('bannerVisible'),\n dialogVisible: field('dialogVisible'),\n categories: field('categories'),\n gpc: field('gpc'),\n prefs: field('prefs'),\n ...controller.actions,\n })\n\n if (typeof window === 'undefined') return\n if (captureErrors) {\n const previous = app.config.errorHandler\n app.config.errorHandler = (error, instance, info) => {\n void analytics.captureError(error, { source: 'boundary', tags: { vue_info: info } })\n if (previous) previous(error, instance, info)\n else console.error(error)\n }\n }\n\n const unsubscribe = controller.subscribe(() => {\n state.value = controller.getSnapshot()\n })\n const stop = controller.start()\n let stopRouter = () => {}\n if (router) {\n stopRouter = router.afterEach((to, _from, failure) => {\n if (!failure) controller.setRoute(routeKey(to), page)\n })\n void router.isReady().then(() => controller.setRoute(routeKey(router.currentRoute.value), page))\n } else {\n controller.setRoute('default', page)\n }\n app.onUnmount(() => {\n stopRouter()\n stop()\n unsubscribe()\n })\n },\n}\n\n/** The instance from `app.use(consentLayer)`, else the one `createAnalytics()` made. */\nexport function useAnalytics(): Analytics {\n const analytics = inject(AnalyticsKey, undefined) ?? latestAnalytics()\n if (!analytics) throw new Error('useAnalytics needs createAnalytics() to have run, or app.use(consentLayer)')\n return analytics\n}\n\n/** Current consent as a ref, `null` before a choice. */\nexport function useConsent(): {\n consent: Readonly<Ref<ConsentState | null>>\n engine: ConsentApi\n setConsent: (consent: ConsentState) => Promise<void>\n update: (partial: Partial<ConsentState>) => Promise<void>\n} {\n const analytics = useAnalytics()\n const consent = shallowRef(analytics.consent.getConsent())\n const unsubscribe = analytics.consent.subscribe(() => {\n consent.value = analytics.consent.getConsent()\n })\n if (getCurrentScope()) onScopeDispose(unsubscribe)\n return {\n consent,\n engine: analytics.consent,\n setConsent: (next) => analytics.consent.setConsent(next),\n update: (partial) => analytics.consent.update(partial),\n }\n}\n\n/** State and actions for building your own consent UI. */\nexport function useConsentController(): ConsentController {\n const controller = inject(ControllerKey, undefined)\n if (!controller) throw new Error('useConsentController needs app.use(consentLayer)')\n return controller\n}\n\n/**\n * Meta's `<noscript>` PageView pixel. It fires only for visitors without\n * JavaScript, who never see the consent banner, so it tracks them without\n * consent. Render it only where that is lawful for you. Renders nothing without\n * `enabled` or while `pixelId` is unset. Other attributes go to the `<img>`.\n */\nexport const MetaPixelNoscript = defineComponent({\n name: 'MetaPixelNoscript',\n inheritAttrs: false,\n props: { pixelId: { type: String, default: undefined }, enabled: Boolean },\n setup(props, { attrs }) {\n return () =>\n props.enabled && props.pixelId ? h('noscript', { innerHTML: metaNoscriptImg(props.pixelId, attrs) }) : null\n },\n})\n\n/**\n * Google Tag Manager's `<noscript>` iframe. It loads the container only for\n * visitors without JavaScript, who never see the consent banner, so its tags\n * run without consent. Render it only where that is lawful for you. Renders\n * nothing without `enabled` or while `containerId` is unset. Other attributes go to the `<iframe>`.\n */\nexport const GtmNoscript = defineComponent({\n name: 'GtmNoscript',\n inheritAttrs: false,\n props: { containerId: { type: String, default: undefined }, enabled: Boolean },\n setup(props, { attrs }) {\n return () =>\n props.enabled && props.containerId\n ? h('noscript', { innerHTML: gtmNoscriptIframe(props.containerId, attrs) })\n : null\n },\n})\n"]}