mytart 0.4.0 → 0.5.1
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 +80 -2
- package/dist/index.d.mts +151 -3
- package/dist/index.d.ts +151 -3
- package/dist/index.js +332 -6
- package/dist/index.mjs +331 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- 🔌 **
|
|
9
|
+
- 🔌 **7 providers out of the box**: Google Analytics 4, Mixpanel, Segment, Amplitude, Plausible, PostHog, Meta Pixel
|
|
10
10
|
- 🌐 **Universal**: works in Node.js, browsers, and any JS framework (Next.js, Remix, Astro, SvelteKit, etc.)
|
|
11
11
|
- 🔷 **TypeScript-first**: precise typings, object-parameter style for great DX
|
|
12
12
|
- 📦 **Dual ESM/CJS output**: works with `import` and `require`
|
|
@@ -176,6 +176,83 @@ Consent Mode is a no-op in server mode (the Measurement Protocol does not suppor
|
|
|
176
176
|
{ provider: 'posthog', apiKey: 'phc_YOUR_KEY', apiUrl?: string }
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
### Meta Pixel
|
|
180
|
+
|
|
181
|
+
Meta Pixel supports two modes via the `appType` option:
|
|
182
|
+
|
|
183
|
+
#### Server mode (default)
|
|
184
|
+
|
|
185
|
+
Uses the [Meta Conversions API](https://developers.facebook.com/docs/marketing-api/conversions-api) — direct HTTP calls, no browser APIs. Use this for Node.js, API routes, serverless functions, etc.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
{
|
|
189
|
+
provider: 'meta-pixel',
|
|
190
|
+
pixelId: '123456789',
|
|
191
|
+
accessToken: 'YOUR_ACCESS_TOKEN',
|
|
192
|
+
enabled: true,
|
|
193
|
+
// appType defaults to 'server'
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
PII fields in `user_data` (`em`, `ph`, `fn`, `ln`, `ge`, `db`, `ct`, `st`, `zp`, `country`) are automatically SHA-256 hashed before being sent to the Conversions API. Already-hashed values are not double-hashed.
|
|
198
|
+
|
|
199
|
+
#### Browser mode
|
|
200
|
+
|
|
201
|
+
Injects Meta's official [fbevents.js snippet](https://developers.facebook.com/docs/meta-pixel/get-started) into the page. Use this for client-side tracking in any framework.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
{
|
|
205
|
+
provider: 'meta-pixel',
|
|
206
|
+
pixelId: '123456789',
|
|
207
|
+
appType: 'browser',
|
|
208
|
+
advancedMatching: { em: 'user@example.com' },
|
|
209
|
+
enabled: true,
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
When `appType: 'browser'` is set:
|
|
214
|
+
|
|
215
|
+
- The fbevents.js script is loaded once on the first `track()`, `identify()`, or `page()` call
|
|
216
|
+
- Standard Meta events (e.g. `Purchase`, `AddToCart`, `ViewContent`) use `fbq('track', ...)` — custom events use `fbq('trackCustom', ...)`
|
|
217
|
+
- SSR-safe: silently succeeds when `window` is undefined
|
|
218
|
+
- `accessToken` is not required
|
|
219
|
+
|
|
220
|
+
#### Event deduplication
|
|
221
|
+
|
|
222
|
+
Pass `eventId` via context to deduplicate browser + server events:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
await analytics.track({
|
|
226
|
+
event: 'Purchase',
|
|
227
|
+
properties: { currency: 'USD', value: 42 },
|
|
228
|
+
context: { eventId: 'order-abc-123' },
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
In browser mode this passes `{ eventID: 'order-abc-123' }` as the 4th `fbq()` parameter. In server mode it sets the `event_id` field in the Conversions API payload.
|
|
233
|
+
|
|
234
|
+
#### Identify
|
|
235
|
+
|
|
236
|
+
In browser mode, `identify()` re-calls `fbq('init', pixelId, newTraits)` to update Advanced Matching data. In server mode, traits are cached in memory and included as `user_data` in all subsequent `track()` / `page()` calls.
|
|
237
|
+
|
|
238
|
+
#### Consent
|
|
239
|
+
|
|
240
|
+
Meta Pixel uses a simple binary consent model. Access it directly on the provider instance:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { MetaPixelProvider } from 'mytart';
|
|
244
|
+
|
|
245
|
+
// Grant consent
|
|
246
|
+
const metaProvider = analytics.getProviders().includes('meta-pixel')
|
|
247
|
+
? (analytics as any) // or access the provider directly
|
|
248
|
+
: null;
|
|
249
|
+
|
|
250
|
+
// Or use the provider directly:
|
|
251
|
+
const provider = new MetaPixelProvider({ provider: 'meta-pixel', pixelId: '123', appType: 'browser' });
|
|
252
|
+
await provider.updatePixelConsent(true); // fbq('consent', 'grant')
|
|
253
|
+
await provider.updatePixelConsent(false); // fbq('consent', 'revoke')
|
|
254
|
+
```
|
|
255
|
+
|
|
179
256
|
## API Reference
|
|
180
257
|
|
|
181
258
|
### `new Mytart(config: MytartConfig)`
|
|
@@ -295,7 +372,8 @@ import type {
|
|
|
295
372
|
MytartConfig, BaseProviderConfig, ProviderConfig, TrackOptions, IdentifyOptions, PageOptions,
|
|
296
373
|
TrackResult, MytartError, EventContext, ProviderName, GoogleAnalyticsAppType,
|
|
297
374
|
GoogleAnalyticsConfig, ConsentSettings, ConsentState, MixpanelConfig, SegmentConfig,
|
|
298
|
-
AmplitudeConfig, PlausibleConfig, PostHogConfig,
|
|
375
|
+
AmplitudeConfig, PlausibleConfig, PostHogConfig, MetaPixelConfig, MetaPixelAppType,
|
|
376
|
+
MetaPixelAdvancedMatching,
|
|
299
377
|
} from 'mytart';
|
|
300
378
|
```
|
|
301
379
|
|
package/dist/index.d.mts
CHANGED
|
@@ -35,7 +35,7 @@ interface ConsentSettings {
|
|
|
35
35
|
/** Controls storage for security purposes (e.g. authentication). */
|
|
36
36
|
security_storage?: ConsentState;
|
|
37
37
|
}
|
|
38
|
-
type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog';
|
|
38
|
+
type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog' | 'meta-pixel';
|
|
39
39
|
type GoogleAnalyticsAppType = 'browser' | 'server';
|
|
40
40
|
interface GoogleAnalyticsConfig extends BaseProviderConfig {
|
|
41
41
|
provider: 'google-analytics';
|
|
@@ -110,7 +110,77 @@ interface PostHogConfig extends BaseProviderConfig {
|
|
|
110
110
|
apiKey: string;
|
|
111
111
|
apiUrl?: string;
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Advanced Matching data for the Meta Pixel.
|
|
115
|
+
* In browser mode these fields are passed to `fbq('init', pixelId, matchData)`.
|
|
116
|
+
* In server mode they are included in the `user_data` object sent to the
|
|
117
|
+
* Conversions API — plain-text values are automatically SHA-256 hashed before
|
|
118
|
+
* sending.
|
|
119
|
+
*/
|
|
120
|
+
interface MetaPixelAdvancedMatching {
|
|
121
|
+
/** Email address */
|
|
122
|
+
em?: string;
|
|
123
|
+
/** Phone number */
|
|
124
|
+
ph?: string;
|
|
125
|
+
/** First name */
|
|
126
|
+
fn?: string;
|
|
127
|
+
/** Last name */
|
|
128
|
+
ln?: string;
|
|
129
|
+
/** Gender (m or f) */
|
|
130
|
+
ge?: string;
|
|
131
|
+
/** Date of birth (YYYYMMDD) */
|
|
132
|
+
db?: string;
|
|
133
|
+
/** City */
|
|
134
|
+
ct?: string;
|
|
135
|
+
/** State / province (2-letter code) */
|
|
136
|
+
st?: string;
|
|
137
|
+
/** Zip / postal code */
|
|
138
|
+
zp?: string;
|
|
139
|
+
/** Country (2-letter ISO code) */
|
|
140
|
+
country?: string;
|
|
141
|
+
/** External ID */
|
|
142
|
+
external_id?: string;
|
|
143
|
+
}
|
|
144
|
+
type MetaPixelAppType = 'browser' | 'server';
|
|
145
|
+
interface MetaPixelConfig extends BaseProviderConfig {
|
|
146
|
+
provider: 'meta-pixel';
|
|
147
|
+
/** The Meta Pixel ID (numeric string). */
|
|
148
|
+
pixelId: string;
|
|
149
|
+
/**
|
|
150
|
+
* Access token for the Conversions API. Required when `appType` is
|
|
151
|
+
* `'server'` — ignored in browser mode.
|
|
152
|
+
*/
|
|
153
|
+
accessToken?: string;
|
|
154
|
+
/**
|
|
155
|
+
* `'browser'` injects the official fbevents.js snippet and calls `window.fbq()`.
|
|
156
|
+
* `'server'` (default) sends events via the Conversions API HTTP endpoint.
|
|
157
|
+
*/
|
|
158
|
+
appType?: MetaPixelAppType;
|
|
159
|
+
/**
|
|
160
|
+
* Graph API version to use for the Conversions API.
|
|
161
|
+
* Defaults to `'v21.0'`.
|
|
162
|
+
*/
|
|
163
|
+
apiVersion?: string;
|
|
164
|
+
/**
|
|
165
|
+
* Test Event Code for the Events Manager Test Events tool.
|
|
166
|
+
* Only used in server mode (Conversions API).
|
|
167
|
+
*/
|
|
168
|
+
testEventCode?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Advanced Matching data passed to `fbq('init')` in browser mode.
|
|
171
|
+
* In server mode this populates the initial `user_data` for all events.
|
|
172
|
+
*/
|
|
173
|
+
advancedMatching?: MetaPixelAdvancedMatching;
|
|
174
|
+
/**
|
|
175
|
+
* When `true` (default), the Pixel uses Automatic Configuration
|
|
176
|
+
* (auto-detects button clicks, page metadata, etc.).
|
|
177
|
+
* Set to `false` to disable.
|
|
178
|
+
*/
|
|
179
|
+
autoConfig?: boolean;
|
|
180
|
+
/** Enable Meta Pixel debug mode (`fbq('set', 'debug', true)`). */
|
|
181
|
+
debug?: boolean;
|
|
182
|
+
}
|
|
183
|
+
type ProviderConfig = GoogleAnalyticsConfig | MixpanelConfig | SegmentConfig | AmplitudeConfig | PlausibleConfig | PostHogConfig | MetaPixelConfig;
|
|
114
184
|
interface MytartConfig {
|
|
115
185
|
providers: ProviderConfig[];
|
|
116
186
|
defaultUserId?: string;
|
|
@@ -322,4 +392,82 @@ declare class PostHogProvider extends BaseProvider {
|
|
|
322
392
|
page({ name, url, userId, anonymousId, properties }: PageOptions): Promise<TrackResult>;
|
|
323
393
|
}
|
|
324
394
|
|
|
325
|
-
|
|
395
|
+
declare global {
|
|
396
|
+
interface Window {
|
|
397
|
+
fbq: FbqFn & {
|
|
398
|
+
callMethod?: FbqFn;
|
|
399
|
+
queue?: unknown[];
|
|
400
|
+
loaded?: boolean;
|
|
401
|
+
version?: string;
|
|
402
|
+
push?: FbqFn;
|
|
403
|
+
};
|
|
404
|
+
_fbq: Window['fbq'];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
type FbqFn = (...args: unknown[]) => void;
|
|
408
|
+
declare class MetaPixelProvider extends BaseProvider {
|
|
409
|
+
readonly name = "meta-pixel";
|
|
410
|
+
private readonly config;
|
|
411
|
+
private readonly http;
|
|
412
|
+
private readonly isBrowser;
|
|
413
|
+
private readonly apiVersion;
|
|
414
|
+
private fbqReady;
|
|
415
|
+
/**
|
|
416
|
+
* Cached user data for the Conversions API. Updated by `identify()` so
|
|
417
|
+
* that subsequent `track()` and `page()` calls include user information.
|
|
418
|
+
*/
|
|
419
|
+
private cachedUserData;
|
|
420
|
+
constructor(config: MetaPixelConfig);
|
|
421
|
+
/**
|
|
422
|
+
* Initialises the Meta Pixel snippet. This mirrors the official snippet from
|
|
423
|
+
* https://developers.facebook.com/docs/meta-pixel/get-started:
|
|
424
|
+
*
|
|
425
|
+
* !function(f,b,e,v,n,t,s){...}(window, document,'script',
|
|
426
|
+
* 'https://connect.facebook.net/en_US/fbevents.js');
|
|
427
|
+
* fbq('init', 'PIXEL_ID');
|
|
428
|
+
* fbq('track', 'PageView');
|
|
429
|
+
*
|
|
430
|
+
* Key details:
|
|
431
|
+
* - The fbq shim queue is set up synchronously before the script loads
|
|
432
|
+
* - `fbq('init', pixelId, advancedMatching?)` is called immediately
|
|
433
|
+
* - autoConfig is respected via `fbq('set', 'autoConfig', false, pixelId)`
|
|
434
|
+
* - debug mode via `fbq('set', 'debug', true)`
|
|
435
|
+
* - The returned promise resolves when the script finishes loading
|
|
436
|
+
*/
|
|
437
|
+
private initFbq;
|
|
438
|
+
/**
|
|
439
|
+
* Ensures the Pixel is initialised exactly once. Subsequent calls return
|
|
440
|
+
* the same promise so the script is never injected twice.
|
|
441
|
+
*/
|
|
442
|
+
private ensureFbq;
|
|
443
|
+
private trackBrowser;
|
|
444
|
+
private identifyBrowser;
|
|
445
|
+
private pageBrowser;
|
|
446
|
+
private buildFbqResult;
|
|
447
|
+
private get capiEndpoint();
|
|
448
|
+
/**
|
|
449
|
+
* Build the `user_data` object for a CAPI event.
|
|
450
|
+
* Merges cached user data (from `identify()` / config) with any per-event
|
|
451
|
+
* overrides, then SHA-256 hashes known PII fields.
|
|
452
|
+
*/
|
|
453
|
+
private buildUserData;
|
|
454
|
+
private trackServer;
|
|
455
|
+
private identifyServer;
|
|
456
|
+
private pageServer;
|
|
457
|
+
/**
|
|
458
|
+
* Meta Pixel consent is a binary grant/revoke model, not the granular
|
|
459
|
+
* settings of Google Consent Mode v2. This method accepts the standard
|
|
460
|
+
* `ConsentSettings` type but is a no-op — use `updatePixelConsent()` for
|
|
461
|
+
* Meta-specific consent control in browser mode.
|
|
462
|
+
*/
|
|
463
|
+
/**
|
|
464
|
+
* Grant or revoke consent for the Meta Pixel in browser mode.
|
|
465
|
+
* Calls `fbq('consent', 'grant')` or `fbq('consent', 'revoke')`.
|
|
466
|
+
*/
|
|
467
|
+
updatePixelConsent(granted: boolean): Promise<void>;
|
|
468
|
+
track(options: TrackOptions): Promise<TrackResult>;
|
|
469
|
+
identify(options: IdentifyOptions): Promise<TrackResult>;
|
|
470
|
+
page(options: PageOptions): Promise<TrackResult>;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MetaPixelAdvancedMatching, type MetaPixelAppType, type MetaPixelConfig, MetaPixelProvider, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ interface ConsentSettings {
|
|
|
35
35
|
/** Controls storage for security purposes (e.g. authentication). */
|
|
36
36
|
security_storage?: ConsentState;
|
|
37
37
|
}
|
|
38
|
-
type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog';
|
|
38
|
+
type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog' | 'meta-pixel';
|
|
39
39
|
type GoogleAnalyticsAppType = 'browser' | 'server';
|
|
40
40
|
interface GoogleAnalyticsConfig extends BaseProviderConfig {
|
|
41
41
|
provider: 'google-analytics';
|
|
@@ -110,7 +110,77 @@ interface PostHogConfig extends BaseProviderConfig {
|
|
|
110
110
|
apiKey: string;
|
|
111
111
|
apiUrl?: string;
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Advanced Matching data for the Meta Pixel.
|
|
115
|
+
* In browser mode these fields are passed to `fbq('init', pixelId, matchData)`.
|
|
116
|
+
* In server mode they are included in the `user_data` object sent to the
|
|
117
|
+
* Conversions API — plain-text values are automatically SHA-256 hashed before
|
|
118
|
+
* sending.
|
|
119
|
+
*/
|
|
120
|
+
interface MetaPixelAdvancedMatching {
|
|
121
|
+
/** Email address */
|
|
122
|
+
em?: string;
|
|
123
|
+
/** Phone number */
|
|
124
|
+
ph?: string;
|
|
125
|
+
/** First name */
|
|
126
|
+
fn?: string;
|
|
127
|
+
/** Last name */
|
|
128
|
+
ln?: string;
|
|
129
|
+
/** Gender (m or f) */
|
|
130
|
+
ge?: string;
|
|
131
|
+
/** Date of birth (YYYYMMDD) */
|
|
132
|
+
db?: string;
|
|
133
|
+
/** City */
|
|
134
|
+
ct?: string;
|
|
135
|
+
/** State / province (2-letter code) */
|
|
136
|
+
st?: string;
|
|
137
|
+
/** Zip / postal code */
|
|
138
|
+
zp?: string;
|
|
139
|
+
/** Country (2-letter ISO code) */
|
|
140
|
+
country?: string;
|
|
141
|
+
/** External ID */
|
|
142
|
+
external_id?: string;
|
|
143
|
+
}
|
|
144
|
+
type MetaPixelAppType = 'browser' | 'server';
|
|
145
|
+
interface MetaPixelConfig extends BaseProviderConfig {
|
|
146
|
+
provider: 'meta-pixel';
|
|
147
|
+
/** The Meta Pixel ID (numeric string). */
|
|
148
|
+
pixelId: string;
|
|
149
|
+
/**
|
|
150
|
+
* Access token for the Conversions API. Required when `appType` is
|
|
151
|
+
* `'server'` — ignored in browser mode.
|
|
152
|
+
*/
|
|
153
|
+
accessToken?: string;
|
|
154
|
+
/**
|
|
155
|
+
* `'browser'` injects the official fbevents.js snippet and calls `window.fbq()`.
|
|
156
|
+
* `'server'` (default) sends events via the Conversions API HTTP endpoint.
|
|
157
|
+
*/
|
|
158
|
+
appType?: MetaPixelAppType;
|
|
159
|
+
/**
|
|
160
|
+
* Graph API version to use for the Conversions API.
|
|
161
|
+
* Defaults to `'v21.0'`.
|
|
162
|
+
*/
|
|
163
|
+
apiVersion?: string;
|
|
164
|
+
/**
|
|
165
|
+
* Test Event Code for the Events Manager Test Events tool.
|
|
166
|
+
* Only used in server mode (Conversions API).
|
|
167
|
+
*/
|
|
168
|
+
testEventCode?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Advanced Matching data passed to `fbq('init')` in browser mode.
|
|
171
|
+
* In server mode this populates the initial `user_data` for all events.
|
|
172
|
+
*/
|
|
173
|
+
advancedMatching?: MetaPixelAdvancedMatching;
|
|
174
|
+
/**
|
|
175
|
+
* When `true` (default), the Pixel uses Automatic Configuration
|
|
176
|
+
* (auto-detects button clicks, page metadata, etc.).
|
|
177
|
+
* Set to `false` to disable.
|
|
178
|
+
*/
|
|
179
|
+
autoConfig?: boolean;
|
|
180
|
+
/** Enable Meta Pixel debug mode (`fbq('set', 'debug', true)`). */
|
|
181
|
+
debug?: boolean;
|
|
182
|
+
}
|
|
183
|
+
type ProviderConfig = GoogleAnalyticsConfig | MixpanelConfig | SegmentConfig | AmplitudeConfig | PlausibleConfig | PostHogConfig | MetaPixelConfig;
|
|
114
184
|
interface MytartConfig {
|
|
115
185
|
providers: ProviderConfig[];
|
|
116
186
|
defaultUserId?: string;
|
|
@@ -322,4 +392,82 @@ declare class PostHogProvider extends BaseProvider {
|
|
|
322
392
|
page({ name, url, userId, anonymousId, properties }: PageOptions): Promise<TrackResult>;
|
|
323
393
|
}
|
|
324
394
|
|
|
325
|
-
|
|
395
|
+
declare global {
|
|
396
|
+
interface Window {
|
|
397
|
+
fbq: FbqFn & {
|
|
398
|
+
callMethod?: FbqFn;
|
|
399
|
+
queue?: unknown[];
|
|
400
|
+
loaded?: boolean;
|
|
401
|
+
version?: string;
|
|
402
|
+
push?: FbqFn;
|
|
403
|
+
};
|
|
404
|
+
_fbq: Window['fbq'];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
type FbqFn = (...args: unknown[]) => void;
|
|
408
|
+
declare class MetaPixelProvider extends BaseProvider {
|
|
409
|
+
readonly name = "meta-pixel";
|
|
410
|
+
private readonly config;
|
|
411
|
+
private readonly http;
|
|
412
|
+
private readonly isBrowser;
|
|
413
|
+
private readonly apiVersion;
|
|
414
|
+
private fbqReady;
|
|
415
|
+
/**
|
|
416
|
+
* Cached user data for the Conversions API. Updated by `identify()` so
|
|
417
|
+
* that subsequent `track()` and `page()` calls include user information.
|
|
418
|
+
*/
|
|
419
|
+
private cachedUserData;
|
|
420
|
+
constructor(config: MetaPixelConfig);
|
|
421
|
+
/**
|
|
422
|
+
* Initialises the Meta Pixel snippet. This mirrors the official snippet from
|
|
423
|
+
* https://developers.facebook.com/docs/meta-pixel/get-started:
|
|
424
|
+
*
|
|
425
|
+
* !function(f,b,e,v,n,t,s){...}(window, document,'script',
|
|
426
|
+
* 'https://connect.facebook.net/en_US/fbevents.js');
|
|
427
|
+
* fbq('init', 'PIXEL_ID');
|
|
428
|
+
* fbq('track', 'PageView');
|
|
429
|
+
*
|
|
430
|
+
* Key details:
|
|
431
|
+
* - The fbq shim queue is set up synchronously before the script loads
|
|
432
|
+
* - `fbq('init', pixelId, advancedMatching?)` is called immediately
|
|
433
|
+
* - autoConfig is respected via `fbq('set', 'autoConfig', false, pixelId)`
|
|
434
|
+
* - debug mode via `fbq('set', 'debug', true)`
|
|
435
|
+
* - The returned promise resolves when the script finishes loading
|
|
436
|
+
*/
|
|
437
|
+
private initFbq;
|
|
438
|
+
/**
|
|
439
|
+
* Ensures the Pixel is initialised exactly once. Subsequent calls return
|
|
440
|
+
* the same promise so the script is never injected twice.
|
|
441
|
+
*/
|
|
442
|
+
private ensureFbq;
|
|
443
|
+
private trackBrowser;
|
|
444
|
+
private identifyBrowser;
|
|
445
|
+
private pageBrowser;
|
|
446
|
+
private buildFbqResult;
|
|
447
|
+
private get capiEndpoint();
|
|
448
|
+
/**
|
|
449
|
+
* Build the `user_data` object for a CAPI event.
|
|
450
|
+
* Merges cached user data (from `identify()` / config) with any per-event
|
|
451
|
+
* overrides, then SHA-256 hashes known PII fields.
|
|
452
|
+
*/
|
|
453
|
+
private buildUserData;
|
|
454
|
+
private trackServer;
|
|
455
|
+
private identifyServer;
|
|
456
|
+
private pageServer;
|
|
457
|
+
/**
|
|
458
|
+
* Meta Pixel consent is a binary grant/revoke model, not the granular
|
|
459
|
+
* settings of Google Consent Mode v2. This method accepts the standard
|
|
460
|
+
* `ConsentSettings` type but is a no-op — use `updatePixelConsent()` for
|
|
461
|
+
* Meta-specific consent control in browser mode.
|
|
462
|
+
*/
|
|
463
|
+
/**
|
|
464
|
+
* Grant or revoke consent for the Meta Pixel in browser mode.
|
|
465
|
+
* Calls `fbq('consent', 'grant')` or `fbq('consent', 'revoke')`.
|
|
466
|
+
*/
|
|
467
|
+
updatePixelConsent(granted: boolean): Promise<void>;
|
|
468
|
+
track(options: TrackOptions): Promise<TrackResult>;
|
|
469
|
+
identify(options: IdentifyOptions): Promise<TrackResult>;
|
|
470
|
+
page(options: PageOptions): Promise<TrackResult>;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MetaPixelAdvancedMatching, type MetaPixelAppType, type MetaPixelConfig, MetaPixelProvider, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
AmplitudeProvider: () => AmplitudeProvider,
|
|
34
34
|
BaseProvider: () => BaseProvider,
|
|
35
35
|
GoogleAnalyticsProvider: () => GoogleAnalyticsProvider,
|
|
36
|
+
MetaPixelProvider: () => MetaPixelProvider,
|
|
36
37
|
MixpanelProvider: () => MixpanelProvider,
|
|
37
38
|
Mytart: () => Mytart,
|
|
38
39
|
PlausibleProvider: () => PlausibleProvider,
|
|
@@ -145,7 +146,9 @@ var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
|
145
146
|
window.gtag("consent", "default", consentParams);
|
|
146
147
|
}
|
|
147
148
|
window.gtag("js", /* @__PURE__ */ new Date());
|
|
148
|
-
const configParams = {
|
|
149
|
+
const configParams = {
|
|
150
|
+
send_page_view: false
|
|
151
|
+
};
|
|
149
152
|
if (this.config.signals === true) {
|
|
150
153
|
configParams["allow_google_signals"] = true;
|
|
151
154
|
configParams["allow_ad_personalization_signals"] = true;
|
|
@@ -153,11 +156,7 @@ var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
|
153
156
|
configParams["allow_google_signals"] = false;
|
|
154
157
|
configParams["allow_ad_personalization_signals"] = false;
|
|
155
158
|
}
|
|
156
|
-
|
|
157
|
-
window.gtag("config", this.config.measurementId, configParams);
|
|
158
|
-
} else {
|
|
159
|
-
window.gtag("config", this.config.measurementId);
|
|
160
|
-
}
|
|
159
|
+
window.gtag("config", this.config.measurementId, configParams);
|
|
161
160
|
return new Promise((resolve) => {
|
|
162
161
|
const script = document.createElement("script");
|
|
163
162
|
script.async = true;
|
|
@@ -760,6 +759,330 @@ var PostHogProvider = class extends BaseProvider {
|
|
|
760
759
|
}
|
|
761
760
|
};
|
|
762
761
|
|
|
762
|
+
// src/utils/hash.ts
|
|
763
|
+
var import_crypto = require("crypto");
|
|
764
|
+
function sha256(value) {
|
|
765
|
+
if (/^[a-f0-9]{64}$/.test(value)) {
|
|
766
|
+
return value;
|
|
767
|
+
}
|
|
768
|
+
return (0, import_crypto.createHash)("sha256").update(value).digest("hex");
|
|
769
|
+
}
|
|
770
|
+
var PII_FIELDS = /* @__PURE__ */ new Set([
|
|
771
|
+
"em",
|
|
772
|
+
"ph",
|
|
773
|
+
"fn",
|
|
774
|
+
"ln",
|
|
775
|
+
"ge",
|
|
776
|
+
"db",
|
|
777
|
+
"ct",
|
|
778
|
+
"st",
|
|
779
|
+
"zp",
|
|
780
|
+
"country"
|
|
781
|
+
]);
|
|
782
|
+
function hashUserData(userData) {
|
|
783
|
+
const result = {};
|
|
784
|
+
for (const [key, value] of Object.entries(userData)) {
|
|
785
|
+
if (PII_FIELDS.has(key) && typeof value === "string" && value.length > 0) {
|
|
786
|
+
result[key] = sha256(value);
|
|
787
|
+
} else {
|
|
788
|
+
result[key] = value;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/providers/meta-pixel.ts
|
|
795
|
+
var FBEVENTS_SCRIPT_URL = "https://connect.facebook.net/en_US/fbevents.js";
|
|
796
|
+
var DEFAULT_API_VERSION = "v21.0";
|
|
797
|
+
var STANDARD_EVENTS = /* @__PURE__ */ new Set([
|
|
798
|
+
"PageView",
|
|
799
|
+
"Purchase",
|
|
800
|
+
"AddToCart",
|
|
801
|
+
"ViewContent",
|
|
802
|
+
"InitiateCheckout",
|
|
803
|
+
"Search",
|
|
804
|
+
"Lead",
|
|
805
|
+
"CompleteRegistration",
|
|
806
|
+
"AddPaymentInfo",
|
|
807
|
+
"AddToWishlist",
|
|
808
|
+
"Contact",
|
|
809
|
+
"StartTrial",
|
|
810
|
+
"Subscribe",
|
|
811
|
+
"Donate",
|
|
812
|
+
"Schedule",
|
|
813
|
+
"SubmitApplication",
|
|
814
|
+
"CustomizeProduct",
|
|
815
|
+
"FindLocation"
|
|
816
|
+
]);
|
|
817
|
+
var MetaPixelProvider = class extends BaseProvider {
|
|
818
|
+
constructor(config) {
|
|
819
|
+
super();
|
|
820
|
+
this.name = "meta-pixel";
|
|
821
|
+
this.fbqReady = null;
|
|
822
|
+
/**
|
|
823
|
+
* Cached user data for the Conversions API. Updated by `identify()` so
|
|
824
|
+
* that subsequent `track()` and `page()` calls include user information.
|
|
825
|
+
*/
|
|
826
|
+
this.cachedUserData = {};
|
|
827
|
+
this.config = config;
|
|
828
|
+
this.http = createHttpClient();
|
|
829
|
+
this.isBrowser = config.appType === "browser";
|
|
830
|
+
this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
|
|
831
|
+
if (config.advancedMatching) {
|
|
832
|
+
this.cachedUserData = { ...config.advancedMatching };
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// ---------------------------------------------------------------------------
|
|
836
|
+
// Browser mode — fbevents.js injection & fbq() calls
|
|
837
|
+
// ---------------------------------------------------------------------------
|
|
838
|
+
/**
|
|
839
|
+
* Initialises the Meta Pixel snippet. This mirrors the official snippet from
|
|
840
|
+
* https://developers.facebook.com/docs/meta-pixel/get-started:
|
|
841
|
+
*
|
|
842
|
+
* !function(f,b,e,v,n,t,s){...}(window, document,'script',
|
|
843
|
+
* 'https://connect.facebook.net/en_US/fbevents.js');
|
|
844
|
+
* fbq('init', 'PIXEL_ID');
|
|
845
|
+
* fbq('track', 'PageView');
|
|
846
|
+
*
|
|
847
|
+
* Key details:
|
|
848
|
+
* - The fbq shim queue is set up synchronously before the script loads
|
|
849
|
+
* - `fbq('init', pixelId, advancedMatching?)` is called immediately
|
|
850
|
+
* - autoConfig is respected via `fbq('set', 'autoConfig', false, pixelId)`
|
|
851
|
+
* - debug mode via `fbq('set', 'debug', true)`
|
|
852
|
+
* - The returned promise resolves when the script finishes loading
|
|
853
|
+
*/
|
|
854
|
+
initFbq() {
|
|
855
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
856
|
+
return Promise.resolve();
|
|
857
|
+
}
|
|
858
|
+
if (!window.fbq) {
|
|
859
|
+
const queue = [];
|
|
860
|
+
const fbq = Object.assign(
|
|
861
|
+
function fbq2(...args) {
|
|
862
|
+
if (fbq2.callMethod) {
|
|
863
|
+
fbq2.callMethod(...args);
|
|
864
|
+
} else {
|
|
865
|
+
queue.push(args);
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
push: void 0,
|
|
870
|
+
loaded: true,
|
|
871
|
+
version: "2.0",
|
|
872
|
+
queue,
|
|
873
|
+
callMethod: void 0
|
|
874
|
+
}
|
|
875
|
+
);
|
|
876
|
+
fbq.push = fbq;
|
|
877
|
+
window.fbq = fbq;
|
|
878
|
+
window._fbq = fbq;
|
|
879
|
+
}
|
|
880
|
+
if (this.config.autoConfig === false) {
|
|
881
|
+
window.fbq("set", "autoConfig", false, this.config.pixelId);
|
|
882
|
+
}
|
|
883
|
+
if (this.config.debug) {
|
|
884
|
+
window.fbq("set", "debug", true);
|
|
885
|
+
}
|
|
886
|
+
const matchData = this.config.advancedMatching;
|
|
887
|
+
if (matchData && Object.keys(matchData).length > 0) {
|
|
888
|
+
window.fbq("init", this.config.pixelId, matchData);
|
|
889
|
+
} else {
|
|
890
|
+
window.fbq("init", this.config.pixelId);
|
|
891
|
+
}
|
|
892
|
+
return new Promise((resolve) => {
|
|
893
|
+
const script = document.createElement("script");
|
|
894
|
+
script.async = true;
|
|
895
|
+
script.src = FBEVENTS_SCRIPT_URL;
|
|
896
|
+
script.onload = () => resolve();
|
|
897
|
+
script.onerror = () => resolve();
|
|
898
|
+
document.head.appendChild(script);
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Ensures the Pixel is initialised exactly once. Subsequent calls return
|
|
903
|
+
* the same promise so the script is never injected twice.
|
|
904
|
+
*/
|
|
905
|
+
ensureFbq() {
|
|
906
|
+
if (!this.fbqReady) {
|
|
907
|
+
this.fbqReady = this.initFbq();
|
|
908
|
+
}
|
|
909
|
+
return this.fbqReady;
|
|
910
|
+
}
|
|
911
|
+
// -- browser track --
|
|
912
|
+
async trackBrowser(options) {
|
|
913
|
+
if (typeof window === "undefined") {
|
|
914
|
+
return this.buildFbqResult();
|
|
915
|
+
}
|
|
916
|
+
await this.ensureFbq();
|
|
917
|
+
const { event, properties, context } = options;
|
|
918
|
+
const eventId = context?.eventId;
|
|
919
|
+
const isStandard = STANDARD_EVENTS.has(event);
|
|
920
|
+
const command = isStandard ? "track" : "trackCustom";
|
|
921
|
+
if (eventId) {
|
|
922
|
+
window.fbq(command, event, properties ?? {}, { eventID: eventId });
|
|
923
|
+
} else {
|
|
924
|
+
window.fbq(command, event, properties ?? {});
|
|
925
|
+
}
|
|
926
|
+
return this.buildFbqResult();
|
|
927
|
+
}
|
|
928
|
+
// -- browser identify --
|
|
929
|
+
async identifyBrowser(options) {
|
|
930
|
+
if (typeof window === "undefined") {
|
|
931
|
+
return this.buildFbqResult();
|
|
932
|
+
}
|
|
933
|
+
await this.ensureFbq();
|
|
934
|
+
const matchData = {};
|
|
935
|
+
if (options.traits) {
|
|
936
|
+
Object.assign(matchData, options.traits);
|
|
937
|
+
}
|
|
938
|
+
matchData["external_id"] = options.userId;
|
|
939
|
+
window.fbq("init", this.config.pixelId, matchData);
|
|
940
|
+
return this.buildFbqResult();
|
|
941
|
+
}
|
|
942
|
+
// -- browser page --
|
|
943
|
+
async pageBrowser(options) {
|
|
944
|
+
if (typeof window === "undefined") {
|
|
945
|
+
return this.buildFbqResult();
|
|
946
|
+
}
|
|
947
|
+
await this.ensureFbq();
|
|
948
|
+
window.fbq("track", "PageView");
|
|
949
|
+
return this.buildFbqResult();
|
|
950
|
+
}
|
|
951
|
+
buildFbqResult() {
|
|
952
|
+
return {
|
|
953
|
+
provider: this.name,
|
|
954
|
+
success: true,
|
|
955
|
+
statusCode: 200
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
// ---------------------------------------------------------------------------
|
|
959
|
+
// Server mode — Meta Conversions API (CAPI)
|
|
960
|
+
// ---------------------------------------------------------------------------
|
|
961
|
+
get capiEndpoint() {
|
|
962
|
+
return `https://graph.facebook.com/${this.apiVersion}/${this.config.pixelId}/events`;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Build the `user_data` object for a CAPI event.
|
|
966
|
+
* Merges cached user data (from `identify()` / config) with any per-event
|
|
967
|
+
* overrides, then SHA-256 hashes known PII fields.
|
|
968
|
+
*/
|
|
969
|
+
buildUserData(overrides) {
|
|
970
|
+
const merged = { ...this.cachedUserData, ...overrides };
|
|
971
|
+
return hashUserData(merged);
|
|
972
|
+
}
|
|
973
|
+
// -- server track --
|
|
974
|
+
async trackServer(options) {
|
|
975
|
+
try {
|
|
976
|
+
const { event, properties, context, timestamp, userId } = options;
|
|
977
|
+
const eventId = context?.eventId;
|
|
978
|
+
const userDataOverrides = {};
|
|
979
|
+
if (userId) {
|
|
980
|
+
userDataOverrides["external_id"] = userId;
|
|
981
|
+
}
|
|
982
|
+
const eventData = {
|
|
983
|
+
event_name: event,
|
|
984
|
+
event_time: timestamp ? Math.floor(timestamp.getTime() / 1e3) : Math.floor(Date.now() / 1e3),
|
|
985
|
+
action_source: "website",
|
|
986
|
+
user_data: this.buildUserData(userDataOverrides)
|
|
987
|
+
};
|
|
988
|
+
if (eventId) {
|
|
989
|
+
eventData["event_id"] = eventId;
|
|
990
|
+
}
|
|
991
|
+
const pageUrl = context?.page?.url;
|
|
992
|
+
if (pageUrl) {
|
|
993
|
+
eventData["event_source_url"] = pageUrl;
|
|
994
|
+
}
|
|
995
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
996
|
+
eventData["custom_data"] = properties;
|
|
997
|
+
}
|
|
998
|
+
const body = {
|
|
999
|
+
data: [eventData]
|
|
1000
|
+
};
|
|
1001
|
+
if (this.config.testEventCode) {
|
|
1002
|
+
body["test_event_code"] = this.config.testEventCode;
|
|
1003
|
+
}
|
|
1004
|
+
const response = await this.http.post(this.capiEndpoint, body, {
|
|
1005
|
+
params: { access_token: this.config.accessToken }
|
|
1006
|
+
});
|
|
1007
|
+
return this.buildSuccess(response.status);
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (isAxiosError(error)) {
|
|
1010
|
+
return this.buildError(
|
|
1011
|
+
error.message,
|
|
1012
|
+
`META_PIXEL_HTTP_${error.response?.status ?? "ERROR"}`,
|
|
1013
|
+
error
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
return this.buildError(String(error), "META_PIXEL_UNKNOWN_ERROR", error);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
// -- server identify --
|
|
1020
|
+
async identifyServer(options) {
|
|
1021
|
+
if (options.traits) {
|
|
1022
|
+
Object.assign(this.cachedUserData, options.traits);
|
|
1023
|
+
}
|
|
1024
|
+
this.cachedUserData["external_id"] = options.userId;
|
|
1025
|
+
return this.buildSuccess(200);
|
|
1026
|
+
}
|
|
1027
|
+
// -- server page --
|
|
1028
|
+
async pageServer(options) {
|
|
1029
|
+
return this.trackServer({
|
|
1030
|
+
event: "PageView",
|
|
1031
|
+
properties: options.properties,
|
|
1032
|
+
userId: options.userId,
|
|
1033
|
+
anonymousId: options.anonymousId,
|
|
1034
|
+
timestamp: options.timestamp,
|
|
1035
|
+
context: {
|
|
1036
|
+
page: {
|
|
1037
|
+
url: options.url,
|
|
1038
|
+
referrer: options.referrer
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
// ---------------------------------------------------------------------------
|
|
1044
|
+
// Consent — Meta Pixel's simple grant / revoke model
|
|
1045
|
+
// ---------------------------------------------------------------------------
|
|
1046
|
+
/**
|
|
1047
|
+
* Meta Pixel consent is a binary grant/revoke model, not the granular
|
|
1048
|
+
* settings of Google Consent Mode v2. This method accepts the standard
|
|
1049
|
+
* `ConsentSettings` type but is a no-op — use `updatePixelConsent()` for
|
|
1050
|
+
* Meta-specific consent control in browser mode.
|
|
1051
|
+
*/
|
|
1052
|
+
/**
|
|
1053
|
+
* Grant or revoke consent for the Meta Pixel in browser mode.
|
|
1054
|
+
* Calls `fbq('consent', 'grant')` or `fbq('consent', 'revoke')`.
|
|
1055
|
+
*/
|
|
1056
|
+
async updatePixelConsent(granted) {
|
|
1057
|
+
if (!this.isBrowser || typeof window === "undefined") {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
await this.ensureFbq();
|
|
1061
|
+
window.fbq("consent", granted ? "grant" : "revoke");
|
|
1062
|
+
}
|
|
1063
|
+
// ---------------------------------------------------------------------------
|
|
1064
|
+
// Public API — routes to browser or server mode
|
|
1065
|
+
// ---------------------------------------------------------------------------
|
|
1066
|
+
async track(options) {
|
|
1067
|
+
if (this.isBrowser) {
|
|
1068
|
+
return this.trackBrowser(options);
|
|
1069
|
+
}
|
|
1070
|
+
return this.trackServer(options);
|
|
1071
|
+
}
|
|
1072
|
+
async identify(options) {
|
|
1073
|
+
if (this.isBrowser) {
|
|
1074
|
+
return this.identifyBrowser(options);
|
|
1075
|
+
}
|
|
1076
|
+
return this.identifyServer(options);
|
|
1077
|
+
}
|
|
1078
|
+
async page(options) {
|
|
1079
|
+
if (this.isBrowser) {
|
|
1080
|
+
return this.pageBrowser(options);
|
|
1081
|
+
}
|
|
1082
|
+
return this.pageServer(options);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
763
1086
|
// src/mytart.ts
|
|
764
1087
|
function createProvider(config) {
|
|
765
1088
|
switch (config.provider) {
|
|
@@ -775,6 +1098,8 @@ function createProvider(config) {
|
|
|
775
1098
|
return new PlausibleProvider(config);
|
|
776
1099
|
case "posthog":
|
|
777
1100
|
return new PostHogProvider(config);
|
|
1101
|
+
case "meta-pixel":
|
|
1102
|
+
return new MetaPixelProvider(config);
|
|
778
1103
|
default: {
|
|
779
1104
|
const exhaustive = config;
|
|
780
1105
|
throw new Error(`Unknown provider: ${exhaustive.provider}`);
|
|
@@ -836,6 +1161,7 @@ var Mytart = class {
|
|
|
836
1161
|
AmplitudeProvider,
|
|
837
1162
|
BaseProvider,
|
|
838
1163
|
GoogleAnalyticsProvider,
|
|
1164
|
+
MetaPixelProvider,
|
|
839
1165
|
MixpanelProvider,
|
|
840
1166
|
Mytart,
|
|
841
1167
|
PlausibleProvider,
|
package/dist/index.mjs
CHANGED
|
@@ -102,7 +102,9 @@ var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
|
102
102
|
window.gtag("consent", "default", consentParams);
|
|
103
103
|
}
|
|
104
104
|
window.gtag("js", /* @__PURE__ */ new Date());
|
|
105
|
-
const configParams = {
|
|
105
|
+
const configParams = {
|
|
106
|
+
send_page_view: false
|
|
107
|
+
};
|
|
106
108
|
if (this.config.signals === true) {
|
|
107
109
|
configParams["allow_google_signals"] = true;
|
|
108
110
|
configParams["allow_ad_personalization_signals"] = true;
|
|
@@ -110,11 +112,7 @@ var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
|
110
112
|
configParams["allow_google_signals"] = false;
|
|
111
113
|
configParams["allow_ad_personalization_signals"] = false;
|
|
112
114
|
}
|
|
113
|
-
|
|
114
|
-
window.gtag("config", this.config.measurementId, configParams);
|
|
115
|
-
} else {
|
|
116
|
-
window.gtag("config", this.config.measurementId);
|
|
117
|
-
}
|
|
115
|
+
window.gtag("config", this.config.measurementId, configParams);
|
|
118
116
|
return new Promise((resolve) => {
|
|
119
117
|
const script = document.createElement("script");
|
|
120
118
|
script.async = true;
|
|
@@ -717,6 +715,330 @@ var PostHogProvider = class extends BaseProvider {
|
|
|
717
715
|
}
|
|
718
716
|
};
|
|
719
717
|
|
|
718
|
+
// src/utils/hash.ts
|
|
719
|
+
import { createHash } from "crypto";
|
|
720
|
+
function sha256(value) {
|
|
721
|
+
if (/^[a-f0-9]{64}$/.test(value)) {
|
|
722
|
+
return value;
|
|
723
|
+
}
|
|
724
|
+
return createHash("sha256").update(value).digest("hex");
|
|
725
|
+
}
|
|
726
|
+
var PII_FIELDS = /* @__PURE__ */ new Set([
|
|
727
|
+
"em",
|
|
728
|
+
"ph",
|
|
729
|
+
"fn",
|
|
730
|
+
"ln",
|
|
731
|
+
"ge",
|
|
732
|
+
"db",
|
|
733
|
+
"ct",
|
|
734
|
+
"st",
|
|
735
|
+
"zp",
|
|
736
|
+
"country"
|
|
737
|
+
]);
|
|
738
|
+
function hashUserData(userData) {
|
|
739
|
+
const result = {};
|
|
740
|
+
for (const [key, value] of Object.entries(userData)) {
|
|
741
|
+
if (PII_FIELDS.has(key) && typeof value === "string" && value.length > 0) {
|
|
742
|
+
result[key] = sha256(value);
|
|
743
|
+
} else {
|
|
744
|
+
result[key] = value;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return result;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/providers/meta-pixel.ts
|
|
751
|
+
var FBEVENTS_SCRIPT_URL = "https://connect.facebook.net/en_US/fbevents.js";
|
|
752
|
+
var DEFAULT_API_VERSION = "v21.0";
|
|
753
|
+
var STANDARD_EVENTS = /* @__PURE__ */ new Set([
|
|
754
|
+
"PageView",
|
|
755
|
+
"Purchase",
|
|
756
|
+
"AddToCart",
|
|
757
|
+
"ViewContent",
|
|
758
|
+
"InitiateCheckout",
|
|
759
|
+
"Search",
|
|
760
|
+
"Lead",
|
|
761
|
+
"CompleteRegistration",
|
|
762
|
+
"AddPaymentInfo",
|
|
763
|
+
"AddToWishlist",
|
|
764
|
+
"Contact",
|
|
765
|
+
"StartTrial",
|
|
766
|
+
"Subscribe",
|
|
767
|
+
"Donate",
|
|
768
|
+
"Schedule",
|
|
769
|
+
"SubmitApplication",
|
|
770
|
+
"CustomizeProduct",
|
|
771
|
+
"FindLocation"
|
|
772
|
+
]);
|
|
773
|
+
var MetaPixelProvider = class extends BaseProvider {
|
|
774
|
+
constructor(config) {
|
|
775
|
+
super();
|
|
776
|
+
this.name = "meta-pixel";
|
|
777
|
+
this.fbqReady = null;
|
|
778
|
+
/**
|
|
779
|
+
* Cached user data for the Conversions API. Updated by `identify()` so
|
|
780
|
+
* that subsequent `track()` and `page()` calls include user information.
|
|
781
|
+
*/
|
|
782
|
+
this.cachedUserData = {};
|
|
783
|
+
this.config = config;
|
|
784
|
+
this.http = createHttpClient();
|
|
785
|
+
this.isBrowser = config.appType === "browser";
|
|
786
|
+
this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
|
|
787
|
+
if (config.advancedMatching) {
|
|
788
|
+
this.cachedUserData = { ...config.advancedMatching };
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
// Browser mode — fbevents.js injection & fbq() calls
|
|
793
|
+
// ---------------------------------------------------------------------------
|
|
794
|
+
/**
|
|
795
|
+
* Initialises the Meta Pixel snippet. This mirrors the official snippet from
|
|
796
|
+
* https://developers.facebook.com/docs/meta-pixel/get-started:
|
|
797
|
+
*
|
|
798
|
+
* !function(f,b,e,v,n,t,s){...}(window, document,'script',
|
|
799
|
+
* 'https://connect.facebook.net/en_US/fbevents.js');
|
|
800
|
+
* fbq('init', 'PIXEL_ID');
|
|
801
|
+
* fbq('track', 'PageView');
|
|
802
|
+
*
|
|
803
|
+
* Key details:
|
|
804
|
+
* - The fbq shim queue is set up synchronously before the script loads
|
|
805
|
+
* - `fbq('init', pixelId, advancedMatching?)` is called immediately
|
|
806
|
+
* - autoConfig is respected via `fbq('set', 'autoConfig', false, pixelId)`
|
|
807
|
+
* - debug mode via `fbq('set', 'debug', true)`
|
|
808
|
+
* - The returned promise resolves when the script finishes loading
|
|
809
|
+
*/
|
|
810
|
+
initFbq() {
|
|
811
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
812
|
+
return Promise.resolve();
|
|
813
|
+
}
|
|
814
|
+
if (!window.fbq) {
|
|
815
|
+
const queue = [];
|
|
816
|
+
const fbq = Object.assign(
|
|
817
|
+
function fbq2(...args) {
|
|
818
|
+
if (fbq2.callMethod) {
|
|
819
|
+
fbq2.callMethod(...args);
|
|
820
|
+
} else {
|
|
821
|
+
queue.push(args);
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
push: void 0,
|
|
826
|
+
loaded: true,
|
|
827
|
+
version: "2.0",
|
|
828
|
+
queue,
|
|
829
|
+
callMethod: void 0
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
fbq.push = fbq;
|
|
833
|
+
window.fbq = fbq;
|
|
834
|
+
window._fbq = fbq;
|
|
835
|
+
}
|
|
836
|
+
if (this.config.autoConfig === false) {
|
|
837
|
+
window.fbq("set", "autoConfig", false, this.config.pixelId);
|
|
838
|
+
}
|
|
839
|
+
if (this.config.debug) {
|
|
840
|
+
window.fbq("set", "debug", true);
|
|
841
|
+
}
|
|
842
|
+
const matchData = this.config.advancedMatching;
|
|
843
|
+
if (matchData && Object.keys(matchData).length > 0) {
|
|
844
|
+
window.fbq("init", this.config.pixelId, matchData);
|
|
845
|
+
} else {
|
|
846
|
+
window.fbq("init", this.config.pixelId);
|
|
847
|
+
}
|
|
848
|
+
return new Promise((resolve) => {
|
|
849
|
+
const script = document.createElement("script");
|
|
850
|
+
script.async = true;
|
|
851
|
+
script.src = FBEVENTS_SCRIPT_URL;
|
|
852
|
+
script.onload = () => resolve();
|
|
853
|
+
script.onerror = () => resolve();
|
|
854
|
+
document.head.appendChild(script);
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Ensures the Pixel is initialised exactly once. Subsequent calls return
|
|
859
|
+
* the same promise so the script is never injected twice.
|
|
860
|
+
*/
|
|
861
|
+
ensureFbq() {
|
|
862
|
+
if (!this.fbqReady) {
|
|
863
|
+
this.fbqReady = this.initFbq();
|
|
864
|
+
}
|
|
865
|
+
return this.fbqReady;
|
|
866
|
+
}
|
|
867
|
+
// -- browser track --
|
|
868
|
+
async trackBrowser(options) {
|
|
869
|
+
if (typeof window === "undefined") {
|
|
870
|
+
return this.buildFbqResult();
|
|
871
|
+
}
|
|
872
|
+
await this.ensureFbq();
|
|
873
|
+
const { event, properties, context } = options;
|
|
874
|
+
const eventId = context?.eventId;
|
|
875
|
+
const isStandard = STANDARD_EVENTS.has(event);
|
|
876
|
+
const command = isStandard ? "track" : "trackCustom";
|
|
877
|
+
if (eventId) {
|
|
878
|
+
window.fbq(command, event, properties ?? {}, { eventID: eventId });
|
|
879
|
+
} else {
|
|
880
|
+
window.fbq(command, event, properties ?? {});
|
|
881
|
+
}
|
|
882
|
+
return this.buildFbqResult();
|
|
883
|
+
}
|
|
884
|
+
// -- browser identify --
|
|
885
|
+
async identifyBrowser(options) {
|
|
886
|
+
if (typeof window === "undefined") {
|
|
887
|
+
return this.buildFbqResult();
|
|
888
|
+
}
|
|
889
|
+
await this.ensureFbq();
|
|
890
|
+
const matchData = {};
|
|
891
|
+
if (options.traits) {
|
|
892
|
+
Object.assign(matchData, options.traits);
|
|
893
|
+
}
|
|
894
|
+
matchData["external_id"] = options.userId;
|
|
895
|
+
window.fbq("init", this.config.pixelId, matchData);
|
|
896
|
+
return this.buildFbqResult();
|
|
897
|
+
}
|
|
898
|
+
// -- browser page --
|
|
899
|
+
async pageBrowser(options) {
|
|
900
|
+
if (typeof window === "undefined") {
|
|
901
|
+
return this.buildFbqResult();
|
|
902
|
+
}
|
|
903
|
+
await this.ensureFbq();
|
|
904
|
+
window.fbq("track", "PageView");
|
|
905
|
+
return this.buildFbqResult();
|
|
906
|
+
}
|
|
907
|
+
buildFbqResult() {
|
|
908
|
+
return {
|
|
909
|
+
provider: this.name,
|
|
910
|
+
success: true,
|
|
911
|
+
statusCode: 200
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
// ---------------------------------------------------------------------------
|
|
915
|
+
// Server mode — Meta Conversions API (CAPI)
|
|
916
|
+
// ---------------------------------------------------------------------------
|
|
917
|
+
get capiEndpoint() {
|
|
918
|
+
return `https://graph.facebook.com/${this.apiVersion}/${this.config.pixelId}/events`;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Build the `user_data` object for a CAPI event.
|
|
922
|
+
* Merges cached user data (from `identify()` / config) with any per-event
|
|
923
|
+
* overrides, then SHA-256 hashes known PII fields.
|
|
924
|
+
*/
|
|
925
|
+
buildUserData(overrides) {
|
|
926
|
+
const merged = { ...this.cachedUserData, ...overrides };
|
|
927
|
+
return hashUserData(merged);
|
|
928
|
+
}
|
|
929
|
+
// -- server track --
|
|
930
|
+
async trackServer(options) {
|
|
931
|
+
try {
|
|
932
|
+
const { event, properties, context, timestamp, userId } = options;
|
|
933
|
+
const eventId = context?.eventId;
|
|
934
|
+
const userDataOverrides = {};
|
|
935
|
+
if (userId) {
|
|
936
|
+
userDataOverrides["external_id"] = userId;
|
|
937
|
+
}
|
|
938
|
+
const eventData = {
|
|
939
|
+
event_name: event,
|
|
940
|
+
event_time: timestamp ? Math.floor(timestamp.getTime() / 1e3) : Math.floor(Date.now() / 1e3),
|
|
941
|
+
action_source: "website",
|
|
942
|
+
user_data: this.buildUserData(userDataOverrides)
|
|
943
|
+
};
|
|
944
|
+
if (eventId) {
|
|
945
|
+
eventData["event_id"] = eventId;
|
|
946
|
+
}
|
|
947
|
+
const pageUrl = context?.page?.url;
|
|
948
|
+
if (pageUrl) {
|
|
949
|
+
eventData["event_source_url"] = pageUrl;
|
|
950
|
+
}
|
|
951
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
952
|
+
eventData["custom_data"] = properties;
|
|
953
|
+
}
|
|
954
|
+
const body = {
|
|
955
|
+
data: [eventData]
|
|
956
|
+
};
|
|
957
|
+
if (this.config.testEventCode) {
|
|
958
|
+
body["test_event_code"] = this.config.testEventCode;
|
|
959
|
+
}
|
|
960
|
+
const response = await this.http.post(this.capiEndpoint, body, {
|
|
961
|
+
params: { access_token: this.config.accessToken }
|
|
962
|
+
});
|
|
963
|
+
return this.buildSuccess(response.status);
|
|
964
|
+
} catch (error) {
|
|
965
|
+
if (isAxiosError(error)) {
|
|
966
|
+
return this.buildError(
|
|
967
|
+
error.message,
|
|
968
|
+
`META_PIXEL_HTTP_${error.response?.status ?? "ERROR"}`,
|
|
969
|
+
error
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
return this.buildError(String(error), "META_PIXEL_UNKNOWN_ERROR", error);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// -- server identify --
|
|
976
|
+
async identifyServer(options) {
|
|
977
|
+
if (options.traits) {
|
|
978
|
+
Object.assign(this.cachedUserData, options.traits);
|
|
979
|
+
}
|
|
980
|
+
this.cachedUserData["external_id"] = options.userId;
|
|
981
|
+
return this.buildSuccess(200);
|
|
982
|
+
}
|
|
983
|
+
// -- server page --
|
|
984
|
+
async pageServer(options) {
|
|
985
|
+
return this.trackServer({
|
|
986
|
+
event: "PageView",
|
|
987
|
+
properties: options.properties,
|
|
988
|
+
userId: options.userId,
|
|
989
|
+
anonymousId: options.anonymousId,
|
|
990
|
+
timestamp: options.timestamp,
|
|
991
|
+
context: {
|
|
992
|
+
page: {
|
|
993
|
+
url: options.url,
|
|
994
|
+
referrer: options.referrer
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
// ---------------------------------------------------------------------------
|
|
1000
|
+
// Consent — Meta Pixel's simple grant / revoke model
|
|
1001
|
+
// ---------------------------------------------------------------------------
|
|
1002
|
+
/**
|
|
1003
|
+
* Meta Pixel consent is a binary grant/revoke model, not the granular
|
|
1004
|
+
* settings of Google Consent Mode v2. This method accepts the standard
|
|
1005
|
+
* `ConsentSettings` type but is a no-op — use `updatePixelConsent()` for
|
|
1006
|
+
* Meta-specific consent control in browser mode.
|
|
1007
|
+
*/
|
|
1008
|
+
/**
|
|
1009
|
+
* Grant or revoke consent for the Meta Pixel in browser mode.
|
|
1010
|
+
* Calls `fbq('consent', 'grant')` or `fbq('consent', 'revoke')`.
|
|
1011
|
+
*/
|
|
1012
|
+
async updatePixelConsent(granted) {
|
|
1013
|
+
if (!this.isBrowser || typeof window === "undefined") {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
await this.ensureFbq();
|
|
1017
|
+
window.fbq("consent", granted ? "grant" : "revoke");
|
|
1018
|
+
}
|
|
1019
|
+
// ---------------------------------------------------------------------------
|
|
1020
|
+
// Public API — routes to browser or server mode
|
|
1021
|
+
// ---------------------------------------------------------------------------
|
|
1022
|
+
async track(options) {
|
|
1023
|
+
if (this.isBrowser) {
|
|
1024
|
+
return this.trackBrowser(options);
|
|
1025
|
+
}
|
|
1026
|
+
return this.trackServer(options);
|
|
1027
|
+
}
|
|
1028
|
+
async identify(options) {
|
|
1029
|
+
if (this.isBrowser) {
|
|
1030
|
+
return this.identifyBrowser(options);
|
|
1031
|
+
}
|
|
1032
|
+
return this.identifyServer(options);
|
|
1033
|
+
}
|
|
1034
|
+
async page(options) {
|
|
1035
|
+
if (this.isBrowser) {
|
|
1036
|
+
return this.pageBrowser(options);
|
|
1037
|
+
}
|
|
1038
|
+
return this.pageServer(options);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
720
1042
|
// src/mytart.ts
|
|
721
1043
|
function createProvider(config) {
|
|
722
1044
|
switch (config.provider) {
|
|
@@ -732,6 +1054,8 @@ function createProvider(config) {
|
|
|
732
1054
|
return new PlausibleProvider(config);
|
|
733
1055
|
case "posthog":
|
|
734
1056
|
return new PostHogProvider(config);
|
|
1057
|
+
case "meta-pixel":
|
|
1058
|
+
return new MetaPixelProvider(config);
|
|
735
1059
|
default: {
|
|
736
1060
|
const exhaustive = config;
|
|
737
1061
|
throw new Error(`Unknown provider: ${exhaustive.provider}`);
|
|
@@ -792,6 +1116,7 @@ export {
|
|
|
792
1116
|
AmplitudeProvider,
|
|
793
1117
|
BaseProvider,
|
|
794
1118
|
GoogleAnalyticsProvider,
|
|
1119
|
+
MetaPixelProvider,
|
|
795
1120
|
MixpanelProvider,
|
|
796
1121
|
Mytart,
|
|
797
1122
|
PlausibleProvider,
|