@c15t/scripts 3.0.0-alpha.0 → 3.0.0-alpha.2

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 (88) hide show
  1. package/AGENTS.md +7 -0
  2. package/README.md +4 -3
  3. package/dist/e2e-test-utils.js +5 -3
  4. package/dist/engine/runtime.js +14 -3
  5. package/dist/events.js +218 -0
  6. package/dist/registry.js +40 -0
  7. package/dist/vendors/ads-and-pixels/pinterest-tag.js +123 -0
  8. package/dist/vendors/analytics/google-tag.js +14 -2
  9. package/dist/vendors/analytics/microsoft-clarity.js +4 -1
  10. package/dist/vendors/analytics/one-dollar-stats.js +30 -0
  11. package/dist/vendors/analytics/segment.js +10 -1
  12. package/dist/vendors/functional/front-chat.js +64 -0
  13. package/dist/vendors/tag-managers/cloudflare-zaraz.js +98 -0
  14. package/dist/vendors/tag-managers/google-tag-manager.js +17 -3
  15. package/dist-types/__tests__/helpers.d.ts +2 -2
  16. package/dist-types/engine/compile.d.ts +1 -1
  17. package/dist-types/engine/runtime.d.ts +1 -1
  18. package/dist-types/events.d.ts +46 -0
  19. package/dist-types/registry.d.ts +36 -0
  20. package/dist-types/resolve.d.ts +1 -1
  21. package/dist-types/vendors/_shared/install-builders.d.ts +1 -1
  22. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +295 -0
  23. package/dist-types/vendors/analytics/adobe-analytics.d.ts +1 -1
  24. package/dist-types/vendors/analytics/google-tag.d.ts +3 -1
  25. package/dist-types/vendors/analytics/matomo-analytics.d.ts +1 -1
  26. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +39 -0
  27. package/dist-types/vendors/analytics/segment.d.ts +7 -1
  28. package/dist-types/vendors/functional/front-chat.d.ts +62 -0
  29. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +39 -0
  30. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +3 -1
  31. package/docs/README.md +7 -0
  32. package/docs/customization/overview.md +4 -3
  33. package/docs/customization/recipes.md +4 -2
  34. package/docs/customization/tokens.md +66 -3
  35. package/docs/frameworks/javascript/script-loader.md +53 -0
  36. package/docs/frameworks/next/script-loader.md +60 -12
  37. package/docs/frameworks/react/script-loader.md +14 -0
  38. package/docs/guides/consent-state.md +327 -0
  39. package/docs/guides/deployment-modes.md +12 -0
  40. package/docs/guides/shared-consent-controls.md +158 -0
  41. package/docs/integrations/adobe-analytics.md +1 -1
  42. package/docs/integrations/ahrefs-analytics.md +1 -1
  43. package/docs/integrations/amplitude.md +1 -1
  44. package/docs/integrations/building-integrations.md +5 -0
  45. package/docs/integrations/clear-on-revocation.md +167 -0
  46. package/docs/integrations/clearbit.md +1 -1
  47. package/docs/integrations/cloudflare-web-analytics.md +1 -1
  48. package/docs/integrations/cloudflare-zaraz.md +399 -0
  49. package/docs/integrations/crisp.md +1 -1
  50. package/docs/integrations/databuddy.md +1 -1
  51. package/docs/integrations/fathom-analytics.md +1 -1
  52. package/docs/integrations/front-chat.md +322 -0
  53. package/docs/integrations/google-maps.md +21 -21
  54. package/docs/integrations/google-tag-manager.md +1 -1
  55. package/docs/integrations/google-tag.md +1 -1
  56. package/docs/integrations/granular-consent.md +210 -0
  57. package/docs/integrations/heap.md +1 -1
  58. package/docs/integrations/hightouch.md +1 -1
  59. package/docs/integrations/hotjar.md +1 -1
  60. package/docs/integrations/intercom.md +1 -1
  61. package/docs/integrations/linkedin-insights.md +1 -1
  62. package/docs/integrations/logrocket.md +1 -1
  63. package/docs/integrations/matomo-analytics.md +1 -1
  64. package/docs/integrations/meta-pixel.md +1 -1
  65. package/docs/integrations/microsoft-clarity.md +1 -1
  66. package/docs/integrations/microsoft-uet.md +1 -1
  67. package/docs/integrations/mixpanel-analytics.md +1 -1
  68. package/docs/integrations/one-dollar-stats.md +305 -0
  69. package/docs/integrations/openai-pixel.md +1 -1
  70. package/docs/integrations/overview.md +22 -18
  71. package/docs/integrations/pinterest-tag.md +321 -0
  72. package/docs/integrations/pirsch.md +1 -1
  73. package/docs/integrations/plausible-analytics.md +1 -1
  74. package/docs/integrations/posthog.md +1 -1
  75. package/docs/integrations/promptwatch.md +1 -1
  76. package/docs/integrations/reddit-pixel.md +1 -1
  77. package/docs/integrations/rudderstack.md +1 -1
  78. package/docs/integrations/rybbit-analytics.md +1 -1
  79. package/docs/integrations/segment.md +1 -1
  80. package/docs/integrations/snapchat-pixel.md +1 -1
  81. package/docs/integrations/tiktok-pixel.md +1 -1
  82. package/docs/integrations/umami-analytics.md +1 -1
  83. package/docs/integrations/vercel-analytics.md +1 -1
  84. package/docs/integrations/x-pixel.md +1 -1
  85. package/docs/integrations/youtube.md +27 -22
  86. package/docs/upgrade-v3.md +176 -1
  87. package/package.json +30 -2
  88. package/readme.json +0 -19
