tracklane 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,7 +91,7 @@ build, so it is never ahead of the library.
91
91
  | -------- | -------------------- | ------ |
92
92
  | GA4 | Measurement Protocol | shipped |
93
93
  | Meta | Conversions API | shipped |
94
- | LinkedIn | Conversions API | next |
94
+ | LinkedIn | Conversions API | shipped |
95
95
  | PostHog | Capture API | shipped |
96
96
  | X | Conversions API | next |
97
97
 
@@ -89,6 +89,41 @@ interface Ga4ServerConfig {
89
89
  */
90
90
  declare function ga4$1(credentials: Ga4ServerCredentials, config?: Ga4ServerConfig): ServerProvider;
91
91
  //#endregion
92
+ //#region src/providers/linkedin.server.d.ts
93
+ /** Credentials for LinkedIn's Conversions API. */
94
+ interface LinkedinServerCredentials {
95
+ /** An access token authorized for the conversion's ad account. */
96
+ accessToken: string;
97
+ }
98
+ /** Configuration for the LinkedIn Conversions API provider. */
99
+ interface LinkedinServerConfig {
100
+ /** Conversion IDs minted in Campaign Manager. `null` explicitly suppresses an event. */
101
+ events: Record<string, EventBinding<string>>;
102
+ /** LinkedIn's Marketing API version, in YYYYMM form. Defaults to `202609`. */
103
+ apiVersion?: string;
104
+ }
105
+ /**
106
+ * Sends event-specific conversions to LinkedIn's Conversions API.
107
+ * Value and currency become conversionValue; items, transaction_id, custom
108
+ * properties, traits and consent have no documented field on this endpoint.
109
+ * The host's deduplication ID becomes eventId, without generating one.
110
+ *
111
+ * @param credentials - The ad account's Conversions API access token.
112
+ * @param config - Canonical event to conversion ID map and API version.
113
+ * @returns A server provider for `createTracking`.
114
+ * @example
115
+ * ```ts
116
+ * import { createTracking, linkedin } from 'tracklane/server';
117
+ * const { track } = createTracking({
118
+ * providers: [linkedin({ accessToken }, { events: { purchase: '12345' } })],
119
+ * });
120
+ * await track('purchase', { value: 15, currency: 'BRL' }, {
121
+ * user: { email: 'buyer@example.com' }, dedupId: 'order-1',
122
+ * });
123
+ * ```
124
+ */
125
+ declare function linkedin$1(credentials: LinkedinServerCredentials, config: LinkedinServerConfig): ServerProvider;
126
+ //#endregion
92
127
  //#region src/providers/meta.server.d.ts
93
128
  /** Credentials for Meta's Conversions API. */
94
129
  interface MetaServerCredentials {
@@ -135,6 +170,11 @@ interface MetaServerConfig {
135
170
  /**
136
171
  * Meta's Conversions API.
137
172
  *
173
+ * If any item lacks a nonempty `item_id`, the complete `contents` list is
174
+ * omitted while totals and order metadata remain unchanged. This avoids
175
+ * presenting a partial order or inventing product identifiers. The error
176
+ * channel receives one payload-free warning after HTTP acceptance.
177
+ *
138
178
  * @param credentials - The pixel id and an access token.
139
179
  * @param config - Event overrides, the default action source, and Meta's own
140
180
  * levers: the API version, a test event code, and Limited Data Use.
@@ -326,8 +366,9 @@ type ProviderDefault = "passthrough" | "ignore";
326
366
  *
327
367
  * Fanning out to N vendors means no single call site can catch anything, so
328
368
  * this is the replica of the `try/catch` a host would have written around a
329
- * hand-made call. `severity` separates "this did not arrive" from "this
330
- * arrived, and here is something you should know".
369
+ * hand-made call. `severity` separates an observed failure from a caveat.
370
+ * Neither proves delivery: a browser command may only be queued, an HTTP
371
+ * success does not prove processing, and a network error may follow acceptance.
331
372
  *
332
373
  * It never carries event contents: the payload holds raw personal data by
333
374
  * design, and no library-owned surface re-emits it into a host's logs.
@@ -337,7 +378,7 @@ interface TrackingError {
337
378
  provider: string;
338
379
  /** The canonical event name being sent when this happened. */
339
380
  event: string;
340
- /** `'error'` when the event did not arrive; `'warning'` when it arrived but is worth a look. */
381
+ /** `'error'` for an observed failure; `'warning'` for a caveat. Neither guarantees delivery. */
341
382
  severity: "error" | "warning";
342
383
  /** What happened, in one line, excluding the payload: event data never appears here. */
343
384
  message: string;
@@ -433,6 +474,8 @@ interface ServerTrackingOptions {
433
474
  /**
434
475
  * The one diagnostic channel. Without it the library is silent, exactly as
435
476
  * a hand-written vendor call is.
477
+ * Unmapped events on an `ignore` provider report warnings; explicit `null`
478
+ * exclusions stay silent. Attach a reporter in development to catch omissions.
436
479
  */
437
480
  onError?: (error: TrackingError) => void;
438
481
  }
@@ -499,8 +542,10 @@ interface Ga4BrowserConfig {
499
542
  * configures the property is yours. Use {@link ConsentState} through
500
543
  * `consent()` for changes after that.
501
544
  *
502
- * @param measurementId - The `G-` id of the data stream, matching the tag on
503
- * the page.
545
+ * With no measurement ID, events keep the tag's configured routing. Pass a
546
+ * measurement ID to address that destination through `send_to` instead.
547
+ * Neither form installs or configures the tag.
548
+ *
504
549
  * @param config - Rarely needed event name overrides.
505
550
  * @returns A provider for `createTracking` from `tracklane/browser`.
506
551
  *
@@ -509,13 +554,50 @@ interface Ga4BrowserConfig {
509
554
  * import { createTracking, ga4 } from 'tracklane/browser';
510
555
  *
511
556
  * // Google's own snippet is already in the page.
512
- * const { track } = createTracking({ providers: [ga4('G-XXXXXXX')] });
557
+ * const { track } = createTracking({ providers: [ga4()] });
513
558
  *
514
559
  * track('purchase', { transaction_id: 'T-1', value: 49.9, currency: 'BRL' });
515
560
  * ```
516
561
  */
562
+ declare function ga4(config?: Ga4BrowserConfig): BrowserProvider;
563
+ /**
564
+ * Addresses one configured Google destination instead of using the tag's
565
+ * default routing. Existing calls keep their `send_to` behavior.
566
+ *
567
+ * @param measurementId - The `G-` ID of the configured data stream.
568
+ * @param config - Rarely needed event name overrides.
569
+ * @returns A provider for `createTracking` from `tracklane/browser`.
570
+ * @example
571
+ * ```ts
572
+ * const provider = ga4('G-XXXXXXX', { events: { my_event: 'my_ga4_name' } });
573
+ * ```
574
+ */
517
575
  declare function ga4(measurementId: string, config?: Ga4BrowserConfig): BrowserProvider;
518
576
  //#endregion
577
+ //#region src/providers/linkedin.browser.d.ts
578
+ /** Configuration for the LinkedIn Insight Tag provider. */
579
+ interface LinkedinBrowserConfig {
580
+ /** Conversion IDs minted in Campaign Manager. `null` explicitly suppresses an event. */
581
+ events: Record<string, EventBinding<string>>;
582
+ }
583
+ /**
584
+ * Calls the host-installed LinkedIn Insight Tag for event-specific conversions.
585
+ * LinkedIn has no event names, user-identification command or consent command.
586
+ * Items and custom parameters have no documented slot and are not forwarded.
587
+ *
588
+ * @param config - The canonical event to conversion ID map.
589
+ * @returns A browser provider; it does not install the Insight Tag.
590
+ * @example
591
+ * ```ts
592
+ * import { createTracking, linkedin } from 'tracklane/browser';
593
+ * const { track } = createTracking({
594
+ * providers: [linkedin({ events: { purchase: '12345' } })],
595
+ * });
596
+ * track('purchase', { value: 15, currency: 'BRL' }, { dedupId: 'order-1' });
597
+ * ```
598
+ */
599
+ declare function linkedin(config: LinkedinBrowserConfig): BrowserProvider;
600
+ //#endregion
519
601
  //#region src/providers/meta.browser.d.ts
520
602
  /** Optional configuration for the Meta browser provider. */
521
603
  interface MetaBrowserConfig {
@@ -561,6 +643,11 @@ interface MetaBrowserConfig {
561
643
  * this adapter cannot deliver. Which pixels exist is declared by your snippet,
562
644
  * in `fbq('init')`.
563
645
  *
646
+ * If any item lacks a nonempty `item_id`, the complete `contents` list is
647
+ * omitted while totals and order metadata remain unchanged. This avoids
648
+ * presenting a partial order or inventing product identifiers. The error
649
+ * channel receives one payload-free warning after the browser command returns.
650
+ *
564
651
  * @param config - Event name overrides, merged over the built-in map.
565
652
  * @returns A provider for `createTracking` from `tracklane/browser`.
566
653
  *
@@ -648,8 +735,13 @@ interface BrowserProvider<Target = string> {
648
735
  * installs again, including providers that were already there.
649
736
  */
650
737
  install?(): void | Promise<void>;
651
- /** Sends one event to this vendor. Throwing here reaches `onError`, not the caller. */
652
- track(name: Target, data: EventData, options: TrackOptions): void;
738
+ /**
739
+ * Sends one event to this vendor. Throwing here reaches `onError`, not the caller.
740
+ * The optional report callback emits a payload-free warning; it is not proof
741
+ * of delivery or provider-side processing. Older three-argument providers
742
+ * and direct calls remain supported.
743
+ */
744
+ track(name: Target, data: EventData, options: TrackOptions, report?: (message: string) => void): void;
653
745
  /** Absent where the vendor keeps no user of its own. */
654
746
  identify?(user: UserData, traits?: Record<string, unknown>): void;
655
747
  /** Absent where the vendor documents no consent command. */
@@ -662,6 +754,8 @@ interface BrowserTrackingOptions {
662
754
  /**
663
755
  * The one diagnostic channel. Without it the library is silent, exactly as
664
756
  * a hand-written vendor call is.
757
+ * Unmapped events on an `ignore` provider report warnings; explicit `null`
758
+ * exclusions stay silent. Attach a reporter in development to catch omissions.
665
759
  */
666
760
  onError?: (error: TrackingError) => void;
667
761
  }
@@ -698,5 +792,5 @@ interface BrowserTracking {
698
792
  */
699
793
  declare function createTracking(options: BrowserTrackingOptions): BrowserTracking;
700
794
  //#endregion
701
- export { MetaServerCredentials as A, ProviderDefault as C, PosthogServerCredentials as D, PosthogServerConfig as E, VendorResponseError as F, isVendorResponseError as I, Ga4ServerConfig as M, Ga4ServerCredentials as N, posthog$1 as O, ga4$1 as P, EventItem as S, UserData as T, createTracking$1 as _, createTracking as a, EventBinding as b, MetaBrowserConfig as c, ga4 as d, EventContext as f, ServerTrackingOptions as g, ServerTracking as h, TrackOptions as i, meta$1 as j, MetaServerConfig as k, meta as l, ServerProvider as m, BrowserTracking as n, PosthogBrowserConfig as o, ResolvedContext as p, BrowserTrackingOptions as r, posthog as s, BrowserProvider as t, Ga4BrowserConfig as u, ActionSource as v, TrackingError as w, EventData as x, ConsentState as y };
702
- //# sourceMappingURL=browser-_0LRgTpx.d.ts.map
795
+ export { posthog$1 as A, VendorResponseError as B, EventData as C, UserData as D, TrackingError as E, LinkedinServerCredentials as F, linkedin$1 as I, Ga4ServerConfig as L, MetaServerCredentials as M, meta$1 as N, PosthogServerConfig as O, LinkedinServerConfig as P, Ga4ServerCredentials as R, EventBinding as S, ProviderDefault as T, isVendorResponseError as V, ServerTracking as _, createTracking as a, ActionSource as b, MetaBrowserConfig as c, linkedin as d, Ga4BrowserConfig as f, ServerProvider as g, ResolvedContext as h, TrackOptions as i, MetaServerConfig as j, PosthogServerCredentials as k, meta as l, EventContext as m, BrowserTracking as n, PosthogBrowserConfig as o, ga4 as p, BrowserTrackingOptions as r, posthog as s, BrowserProvider as t, LinkedinBrowserConfig as u, ServerTrackingOptions as v, EventItem as w, ConsentState as x, createTracking$1 as y, ga4$1 as z };
796
+ //# sourceMappingURL=browser-34-CHcxK.d.ts.map
@@ -0,0 +1,432 @@
1
+ import { i as resolveEvent, n as makeReporter, r as messageOf, t as assertUniqueProviders } from "./dispatch-B32jGhlh.js";
2
+ //#region src/providers/ga4.browser.ts
3
+ function gtagWindow() {
4
+ return typeof window === "undefined" ? void 0 : window;
5
+ }
6
+ /**
7
+ * Sends a command, or fails the way a hand-written call would.
8
+ *
9
+ * Writing `gtag(…)` on a page without Google's snippet throws, and this
10
+ * library replicates that rather than swallowing it: a silent no-op is how a
11
+ * host ends up believing it is measuring something.
12
+ */
13
+ function push$1(...args) {
14
+ const gtag = gtagWindow()?.gtag;
15
+ if (!gtag) throw new Error("ga4: window.gtag is not defined. Add Google's tag to the page before creating tracking.");
16
+ gtag(...args);
17
+ }
18
+ function ga4(measurementIdOrConfig = {}, config = {}) {
19
+ const measurementId = typeof measurementIdOrConfig === "string" ? measurementIdOrConfig : void 0;
20
+ const { events } = typeof measurementIdOrConfig === "string" ? config : measurementIdOrConfig;
21
+ return {
22
+ name: "ga4",
23
+ default: "passthrough",
24
+ ...events ? { events } : {},
25
+ track(name, data, _options) {
26
+ push$1("event", name, measurementId === void 0 ? data : {
27
+ ...data,
28
+ send_to: measurementId
29
+ });
30
+ },
31
+ identify(user, traits) {
32
+ if (user.userId !== void 0) push$1("set", { user_id: user.userId });
33
+ if (traits) push$1("set", "user_properties", traits);
34
+ },
35
+ consent(command, state) {
36
+ push$1("consent", command, state);
37
+ }
38
+ };
39
+ }
40
+ //#endregion
41
+ //#region src/providers/linkedin.browser.ts
42
+ /**
43
+ * Calls the host-installed LinkedIn Insight Tag for event-specific conversions.
44
+ * LinkedIn has no event names, user-identification command or consent command.
45
+ * Items and custom parameters have no documented slot and are not forwarded.
46
+ *
47
+ * @param config - The canonical event to conversion ID map.
48
+ * @returns A browser provider; it does not install the Insight Tag.
49
+ * @example
50
+ * ```ts
51
+ * import { createTracking, linkedin } from 'tracklane/browser';
52
+ * const { track } = createTracking({
53
+ * providers: [linkedin({ events: { purchase: '12345' } })],
54
+ * });
55
+ * track('purchase', { value: 15, currency: 'BRL' }, { dedupId: 'order-1' });
56
+ * ```
57
+ */
58
+ function linkedin(config) {
59
+ return {
60
+ name: "linkedin",
61
+ default: "ignore",
62
+ events: config.events,
63
+ track(name, data, options) {
64
+ const conversionId = Number(name);
65
+ if (!/^\d+$/.test(name) || !Number.isSafeInteger(conversionId) || conversionId <= 0) throw new Error("linkedin: conversion_id must be a positive safe integer from Campaign Manager");
66
+ const sdk = typeof window === "undefined" ? void 0 : window.lintrk;
67
+ if (!sdk) throw new Error("linkedin: window.lintrk is missing. Install the Insight Tag before tracking.");
68
+ const payload = { conversion_id: conversionId };
69
+ if (data.currency !== void 0) payload.conversion_currency = data.currency;
70
+ if (data.value !== void 0) payload.conversion_value = data.value;
71
+ if (options.dedupId !== void 0) payload.event_id = options.dedupId;
72
+ sdk("track", payload);
73
+ }
74
+ };
75
+ }
76
+ //#endregion
77
+ //#region src/providers/meta.browser.ts
78
+ /**
79
+ * The names Meta accepts through `fbq('track')`. Anything else is a custom
80
+ * event and goes through `trackCustom`, which is a different command rather
81
+ * than a different argument.
82
+ *
83
+ * Seventeen standard events plus `PageView`, which Meta documents separately
84
+ * from the seventeen and which the base snippet fires by itself.
85
+ */
86
+ const STANDARD_EVENTS = [
87
+ "AddPaymentInfo",
88
+ "AddToCart",
89
+ "AddToWishlist",
90
+ "CompleteRegistration",
91
+ "Contact",
92
+ "CustomizeProduct",
93
+ "Donate",
94
+ "FindLocation",
95
+ "InitiateCheckout",
96
+ "Lead",
97
+ "PageView",
98
+ "Purchase",
99
+ "Schedule",
100
+ "Search",
101
+ "StartTrial",
102
+ "SubmitApplication",
103
+ "Subscribe",
104
+ "ViewContent"
105
+ ];
106
+ /**
107
+ * How Meta spells the canonical vocabulary, which is GA4's.
108
+ *
109
+ * Only canonical names appear here. Meta has eight more standard events with
110
+ * no GA4 counterpart (`Subscribe`, `StartTrial`, `Schedule`, `Contact`,
111
+ * `Donate`, `FindLocation`, `CustomizeProduct`, `SubmitApplication`), and
112
+ * inventing canonical names for them would make this library the author of a
113
+ * vocabulary it deliberately borrows. A host that fires those maps its own
114
+ * event name to them through `events`.
115
+ *
116
+ * A canonical event with no standard counterpart is **not** dropped: it goes
117
+ * to `trackCustom` under its own name, because by hand that event would have
118
+ * been sent.
119
+ */
120
+ const CANONICAL_EVENTS = {
121
+ page_view: "PageView",
122
+ view_item: "ViewContent",
123
+ search: "Search",
124
+ add_to_cart: "AddToCart",
125
+ add_to_wishlist: "AddToWishlist",
126
+ begin_checkout: "InitiateCheckout",
127
+ add_payment_info: "AddPaymentInfo",
128
+ purchase: "Purchase",
129
+ sign_up: "CompleteRegistration",
130
+ generate_lead: "Lead"
131
+ };
132
+ function fbqWindow() {
133
+ return typeof window === "undefined" ? void 0 : window;
134
+ }
135
+ /**
136
+ * Sends a command, or fails the way a hand-written call would.
137
+ *
138
+ * Writing `fbq(…)` on a page without Meta's snippet throws, and this library
139
+ * replicates that rather than swallowing it: a silent no-op is how a host
140
+ * ends up believing it is measuring something.
141
+ */
142
+ function push(...args) {
143
+ const fbq = fbqWindow()?.fbq;
144
+ if (!fbq) throw new Error("meta: window.fbq is not defined. Add Meta's pixel to the page before creating tracking.");
145
+ fbq(...args);
146
+ }
147
+ /**
148
+ * GA4's `items` become Meta's `contents`, which is a closed shape: `id`,
149
+ * `quantity`, `item_price` and `delivery_category`, and nothing else.
150
+ *
151
+ * The rest of a GA4 item, `item_name` and `item_brand`, is dropped rather than
152
+ * forwarded, because a key Meta does not read inside `contents` looks mapped
153
+ * and arrives nowhere. That is the failure mode this project keeps finding.
154
+ */
155
+ function contents(items) {
156
+ if (items.some((item) => typeof item.item_id !== "string" || !item.item_id.trim())) return void 0;
157
+ return items.map((item) => {
158
+ const content = {};
159
+ if (item.item_id !== void 0) content.id = item.item_id;
160
+ if (item.quantity !== void 0) content.quantity = item.quantity;
161
+ if (item.price !== void 0) content.item_price = item.price;
162
+ return content;
163
+ });
164
+ }
165
+ /**
166
+ * `value` and `currency` are spelled identically by both vendors, so they
167
+ * pass through untouched. Everything the host named itself passes through
168
+ * too: Meta keeps unknown keys as custom properties, which is exactly what a
169
+ * hand-written call would have produced.
170
+ *
171
+ * `transaction_id` stays under its own name here. Meta's pixel documents no
172
+ * order id among its object properties. `order_id` exists on the Conversions
173
+ * API and not on this surface, so translating it would be inventing a slot.
174
+ */
175
+ function pixelParams(data) {
176
+ const { items, ...rest } = data;
177
+ const params = { ...rest };
178
+ if (items) {
179
+ const mapped = contents(items);
180
+ if (mapped !== void 0) params.contents = mapped;
181
+ else delete params.contents;
182
+ }
183
+ return params;
184
+ }
185
+ /**
186
+ * Meta's pixel has one consent switch where this library carries Google's
187
+ * four signals, so the four are collapsed into it.
188
+ *
189
+ * The collapse is the strictest declared answer: any `denied` produces
190
+ * `revoke`, and `grant` needs every declared signal to be `granted`. There is
191
+ * no field to partition, so this is the only reduction that cannot widen a
192
+ * denial, and widening is the one thing a projection must never do.
193
+ *
194
+ * A host that wants Meta to follow only its advertising category, and not its
195
+ * analytics one, decides that before `createTracking` by leaving this
196
+ * provider out, which is what `@tracklane/consent` exists to do.
197
+ */
198
+ function collapse(state) {
199
+ const signals = [
200
+ state.ad_storage,
201
+ state.analytics_storage,
202
+ state.ad_user_data,
203
+ state.ad_personalization
204
+ ].filter((signal) => signal !== void 0);
205
+ if (signals.length === 0) return void 0;
206
+ return signals.every((signal) => signal === "granted") ? "grant" : "revoke";
207
+ }
208
+ /**
209
+ * The Meta pixel in the browser, through `fbq`.
210
+ *
211
+ * **This provider does not load Meta's pixel.** The base code belongs on the
212
+ * page, installed the way
213
+ * [Meta documents it](https://developers.facebook.com/docs/meta-pixel/get-started):
214
+ * a snippet in the HTML, or a tag manager. This library talks to the pixel
215
+ * that is already there; it never injects a third-party script and never
216
+ * issues `fbq('init')`.
217
+ *
218
+ * Three consequences follow from that, and all three are Meta's rules rather
219
+ * than ours:
220
+ *
221
+ * - **There is no `identify` here.** Meta accepts advanced matching only in
222
+ * the base code ("be sure to place advanced matching parameters in the
223
+ * pixel base code or the values will not be treated as manual advanced
224
+ * matching values"), and the base code is yours. Put `em`, `ph`, `fn`, `ln`
225
+ * in your own `fbq('init')`, where the pixel normalises and hashes them for
226
+ * you. On the server, where there is no snippet, the same identity travels
227
+ * in `context.user` and is hashed by the adapter.
228
+ * - **The initial consent declaration is not ours.** Meta wants
229
+ * `fbq('consent', 'revoke')` before `init`, and by the time you hold what
230
+ * `createTracking` returns that moment has passed. Ours is every
231
+ * declaration after it, through `consent()`.
232
+ * - **Limited Data Use is not ours either.** `fbq('dataProcessingOptions', …)`
233
+ * is documented as running before `init`. It belongs in your snippet. The
234
+ * server half, which has no snippet, carries it on its factory.
235
+ *
236
+ * **There is no pixel id here**, and that is deliberate rather than an
237
+ * omission. `fbq('track')` reaches every pixel the page initialised, so an id
238
+ * passed in would address nothing and the parameter would promise something
239
+ * this adapter cannot deliver. Which pixels exist is declared by your snippet,
240
+ * in `fbq('init')`.
241
+ *
242
+ * If any item lacks a nonempty `item_id`, the complete `contents` list is
243
+ * omitted while totals and order metadata remain unchanged. This avoids
244
+ * presenting a partial order or inventing product identifiers. The error
245
+ * channel receives one payload-free warning after the browser command returns.
246
+ *
247
+ * @param config - Event name overrides, merged over the built-in map.
248
+ * @returns A provider for `createTracking` from `tracklane/browser`.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * import { createTracking, meta } from 'tracklane/browser';
253
+ *
254
+ * // Meta's own base code is already in the page, and it named the pixel.
255
+ * const { track } = createTracking({ providers: [meta()] });
256
+ *
257
+ * // fbq('track', 'Purchase', { value: 49.9, currency: 'BRL' }, { eventID: 'T-1' })
258
+ * track('purchase', { value: 49.9, currency: 'BRL' }, { dedupId: 'T-1' });
259
+ * ```
260
+ */
261
+ function meta(config = {}) {
262
+ return {
263
+ name: "meta",
264
+ default: "passthrough",
265
+ events: {
266
+ ...CANONICAL_EVENTS,
267
+ ...config.events
268
+ },
269
+ track(name, data, options, report) {
270
+ const command = STANDARD_EVENTS.includes(name) ? "track" : "trackCustom";
271
+ const params = pixelParams(data);
272
+ const args = [
273
+ command,
274
+ name,
275
+ params
276
+ ];
277
+ if (options.dedupId !== void 0) args.push({ eventID: options.dedupId });
278
+ push(...args);
279
+ if (data.items && params.contents === void 0) report?.("contents omitted: every item needs a nonempty item_id; totals were preserved");
280
+ },
281
+ consent(_command, state) {
282
+ const answer = collapse(state);
283
+ if (answer) push("consent", answer);
284
+ }
285
+ };
286
+ }
287
+ //#endregion
288
+ //#region src/providers/posthog.browser.ts
289
+ function posthogWindow() {
290
+ return typeof window === "undefined" ? void 0 : window;
291
+ }
292
+ /**
293
+ * Returns the vendor SDK, or fails the way a hand-written call would.
294
+ *
295
+ * Calling `posthog.capture(...)` on a page without PostHog's snippet throws,
296
+ * and this library replicates that rather than swallowing it: a silent no-op
297
+ * is how a host ends up believing it is measuring something.
298
+ */
299
+ function sdk() {
300
+ const instance = posthogWindow()?.posthog;
301
+ if (!instance) throw new Error("posthog: window.posthog is not defined. Add PostHog's snippet to the page before creating tracking.");
302
+ return instance;
303
+ }
304
+ /**
305
+ * PostHog in the browser, through `posthog-js`.
306
+ *
307
+ * **This provider does not load PostHog's snippet.** It belongs on the page,
308
+ * installed the way PostHog documents it: a snippet in the HTML, or a tag
309
+ * manager. This library talks to the instance that is already there; it
310
+ * never injects a third-party script into your page.
311
+ *
312
+ * @param config - Rarely needed event name overrides.
313
+ * @returns A provider for `createTracking` from `tracklane/browser`.
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * import { createTracking, posthog } from 'tracklane/browser';
318
+ *
319
+ * // PostHog's own snippet is already in the page.
320
+ * const { track } = createTracking({ providers: [posthog()] });
321
+ *
322
+ * track('purchase', { transaction_id: 'T-1', value: 49.9, currency: 'BRL' });
323
+ * ```
324
+ */
325
+ function posthog(config = {}) {
326
+ const { events } = config;
327
+ return {
328
+ name: "posthog",
329
+ default: "passthrough",
330
+ ...events ? { events } : {},
331
+ track(name, data, _options) {
332
+ sdk().capture(name, data);
333
+ },
334
+ identify(user, traits) {
335
+ if (user.userId !== void 0) sdk().identify(user.userId);
336
+ if (traits) sdk().setPersonProperties(traits);
337
+ },
338
+ consent(_command, state) {
339
+ const { analytics_storage } = state;
340
+ if (analytics_storage === "denied") sdk().opt_out_capturing();
341
+ else if (analytics_storage === "granted") sdk().opt_in_capturing();
342
+ }
343
+ };
344
+ }
345
+ //#endregion
346
+ //#region src/browser.ts
347
+ /**
348
+ * Creates the browser tracking surface.
349
+ *
350
+ * Providers install eagerly, which is what pasting a vendor snippet does and
351
+ * what lets each vendor record its own initial page view. Every vendor
352
+ * shipped here queues its own commands until its script lands, so nothing is
353
+ * lost to loading and this library needs no queue of its own.
354
+ *
355
+ * @param options - The providers to send to, and an optional error channel.
356
+ * @returns The `track`, `identify` and `consent` calls.
357
+ *
358
+ * @example
359
+ * ```ts
360
+ * import { createTracking, ga4 } from 'tracklane/browser';
361
+ *
362
+ * const { track } = createTracking({ providers: [ga4('G-XXXXXXX')] });
363
+ * track('purchase', { transaction_id: 'T-1', value: 49.9, currency: 'BRL' });
364
+ * ```
365
+ */
366
+ function createTracking(options) {
367
+ const { providers } = options;
368
+ assertUniqueProviders(providers);
369
+ const report = makeReporter(options.onError);
370
+ const fail = (provider, event, cause) => {
371
+ report({
372
+ provider,
373
+ event,
374
+ severity: "error",
375
+ message: messageOf(cause),
376
+ cause
377
+ });
378
+ };
379
+ for (const provider of providers) try {
380
+ const installing = provider.install?.();
381
+ Promise.resolve(installing).catch((cause) => {
382
+ fail(provider.name, "install", cause);
383
+ });
384
+ } catch (cause) {
385
+ fail(provider.name, "install", cause);
386
+ }
387
+ return {
388
+ track(name, data = {}, trackOptions = {}) {
389
+ for (const provider of providers) {
390
+ const target = resolveEvent(provider, name, (message) => {
391
+ report({
392
+ provider: provider.name,
393
+ event: name,
394
+ severity: "warning",
395
+ message
396
+ });
397
+ });
398
+ if (target === void 0) continue;
399
+ try {
400
+ provider.track(target, data, trackOptions, (message) => {
401
+ report({
402
+ provider: provider.name,
403
+ event: name,
404
+ severity: "warning",
405
+ message
406
+ });
407
+ });
408
+ } catch (cause) {
409
+ fail(provider.name, name, cause);
410
+ }
411
+ }
412
+ },
413
+ identify(user, traits) {
414
+ for (const provider of providers) try {
415
+ provider.identify?.(user, traits);
416
+ } catch (cause) {
417
+ fail(provider.name, "identify", cause);
418
+ }
419
+ },
420
+ consent(command, state) {
421
+ for (const provider of providers) try {
422
+ provider.consent?.(command, state);
423
+ } catch (cause) {
424
+ fail(provider.name, "consent", cause);
425
+ }
426
+ }
427
+ };
428
+ }
429
+ //#endregion
430
+ export { ga4 as a, linkedin as i, posthog as n, meta as r, createTracking as t };
431
+
432
+ //# sourceMappingURL=browser-DhzO5qPS.js.map