@@ -0,0 +1,64 @@
1
+ import { resolveManifest } from "../../resolve.js";
2
+ import { vendorManifestContract } from "../../types.js";
3
+ import { resolveScriptUrl, trimToUndefined } from "../_shared/script-url.js";
4
+ const FRONT_CHAT_SCRIPT_SRC = 'https://chat-assets.frontapp.com/v1/chat.bundle.js';
5
+ const frontChatManifest = {
6
+ ...vendorManifestContract,
7
+ category: 'functionality',
8
+ install: [
9
+ {
10
+ async: true,
11
+ src: "{{scriptSrc}}",
12
+ type: 'loadScript'
13
+ }
14
+ ],
15
+ onLoadGranted: [
16
+ {
17
+ args: [
18
+ 'init',
19
+ '{{initOptions}}'
20
+ ],
21
+ global: 'FrontChat',
22
+ type: 'callGlobal'
23
+ }
24
+ ],
25
+ vendor: 'front-chat'
26
+ };
27
+ const front_chat_frontChat = function(options) {
28
+ const chatId = 'string' == typeof options?.chatId ? options.chatId.trim() : '';
29
+ if (!chatId) throw new Error('frontChat: chatId must be a non-empty string.');
30
+ const scriptSrc = resolveScriptUrl(trimToUndefined(options.scriptSrc), FRONT_CHAT_SCRIPT_SRC);
31
+ const nonce = trimToUndefined(options.nonce);
32
+ const initOptions = {
33
+ chatId,
34
+ useDefaultLauncher: options.useDefaultLauncher ?? true
35
+ };
36
+ if (nonce) initOptions.nonce = nonce;
37
+ const script = resolveManifest(frontChatManifest, {
38
+ initOptions,
39
+ scriptSrc
40
+ });
41
+ return {
42
+ ...script,
43
+ nonce,
44
+ onLoad (info) {
45
+ if (!info.hasConsent || !info.element?.isConnected) return;
46
+ const loadedNonce = info.element.nonce;
47
+ const resolved = loadedNonce ? resolveManifest(frontChatManifest, {
48
+ initOptions: {
49
+ ...initOptions,
50
+ nonce: loadedNonce
51
+ },
52
+ scriptSrc
53
+ }) : script;
54
+ resolved.onLoad?.(info);
55
+ },
56
+ target: 'body'
57
+ };
58
+ };
59
+ const front_chat_shutdownFrontChat = function() {
60
+ if ("u" > typeof window && 'function' == typeof window.FrontChat) window.FrontChat('shutdown', {
61
+ clearSession: true
62
+ });
63
+ };
64
+ export { frontChatManifest, front_chat_frontChat as frontChat, front_chat_shutdownFrontChat as shutdownFrontChat };
@@ -0,0 +1,98 @@
1
+ const consentCategories = [
2
+ 'necessary',
3
+ 'functionality',
4
+ 'experience',
5
+ 'measurement',
6
+ 'marketing'
7
+ ];
8
+ const readyEvent = 'zarazConsentAPIReady';
9
+ const isConsentApi = (api)=>'object' == typeof api && null !== api && 'APIReady' in api && true === api.APIReady && 'getAll' in api && 'function' == typeof api.getAll && 'set' in api && 'function' == typeof api.set && 'sendQueuedEvents' in api && 'function' == typeof api.sendQueuedEvents;
10
+ const getConsentApi = ()=>{
11
+ if ("u" < typeof window || !('zaraz' in window)) return;
12
+ const { zaraz } = window;
13
+ if ('object' != typeof zaraz || null === zaraz || !('consent' in zaraz)) return;
14
+ return isConsentApi(zaraz.consent) ? zaraz.consent : void 0;
15
+ };
16
+ const cloudflareZaraz = (options)=>{
17
+ const mappings = new Map();
18
+ for (const category of consentCategories)for (const purpose of options.purposes[category] ?? []){
19
+ if (!purpose.trim() || purpose !== purpose.trim()) throw new Error('Zaraz purpose IDs must be non-empty and have no surrounding whitespace.');
20
+ if (mappings.has(purpose)) throw new Error(`Zaraz purpose '${purpose}' is mapped more than once.`);
21
+ mappings.set(purpose, category);
22
+ }
23
+ if (0 === mappings.size) throw new Error('Map at least one Zaraz purpose to a c15t category.');
24
+ let latest;
25
+ let listeningDocument;
26
+ let initialized = false;
27
+ const pendingReplay = new Set();
28
+ const synchronize = ()=>{
29
+ const api = getConsentApi();
30
+ if (!latest || !api) return false;
31
+ const previous = api.getAll();
32
+ const permissions = {};
33
+ let changed = false;
34
+ for (const purpose of Object.keys(previous)){
35
+ const category = mappings.get(purpose);
36
+ const allowed = void 0 !== category && true === latest[category];
37
+ Object.defineProperty(permissions, purpose, {
38
+ enumerable: true,
39
+ value: allowed
40
+ });
41
+ changed ||= previous[purpose] !== allowed;
42
+ if (allowed && true !== previous[purpose]) pendingReplay.add(purpose);
43
+ }
44
+ for (const purpose of pendingReplay)if (true !== permissions[purpose]) pendingReplay.delete(purpose);
45
+ if (false !== options.hideBuiltInModal && true === api.modal) api.modal = false;
46
+ if (changed) api.set(permissions);
47
+ if (pendingReplay.size > 0 && false !== options.sendQueuedEvents) api.sendQueuedEvents();
48
+ pendingReplay.clear();
49
+ return true;
50
+ };
51
+ const apply = ()=>{
52
+ try {
53
+ if (!synchronize()) return false;
54
+ } catch (error) {
55
+ if (!options.onError) throw error;
56
+ options.onError(error);
57
+ return false;
58
+ }
59
+ listeningDocument?.removeEventListener(readyEvent, apply);
60
+ listeningDocument = void 0;
61
+ if (!initialized) {
62
+ initialized = true;
63
+ options.onReady?.();
64
+ }
65
+ return true;
66
+ };
67
+ const update = ({ consents })=>{
68
+ latest = consents;
69
+ if ("u" < typeof document) return;
70
+ let applied = false;
71
+ try {
72
+ applied = apply();
73
+ } finally{
74
+ if (!applied && !listeningDocument) {
75
+ listeningDocument = document;
76
+ document.addEventListener(readyEvent, apply);
77
+ }
78
+ }
79
+ };
80
+ return {
81
+ alwaysLoad: true,
82
+ callbackOnly: true,
83
+ category: 'necessary',
84
+ id: 'cloudflare-zaraz',
85
+ onConsentChange: (info)=>{
86
+ if (info.hasConsent) update(info);
87
+ },
88
+ onDispose: ()=>{
89
+ listeningDocument?.removeEventListener(readyEvent, apply);
90
+ listeningDocument = void 0;
91
+ latest = void 0;
92
+ initialized = false;
93
+ pendingReplay.clear();
94
+ },
95
+ onLoad: update
96
+ };
97
+ };
98
+ export { cloudflareZaraz };
@@ -48,12 +48,26 @@ const googleTagManagerManifest = {
48
48
  ],
49
49
  vendor: 'google-tag-manager'
50
50
  };
51
- const google_tag_manager_googleTagManager = function({ id, updateEventName, consentMapping }) {
52
- const manifest = withOptionalConsentMapping(googleTagManagerManifest, consentMapping);
51
+ const google_tag_manager_googleTagManager = function({ id, dataLayer = 'dataLayer', updateEventName, consentMapping }) {
52
+ let manifest = withOptionalConsentMapping(googleTagManagerManifest, consentMapping);
53
+ if ('dataLayer' !== dataLayer) {
54
+ manifest = JSON.parse(JSON.stringify(manifest).replace(/"(?:dataLayer|gtag)"/gu, (token)=>JSON.stringify('"dataLayer"' === token ? dataLayer : `${dataLayer}Gtag`)).replace('gtm.js?id={{id}}', `gtm.js?id={{id}}&l=${encodeURIComponent(dataLayer)}`));
55
+ manifest = {
56
+ ...manifest,
57
+ consentSignal: 'gtag',
58
+ consentSignalTarget: `${dataLayer}Gtag`
59
+ };
60
+ }
53
61
  const resolved = resolveManifest(manifest, {
54
62
  id,
55
63
  updateEventName: updateEventName ?? 'consent-update'
56
64
  });
57
- return resolved;
65
+ return {
66
+ ...resolved,
67
+ attributes: {
68
+ ...resolved.attributes,
69
+ 'data-c15t-layer': dataLayer
70
+ }
71
+ };
58
72
  };
59
73
  export { googleTagManagerManifest, google_tag_manager_googleTagManager as googleTagManager };
@@ -1,5 +1,5 @@
1
1
  import type { ConsentState, Script, ScriptCallbackInfo } from '@c15t/core';
2
- import type { BuiltInScriptIntegrationKey } from '../registry';
2
+ import type { BuiltInScriptIntegrationKey } from '../registry.js';
3
3
  /**
4
4
  * Typed view of `globalThis` used by script helper tests.
5
5
  *
@@ -31,7 +31,7 @@ export interface ExpectedScriptSnapshot {
31
31
  /** Expected consent-revocation persistence flag from the generated script. */
32
32
  persistAfterConsentRevoked: boolean | undefined;
33
33
  /** Expected remote script URL from the generated helper script. */
34
- src: string;
34
+ src: string | undefined;
35
35
  }
36
36
  /**
37
37
  * Consent state with only required storage enabled.
@@ -1,3 +1,3 @@
1
- import type { ResolvedManifest, VendorManifest } from '../types';
1
+ import type { ResolvedManifest, VendorManifest } from '../types.js';
2
2
  export declare const interpolateValue: (value: unknown, config: Record<string, unknown>) => unknown;
3
3
  export declare const compileManifest: (manifest: VendorManifest, config?: Record<string, unknown>) => ResolvedManifest;
@@ -1,3 +1,3 @@
1
1
  import type { Script } from '@c15t/core';
2
- import type { ResolvedManifest } from '../types';
2
+ import type { ResolvedManifest } from '../types.js';
3
3
  export declare const resolvedManifestToScript: (resolvedManifest: ResolvedManifest) => Script;
@@ -0,0 +1,46 @@
1
+ import type { ConsentSnapshot, Script } from '@c15t/core';
2
+ /** Flat event metadata accepted by the shared dispatcher. */
3
+ export type EventProperties = Record<string, string | number | boolean>;
4
+ /** Runtime inputs: the host owns event names, c15t owns delivery eligibility. */
5
+ export interface EventDispatcherOptions {
6
+ scripts: readonly Script[];
7
+ getSnapshot: () => ConsentSnapshot;
8
+ /** SDKs needing explicit SPA pageviews. Omit SDKs that track history themselves. */
9
+ pageviews?: readonly string[];
10
+ /** Injectable browser globals for testing or embedded runtimes. */
11
+ globals?: object;
12
+ }
13
+ /**
14
+ * Deliver events only to configured integrations with current analytics consent.
15
+ * Denied events are discarded, and a vendor error does not stop other deliveries.
16
+ *
17
+ * @param options - Scripts, current consent snapshot, optional SPA integrations
18
+ * and browser globals used to call their SDKs.
19
+ * @returns A dispatcher with `track` for named events and `pageview` for SPA
20
+ * navigation. The first pageview seeds the path without sending; later calls
21
+ * ignore duplicate paths and hash-only changes. Only configured `pageviews`
22
+ * integrations receive navigation events.
23
+ * @example
24
+ * ```ts
25
+ * const events = createEventDispatcher({ scripts, getSnapshot: kernel.getSnapshot,
26
+ * pageviews: ['segment'] });
27
+ * events.pageview(location.pathname);
28
+ * events.track('search', { length: 4 });
29
+ * events.pageview('/results');
30
+ * ```
31
+ */
32
+ export declare const createEventDispatcher: (options: EventDispatcherOptions) => {
33
+ /**
34
+ * Seed the initial path, then deliver permitted navigation events.
35
+ * @param path - Current path, including any query and hash. Hash-only changes are ignored.
36
+ * @returns Nothing. The first call and duplicate paths do not send events.
37
+ */
38
+ pageview(path: string): void;
39
+ /**
40
+ * Send a named event to each configured, currently permitted integration.
41
+ * @param event - Application event name understood by the configured SDKs.
42
+ * @param properties - Flat metadata copied for each vendor delivery.
43
+ * @returns Nothing. Denied events are discarded and SDK errors are isolated.
44
+ */
45
+ track(event: string, properties?: EventProperties): void;
46
+ };
@@ -77,6 +77,15 @@ export declare const BUILT_IN_INTEGRATION_CATEGORIES: readonly [{
77
77
  * the CLI can discover it from the same source.
78
78
  */
79
79
  export declare const builtInScriptIntegrations: readonly [{
80
+ readonly consentCategory: 'necessary';
81
+ readonly docsSlug: 'cloudflare-zaraz';
82
+ readonly hint: 'Consent bridge for tools managed by Zaraz';
83
+ readonly integrationCategory: 'tag-manager';
84
+ readonly key: 'cloudflareZaraz';
85
+ readonly label: 'Cloudflare Zaraz';
86
+ readonly packageSubpath: 'cloudflare-zaraz';
87
+ readonly vendor: 'cloudflare-zaraz';
88
+ }, {
80
89
  readonly consentCategory: 'necessary';
81
90
  readonly docsSlug: 'google-tag-manager';
82
91
  readonly hint: 'GTM container script';
@@ -184,6 +193,15 @@ export declare const builtInScriptIntegrations: readonly [{
184
193
  readonly label: 'Mixpanel Analytics';
185
194
  readonly packageSubpath: 'mixpanel-analytics';
186
195
  readonly vendor: 'mixpanel-analytics';
196
+ }, {
197
+ readonly consentCategory: 'measurement';
198
+ readonly docsSlug: 'one-dollar-stats';
199
+ readonly hint: 'Website analytics with no API key';
200
+ readonly integrationCategory: 'analytics';
201
+ readonly key: 'oneDollarStats';
202
+ readonly label: 'OneDollarStats';
203
+ readonly packageSubpath: 'one-dollar-stats';
204
+ readonly vendor: 'one-dollar-stats';
187
205
  }, {
188
206
  readonly consentCategory: 'measurement';
189
207
  readonly docsSlug: 'hotjar';
@@ -310,6 +328,15 @@ export declare const builtInScriptIntegrations: readonly [{
310
328
  readonly label: 'Crisp';
311
329
  readonly packageSubpath: 'crisp';
312
330
  readonly vendor: 'crisp';
331
+ }, {
332
+ readonly consentCategory: 'functionality';
333
+ readonly docsSlug: 'front-chat';
334
+ readonly hint: 'Live chat widget';
335
+ readonly integrationCategory: 'functional';
336
+ readonly key: 'frontChat';
337
+ readonly label: 'Front Chat';
338
+ readonly packageSubpath: 'front-chat';
339
+ readonly vendor: 'front-chat';
313
340
  }, {
314
341
  readonly consentCategory: 'functionality';
315
342
  readonly docsSlug: 'intercom';
@@ -337,6 +364,15 @@ export declare const builtInScriptIntegrations: readonly [{
337
364
  readonly label: 'OpenAI Pixel (ChatGPT Ads)';
338
365
  readonly packageSubpath: 'openai-pixel';
339
366
  readonly vendor: 'openai-pixel';
367
+ }, {
368
+ readonly consentCategory: 'marketing';
369
+ readonly docsSlug: 'pinterest-tag';
370
+ readonly hint: 'Pinterest ads tracking';
371
+ readonly integrationCategory: 'ads-and-pixels';
372
+ readonly key: 'pinterestTag';
373
+ readonly label: 'Pinterest Tag';
374
+ readonly packageSubpath: 'pinterest-tag';
375
+ readonly vendor: 'pinterest-tag';
340
376
  }, {
341
377
  readonly consentCategory: 'marketing';
342
378
  readonly docsSlug: 'reddit-pixel';
@@ -1,5 +1,5 @@
1
1
  import type { Script } from '@c15t/core';
2
- import type { VendorManifest } from './types';
2
+ import type { VendorManifest } from './types.js';
3
3
  /**
4
4
  * Compiles a `VendorManifest` + config into a `Script` object.
5
5
  *
@@ -1,4 +1,4 @@
1
- import type { VendorManifest } from '../../types';
1
+ import type { VendorManifest } from '../../types.js';
2
2
  /**
3
3
  * Optional tracking call inserted between the vendor init call and script load.
4
4
  */
@@ -0,0 +1,295 @@
1
+ import type { Script } from '@c15t/core';
2
+ /**
3
+ * The 20 Pinterest Tag event types accepted by `pintrk('track', ...)`.
4
+ *
5
+ * Conversion tracking and reporting require one of these names. Any other
6
+ * string is treated as a user-defined event, which Pinterest makes available
7
+ * for audience targeting only.
8
+ *
9
+ * @see {@link https://help.pinterest.com/en/business/article/add-event-codes} Add event codes
10
+ */
11
+ export type PinterestTagEventName = 'pagevisit' | 'viewcategory' | 'search' | 'addtocart' | 'checkout' | 'watchvideo' | 'signup' | 'lead' | 'custom' | 'addpaymentinfo' | 'addtowishlist' | 'initiatecheckout' | 'subscribe' | 'viewcontent' | 'contact' | 'schedule' | 'findlocation' | 'customizeproduct' | 'submitapplication' | 'starttrial';
12
+ /**
13
+ * A product entry in `PinterestTagEventData.line_items`.
14
+ *
15
+ * Pinterest reads product-level details from `line_items`, not from the
16
+ * top level of the event data.
17
+ */
18
+ export interface PinterestTagLineItem {
19
+ /** Product name, for example `Parker Boots`. */
20
+ product_name?: string;
21
+ /** Product identifier or SKU. */
22
+ product_id?: string;
23
+ /** Product category, for example `Shoes`. */
24
+ product_category?: string;
25
+ /** Variant identifier, for example `1414-Red`. */
26
+ product_variant_id?: string;
27
+ /** Human-readable variant, for example `Red`. */
28
+ product_variant?: string;
29
+ /** Unit price of the product. */
30
+ product_price?: number;
31
+ /** Quantity of this product in the event. */
32
+ product_quantity?: number;
33
+ /** Brand name. */
34
+ product_brand?: string;
35
+ [key: string]: unknown;
36
+ }
37
+ /**
38
+ * Currency codes Pinterest accepts in `PinterestTagEventData.currency`.
39
+ *
40
+ * Pinterest converts the reported value to your advertiser account currency.
41
+ */
42
+ export type PinterestTagCurrency = 'AED' | 'AMD' | 'ARS' | 'AUD' | 'AZN' | 'BAM' | 'BGN' | 'BHD' | 'BMD' | 'BND' | 'BOB' | 'BRL' | 'BSD' | 'CAD' | 'CHF' | 'CLP' | 'CNY' | 'COP' | 'CRC' | 'CZK' | 'DKK' | 'DOP' | 'EGP' | 'EUR' | 'FJD' | 'GBP' | 'GEL' | 'GIP' | 'HKD' | 'HNL' | 'HRK' | 'HUF' | 'IDR' | 'ILS' | 'INR' | 'ISK' | 'JMD' | 'JPY' | 'KGS' | 'KRW' | 'KWD' | 'KZT' | 'MAD' | 'MDL' | 'MOP' | 'MXN' | 'MYR' | 'NOK' | 'NZD' | 'OMR' | 'PAB' | 'PEN' | 'PHP' | 'PKR' | 'PLN' | 'QAR' | 'RON' | 'RSD' | 'RUB' | 'SAR' | 'SEK' | 'SGD' | 'THB' | 'TRY' | 'TWD' | 'UAH' | 'USD' | 'UYU' | 'VND' | 'ZAR';
43
+ /**
44
+ * Event data accepted by `pintrk('track', ...)`.
45
+ *
46
+ * Pinterest documents the same field set for every event type; which fields
47
+ * are meaningful depends on the event. All event data is available for
48
+ * audience targeting, but only `value` and `order_quantity` appear in paid
49
+ * and organic conversion reporting. Any additional keys are forwarded
50
+ * unchanged.
51
+ */
52
+ export interface PinterestTagEventData {
53
+ /**
54
+ * Unique event identifier used to deduplicate against Conversions API
55
+ * events. Pinterest also accepts this under `eventID` or `eid`.
56
+ */
57
+ event_id?: string;
58
+ /** Case-sensitive alias for `event_id`. */
59
+ eventID?: string;
60
+ /** Case-sensitive alias for `event_id`. */
61
+ eid?: string;
62
+ /**
63
+ * Total monetary value for commerce events such as `checkout`.
64
+ *
65
+ * Available in conversion reporting.
66
+ */
67
+ value?: number;
68
+ /**
69
+ * Total number of items in the order.
70
+ *
71
+ * Available in conversion reporting.
72
+ */
73
+ order_quantity?: number;
74
+ /**
75
+ * Currency code for `value`, for example `USD`. Required for `addtocart`
76
+ * and `checkout` in catalog sales campaigns.
77
+ */
78
+ currency?: PinterestTagCurrency | (string & Record<never, never>);
79
+ /**
80
+ * Order identifier for `checkout` events. Required for conversion
81
+ * analysis reporting.
82
+ */
83
+ order_id?: string;
84
+ /** Promo code applied to the order. */
85
+ promo_code?: string;
86
+ /** Property or store name, for example `Athleta`. */
87
+ property?: string;
88
+ /** Query text for `search` events. */
89
+ search_query?: string;
90
+ /** Video title for `watchvideo` events. */
91
+ video_title?: string;
92
+ /** Lead type for `lead` events, for example `Newsletter`. */
93
+ lead_type?: string;
94
+ /** Products associated with the event. */
95
+ line_items?: PinterestTagLineItem[];
96
+ [key: string]: unknown;
97
+ }
98
+ /**
99
+ * Options passed as the third argument to `pintrk('load', tagId, options)`.
100
+ */
101
+ export interface PinterestTagLoadOptions {
102
+ /**
103
+ * Email address (plain or SHA-256 hashed) for Pinterest enhanced match.
104
+ *
105
+ * Only provide this when your application has separately obtained the
106
+ * appropriate user consent to share it with Pinterest.
107
+ */
108
+ em?: string;
109
+ /**
110
+ * Hashed external user identifier used by Pinterest for attribution.
111
+ */
112
+ external_id?: string;
113
+ [key: string]: unknown;
114
+ }
115
+ /**
116
+ * Optional callback passed as the last argument to `pintrk('track', ...)`.
117
+ *
118
+ * @param didInit - `true` when Pinterest constructed the event call
119
+ * successfully, `false` when it detected an error.
120
+ * @param error - Error description when `didInit` is `false`, otherwise
121
+ * `undefined`.
122
+ */
123
+ export type PinterestTagEventCallback = (didInit: boolean, error?: string) => void;
124
+ interface PinterestTagFunction {
125
+ (command: 'load', tagId: string, options?: PinterestTagLoadOptions): void;
126
+ (command: 'page'): void;
127
+ (command: 'track', eventName: PinterestTagEventName | (string & Record<never, never>), eventData?: PinterestTagEventData, callback?: PinterestTagEventCallback): void;
128
+ (command: 'setconsent', consent: boolean): void;
129
+ (command: 'set', data: Record<string, unknown>): void;
130
+ (command: string, ...args: unknown[]): void;
131
+ }
132
+ declare global {
133
+ interface Window {
134
+ pintrk?: PinterestTagFunction & {
135
+ queue?: unknown[][];
136
+ version?: string;
137
+ };
138
+ }
139
+ }
140
+ /**
141
+ * Pinterest Tag vendor manifest.
142
+ *
143
+ * Mirrors Pinterest's v3 base code: a `pintrk` stub that pushes argument
144
+ * arrays onto `pintrk.queue`, `pintrk.version = "3.0"`, then the async
145
+ * `core.js` loader.
146
+ *
147
+ * Pinterest exposes a runtime consent API via `pintrk('setconsent', boolean)`.
148
+ * `setconsent(false)` stops events and clears Pinterest's first-party
149
+ * storage, so the script persists after consent revocation and receives the
150
+ * updated consent state instead of being removed from the document.
151
+ */
152
+ export declare const pinterestTagManifest: {
153
+ readonly kind: "c15t.vendor-manifest";
154
+ readonly schemaVersion: 1;
155
+ readonly bootstrap: [{
156
+ readonly ifUndefined: true;
157
+ readonly name: 'pintrk';
158
+ readonly properties: {
159
+ readonly version: '3.0';
160
+ };
161
+ readonly queue: {
162
+ readonly property: 'queue';
163
+ };
164
+ readonly queueFormat: 'array';
165
+ readonly type: 'defineStubFunction';
166
+ }];
167
+ readonly category: 'marketing';
168
+ readonly install: [{
169
+ readonly args: ["load", "{{tagId}}"];
170
+ readonly global: 'pintrk';
171
+ readonly type: 'callGlobal';
172
+ }, {
173
+ readonly args: ["setconsent", true];
174
+ readonly global: 'pintrk';
175
+ readonly type: 'callGlobal';
176
+ }, {
177
+ readonly args: ["page"];
178
+ readonly global: 'pintrk';
179
+ readonly type: 'callGlobal';
180
+ }, {
181
+ readonly async: true;
182
+ readonly src: '{{scriptUrl}}';
183
+ readonly type: 'loadScript';
184
+ }];
185
+ readonly onConsentDenied: [{
186
+ readonly args: ["setconsent", false];
187
+ readonly global: 'pintrk';
188
+ readonly type: 'callGlobal';
189
+ }];
190
+ readonly onConsentGranted: [{
191
+ readonly args: ["setconsent", true];
192
+ readonly global: 'pintrk';
193
+ readonly type: 'callGlobal';
194
+ }];
195
+ readonly persistAfterConsentRevoked: true;
196
+ readonly vendor: 'pinterest-tag';
197
+ };
198
+ export interface PinterestTagOptions {
199
+ /**
200
+ * Your Pinterest Tag ID.
201
+ * @example `2613654212508`
202
+ */
203
+ tagId: string;
204
+ /**
205
+ * Optional payload passed to `pintrk('load', tagId, loadOptions)`, for
206
+ * example enhanced match values.
207
+ *
208
+ * Do not provide user identifiers unless the appropriate consent and
209
+ * privacy requirements have already been satisfied.
210
+ */
211
+ loadOptions?: PinterestTagLoadOptions;
212
+ /**
213
+ * Queue the default `pintrk('page')` page-visit event during setup.
214
+ * @default true
215
+ */
216
+ trackPageVisit?: boolean;
217
+ /** Pinterest Tag loader URL. */
218
+ scriptUrl?: string;
219
+ }
220
+ /**
221
+ * Creates a Pinterest Tag script.
222
+ *
223
+ * This script persists after consent is revoked because Pinterest exposes
224
+ * `pintrk('setconsent', boolean)`, which lets c15t disable tracking and clear
225
+ * Pinterest's first-party storage without removing the script element.
226
+ *
227
+ * @param options.tagId - Pinterest Tag ID used in `pintrk('load', ...)`.
228
+ * Numeric string from Pinterest Ads Manager, for example `'2613654212508'`.
229
+ * @param options.loadOptions - Optional object passed as the third argument to
230
+ * `pintrk('load', tagId, loadOptions)`, for example `{ em: 'user@example.com' }`.
231
+ * @param options.trackPageVisit - Whether to queue the default `pintrk('page')`
232
+ * call during setup. Defaults to `true`.
233
+ * @param options.scriptUrl - Override for Pinterest's `core.js` loader URL.
234
+ * @returns A resolved c15t `Script` configuration that defines the `pintrk`
235
+ * queue stub, queues `load`, `setconsent`, and optionally `page`, then loads
236
+ * Pinterest's `core.js`.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * const script = pinterestTag({
241
+ * tagId: '2613654212508',
242
+ * loadOptions: { em: 'user@example.com' },
243
+ * trackPageVisit: false,
244
+ * });
245
+ * ```
246
+ *
247
+ * @see {@link https://help.pinterest.com/en/business/article/install-the-pinterest-tag} Pinterest Tag documentation
248
+ */
249
+ export declare const pinterestTag: ({ tagId, loadOptions, trackPageVisit, scriptUrl, }: PinterestTagOptions) => Script;
250
+ /**
251
+ * Tracks a Pinterest Tag event.
252
+ *
253
+ * This helper is a no-op until Pinterest has been initialized by c15t, so it
254
+ * is safe to call before marketing consent is granted. It does not bypass
255
+ * consent: after revocation Pinterest's own `setconsent(false)` state
256
+ * suppresses the event.
257
+ *
258
+ * @param eventName - One of Pinterest's 20 event types or a user-defined
259
+ * event name.
260
+ * @param eventData - Optional event data, including `event_id` for Tag plus
261
+ * Conversions API deduplication.
262
+ * @param callback - Optional `(didInit, error)` callback Pinterest invokes
263
+ * after constructing the event call. Useful for surfacing tag errors in
264
+ * development.
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * pinterestTagEvent('checkout', {
269
+ * event_id: 'event-123',
270
+ * value: 99.99,
271
+ * order_quantity: 1,
272
+ * currency: 'USD',
273
+ * order_id: 'order-123',
274
+ * line_items: [
275
+ * {
276
+ * product_name: 'Parker Boots',
277
+ * product_id: '1414',
278
+ * product_price: 99.99,
279
+ * product_quantity: 1,
280
+ * },
281
+ * ],
282
+ * });
283
+ * ```
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * pinterestTagEvent('lead', { lead_type: 'Newsletter' }, (didInit, error) => {
288
+ * if (!didInit) {
289
+ * console.error(error);
290
+ * }
291
+ * });
292
+ * ```
293
+ */
294
+ export declare const pinterestTagEvent: (eventName: PinterestTagEventName | (string & Record<never, never>), eventData?: PinterestTagEventData, callback?: PinterestTagEventCallback) => void;
295
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { Script } from '@c15t/core';
2
- import type { VendorManifest } from '../../types';
2
+ import type { VendorManifest } from '../../types.js';
3
3
  declare global {
4
4
  interface Window {
5
5
  adobeDataLayer?: unknown[];
@@ -49,6 +49,8 @@ export declare const gtagManifest: {
49
49
  readonly vendor: 'gtag';
50
50
  };
51
51
  export interface GtagOptions {
52
+ /** Parameters forwarded to gtag config. */
53
+ config?: Record<string, unknown>;
52
54
  /**
53
55
  * Your gtag id
54
56
  * @example `G-XXXXXXX`
@@ -90,4 +92,4 @@ export interface GtagOptions {
90
92
  * @param options - The options for the gtag script.
91
93
  * @returns The Google Tag Manager script.
92
94
  */
93
- export declare const gtag: ({ id, category, consentMapping, script, }: GtagOptions) => Script;
95
+ export declare const gtag: ({ id, config, category, consentMapping, script, }: GtagOptions) => Script;
@@ -1,5 +1,5 @@
1
1
  import type { Script } from '@c15t/core';
2
- import type { VendorManifest } from '../../types';
2
+ import type { VendorManifest } from '../../types.js';
3
3
  declare global {
4
4
  interface Window {
5
5
  _paq?: unknown[];