@v-tilt/browser 1.11.0 → 1.13.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/dist/all-external-dependencies.js +1 -1
- package/dist/all-external-dependencies.js.map +1 -1
- package/dist/array.full.js +1 -1
- package/dist/array.full.js.map +1 -1
- package/dist/array.js +1 -1
- package/dist/array.js.map +1 -1
- package/dist/array.no-external.js +1 -1
- package/dist/array.no-external.js.map +1 -1
- package/dist/autocapture-types.d.ts +17 -0
- package/dist/autocapture-utils.d.ts +24 -1
- package/dist/autocapture.d.ts +94 -5
- package/dist/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/constants.d.ts +19 -13
- package/dist/core/capture.d.ts +15 -5
- package/dist/core/config-utils.d.ts +15 -0
- package/dist/core/consent.d.ts +62 -0
- package/dist/core/event-buffer.d.ts +60 -0
- package/dist/core/fb-cookies.d.ts +32 -0
- package/dist/core/feature-manager.d.ts +61 -69
- package/dist/core/fifo-queue.d.ts +23 -0
- package/dist/core/identity.d.ts +23 -33
- package/dist/core/index.d.ts +7 -1
- package/dist/core/page-lifecycle.d.ts +41 -0
- package/dist/core/remote-config.d.ts +14 -17
- package/dist/extensions/chat/bubble-drag.d.ts +30 -0
- package/dist/extensions/chat/chat-api.d.ts +15 -0
- package/dist/extensions/chat/chat-styles.d.ts +27 -0
- package/dist/extensions/chat/chat-wrapper.d.ts +20 -145
- package/dist/extensions/chat/chat.d.ts +261 -14
- package/dist/extensions/chat/message-content-styles.d.ts +1 -0
- package/dist/extensions/chat/message-html.d.ts +6 -0
- package/dist/extensions/chat/message-markdown.d.ts +8 -0
- package/dist/extensions/chat/normalize-send-content.d.ts +24 -0
- package/dist/extensions/chat/types.d.ts +19 -57
- package/dist/extensions/chat/widget-registry.d.ts +53 -0
- package/dist/extensions/chat/widgets/collect-email.d.ts +6 -0
- package/dist/extensions/chat/widgets/escalate-to-human.d.ts +6 -0
- package/dist/extensions/ga4-proxy.d.ts +59 -0
- package/dist/extensions/google-tag-gateway/consent-bridge.d.ts +27 -0
- package/dist/extensions/google-tag-gateway/enhanced-conversions.d.ts +35 -0
- package/dist/extensions/google-tag-gateway/event-bridge.d.ts +74 -0
- package/dist/extensions/google-tag-gateway/google-tag-gateway.d.ts +95 -0
- package/dist/extensions/google-tag-gateway/gtag-loader.d.ts +85 -0
- package/dist/extensions/google-tag-gateway/index.d.ts +7 -0
- package/dist/extensions/google-tag-gateway/normalize.d.ts +28 -0
- package/dist/extensions/google-tag-gateway/public-api.d.ts +23 -0
- package/dist/extensions/history-autocapture.d.ts +2 -2
- package/dist/extensions/replay/index.d.ts +1 -1
- package/dist/extensions/replay/session-recording-utils.d.ts +13 -43
- package/dist/extensions/replay/session-recording-wrapper.d.ts +10 -66
- package/dist/extensions/replay/session-recording.d.ts +53 -1
- package/dist/extensions/replay/types.d.ts +6 -1
- package/dist/extensions/web-vitals/web-vitals-manager.d.ts +14 -43
- package/dist/external-scripts-loader.js +1 -1
- package/dist/external-scripts-loader.js.map +1 -1
- package/dist/feature.d.ts +54 -172
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +728 -753
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +728 -753
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/rate-limiter.d.ts +0 -1
- package/dist/recorder.js +1 -1
- package/dist/recorder.js.map +1 -1
- package/dist/request.d.ts +34 -20
- package/dist/scroll-depth-tracker.d.ts +42 -0
- package/dist/server.d.ts +114 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +12 -0
- package/dist/types.d.ts +204 -9
- package/dist/user-manager.d.ts +26 -52
- package/dist/utils/base64.d.ts +30 -0
- package/dist/utils/bot-detection.d.ts +28 -0
- package/dist/utils/endpoint-url.d.ts +36 -0
- package/dist/utils/event-emitter.d.ts +1 -0
- package/dist/utils/globals.d.ts +71 -2
- package/dist/utils/index.d.ts +20 -5
- package/dist/utils/logger.d.ts +66 -0
- package/dist/utils/request-utils.d.ts +5 -0
- package/dist/utils/safewrap.d.ts +6 -1
- package/dist/utils/transport-health.d.ts +55 -0
- package/dist/vtilt.d.ts +85 -25
- package/dist/web-vitals.js.map +1 -1
- package/package.json +71 -66
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Conversions helper.
|
|
3
|
+
*
|
|
4
|
+
* Hashes user-provided PII (email/phone/name/address) with SHA-256 before
|
|
5
|
+
* it leaves the browser, matching Google Ads' Enhanced Conversions format:
|
|
6
|
+
* https://support.google.com/google-ads/answer/13258081
|
|
7
|
+
*
|
|
8
|
+
* Returns an object suitable for `gtag('set', 'user_data', { ... })`.
|
|
9
|
+
*/
|
|
10
|
+
export interface RawUserData {
|
|
11
|
+
email?: string | null;
|
|
12
|
+
phone?: string | null;
|
|
13
|
+
first_name?: string | null;
|
|
14
|
+
last_name?: string | null;
|
|
15
|
+
street?: string | null;
|
|
16
|
+
city?: string | null;
|
|
17
|
+
region?: string | null;
|
|
18
|
+
postal_code?: string | null;
|
|
19
|
+
country?: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface HashedUserAddress {
|
|
22
|
+
sha256_first_name?: string;
|
|
23
|
+
sha256_last_name?: string;
|
|
24
|
+
sha256_street?: string;
|
|
25
|
+
city?: string;
|
|
26
|
+
region?: string;
|
|
27
|
+
postal_code?: string;
|
|
28
|
+
country?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface HashedUserData {
|
|
31
|
+
sha256_email_address?: string;
|
|
32
|
+
sha256_phone_number?: string;
|
|
33
|
+
address?: HashedUserAddress;
|
|
34
|
+
}
|
|
35
|
+
export declare function buildHashedUserData(raw: RawUserData): Promise<HashedUserData | null>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic event bridge from `vt.capture()` into `gtag('event', ...)`.
|
|
3
|
+
*
|
|
4
|
+
* Two layers run in order on every captured event:
|
|
5
|
+
*
|
|
6
|
+
* 1. **GA4 forwarder** (`forwardToGtag`) — applies the admin's
|
|
7
|
+
* `event_filter`, then an explicit `event_mappings` row, then a built-in
|
|
8
|
+
* preset (e.g. `$pageview` → `page_view`), then the `autoForward`
|
|
9
|
+
* default-on fallback. Fires `gtag('event', mapped_name, params)` on
|
|
10
|
+
* success. This is the primary path: because most customers link GA4 to
|
|
11
|
+
* Google Ads, the GA4 event flows to Ads automatically when marked as a
|
|
12
|
+
* conversion.
|
|
13
|
+
* 2. **Ads-only conversions** (`forwardEvent`) — for customers who
|
|
14
|
+
* configured direct Ads conversion mappings on the destination. Fires
|
|
15
|
+
* `gtag('event', 'conversion', {send_to: 'AW-.../...'})`. Unchanged by
|
|
16
|
+
* design so existing deployments keep working.
|
|
17
|
+
*
|
|
18
|
+
* Matching on both layers is by exact event name. vTilt's own internal
|
|
19
|
+
* events (`$identify`, `$set`, etc.) are skipped unless the admin wired
|
|
20
|
+
* them up explicitly.
|
|
21
|
+
*/
|
|
22
|
+
import type { GoogleAdsConversionMapping, GoogleTagClientConfig } from "../../types";
|
|
23
|
+
import type { GtagFn } from "./consent-bridge";
|
|
24
|
+
export interface ConversionBuildOptions {
|
|
25
|
+
mapping: GoogleAdsConversionMapping;
|
|
26
|
+
payload: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export interface BuiltConversion {
|
|
29
|
+
send_to: string;
|
|
30
|
+
params: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export declare function buildConversionParams({ mapping, payload, }: ConversionBuildOptions): BuiltConversion;
|
|
33
|
+
/**
|
|
34
|
+
* Bridge a single captured event to gtag using the Ads conversion mappings
|
|
35
|
+
* that match `eventName`. Each matching mapping results in one
|
|
36
|
+
* `gtag('event', 'conversion', ...)` call.
|
|
37
|
+
*/
|
|
38
|
+
export declare function forwardEvent(gtag: GtagFn, eventName: string, payload: Record<string, unknown>, config: GoogleTagClientConfig): BuiltConversion[];
|
|
39
|
+
/**
|
|
40
|
+
* Check whether a captured event name is allowed by the admin's
|
|
41
|
+
* `event_filter`. `exclude` wins over `include`; an empty/missing
|
|
42
|
+
* `include` means "all events pass". Mirrors the server-side filter
|
|
43
|
+
* semantics exactly.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isEventAllowed(eventName: string, filter: GoogleTagClientConfig["eventFilter"]): boolean;
|
|
46
|
+
export interface ForwardToGtagResult {
|
|
47
|
+
/** `true` when a `gtag('event', ...)` call was made. */
|
|
48
|
+
fired: boolean;
|
|
49
|
+
/** The event name that was forwarded (post-mapping/preset), if fired. */
|
|
50
|
+
eventName?: string;
|
|
51
|
+
/** The params object that was passed to gtag, if fired. */
|
|
52
|
+
params?: Record<string, unknown>;
|
|
53
|
+
/** Which rule produced the forward. */
|
|
54
|
+
source?: "mapping" | "preset" | "auto";
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Forward one captured event to gtag as a GA4 event. Resolution order:
|
|
58
|
+
*
|
|
59
|
+
* 1. Filter — `event_filter.exclude` drops, then `event_filter.include`
|
|
60
|
+
* (when non-empty) whitelists. Same as the server-side filter.
|
|
61
|
+
* 2. Explicit mapping — an `event_mappings` row with matching `source`
|
|
62
|
+
* wins and always fires, regardless of `autoForward`. This is the
|
|
63
|
+
* admin's "force fire" path.
|
|
64
|
+
* 3. Built-in preset — when no explicit mapping is set and
|
|
65
|
+
* `autoForward !== false`, `$pageview`/`$pageleave` fire their GA4
|
|
66
|
+
* analogues with sensibly-named params.
|
|
67
|
+
* 4. Auto-forward fallback — when `autoForward !== false` and the event
|
|
68
|
+
* is not a `$*` internal event, fire `gtag('event', eventName, payload)`
|
|
69
|
+
* with reserved/internal keys stripped.
|
|
70
|
+
*
|
|
71
|
+
* Admins who want a strict allowlist set `autoForward: false` and use
|
|
72
|
+
* `event_mappings` to enumerate the events they care about.
|
|
73
|
+
*/
|
|
74
|
+
export declare function forwardToGtag(gtag: GtagFn, eventName: string, payload: Record<string, unknown>, config: GoogleTagClientConfig): ForwardToGtagResult;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoogleTagGateway feature.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the client-side half of the Google Tag Gateway destination:
|
|
5
|
+
*
|
|
6
|
+
* 1. Maintains `window.dataLayer` / `window.gtag`.
|
|
7
|
+
* 2. Pushes Consent Mode v2 defaults BEFORE gtag.js loads, and re-pushes
|
|
8
|
+
* on `consent:updated` events.
|
|
9
|
+
* 3. Injects `gtag/js?id=<primary>` from the vTilt gateway (`/gt`) so
|
|
10
|
+
* every measurement request flows through the first-party origin.
|
|
11
|
+
* 4. Subscribes to EVENT_CAPTURED and forwards mapped events to Ads
|
|
12
|
+
* (`gtag('event', 'conversion', ...)`).
|
|
13
|
+
* 5. Exposes a flat `vt.gtag(...)` passthrough for power users.
|
|
14
|
+
*
|
|
15
|
+
* Forwards only after `__remote_config_loaded` is true (same signal as
|
|
16
|
+
* EventBuffer: fresh `/decide` applied, bootstrap, or fetch failure).
|
|
17
|
+
* Captures before that are queued so we never drop events just because
|
|
18
|
+
* `/decide` has not committed yet. After remote is ready, we install the
|
|
19
|
+
* official `dataLayer` + `gtag` shim (`ensureDataLayer`) and forward
|
|
20
|
+
* immediately — the shim queues until `gtag.js` loads; we do not maintain a
|
|
21
|
+
* second SDK queue for that window.
|
|
22
|
+
*
|
|
23
|
+
* Feature lifecycle matches the rest of the SDK — registered with
|
|
24
|
+
* FeatureManager via a descriptor that pulls the admin-configured shape
|
|
25
|
+
* out of `/decide` under the `googleTag` key.
|
|
26
|
+
*/
|
|
27
|
+
import type { VTilt } from "../../vtilt";
|
|
28
|
+
import type { VTiltConfig, GoogleTagClientConfig } from "../../types";
|
|
29
|
+
import type { Feature, FeatureConfig } from "../../feature";
|
|
30
|
+
import { type GtagFn } from "./consent-bridge";
|
|
31
|
+
import { type GtagCall } from "./public-api";
|
|
32
|
+
import { type RawUserData } from "./enhanced-conversions";
|
|
33
|
+
export interface GoogleTagGatewayFeatureConfig extends FeatureConfig {
|
|
34
|
+
remote?: GoogleTagClientConfig;
|
|
35
|
+
}
|
|
36
|
+
export interface DeliveryLogEntry {
|
|
37
|
+
ts: number;
|
|
38
|
+
tag_ids: string[];
|
|
39
|
+
event_name: string;
|
|
40
|
+
send_to: string;
|
|
41
|
+
status: "fired" | "dropped";
|
|
42
|
+
reason?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare class GoogleTagGateway implements Feature {
|
|
45
|
+
readonly name = "GoogleTagGateway";
|
|
46
|
+
private _instance;
|
|
47
|
+
private _config;
|
|
48
|
+
private _isStarted;
|
|
49
|
+
private _scriptInjected;
|
|
50
|
+
private _scriptLoaded;
|
|
51
|
+
private _consentDefaultsPushed;
|
|
52
|
+
private _gtag;
|
|
53
|
+
private _publicApi;
|
|
54
|
+
private _loaderOptions;
|
|
55
|
+
private _unsubscribeCaptured;
|
|
56
|
+
private _unsubscribeConsent;
|
|
57
|
+
private _deliveryLog;
|
|
58
|
+
private readonly _maxDeliveryLog;
|
|
59
|
+
/**
|
|
60
|
+
* Captures observed before `__remote_config_loaded` only. Once remote
|
|
61
|
+
* commits, `ensureDataLayer` + gtag forward — gtag's own queue handles
|
|
62
|
+
* ordering until `gtag.js` is on the wire.
|
|
63
|
+
*/
|
|
64
|
+
private readonly _pendingCaptures;
|
|
65
|
+
constructor(instance: VTilt, config?: GoogleTagGatewayFeatureConfig);
|
|
66
|
+
static extractConfig(config: VTiltConfig): GoogleTagGatewayFeatureConfig;
|
|
67
|
+
get isEnabled(): boolean;
|
|
68
|
+
get isStarted(): boolean;
|
|
69
|
+
/** Exposed for tests and the public `vt.gtag` binding. */
|
|
70
|
+
get gtag(): GtagFn;
|
|
71
|
+
get deliveryLog(): DeliveryLogEntry[];
|
|
72
|
+
startIfEnabled(): void;
|
|
73
|
+
stop(): void;
|
|
74
|
+
onConfigUpdate(config: VTiltConfig): void;
|
|
75
|
+
/**
|
|
76
|
+
* Set the user identifiers that power Enhanced Conversions. Values are
|
|
77
|
+
* normalized + SHA-256 hashed client-side before being pushed to gtag,
|
|
78
|
+
* so raw PII never leaves the browser.
|
|
79
|
+
*/
|
|
80
|
+
setUserData(raw: RawUserData): Promise<void>;
|
|
81
|
+
getRecentPublicCalls(): GtagCall[];
|
|
82
|
+
private _start;
|
|
83
|
+
private _drainPending;
|
|
84
|
+
private _pushConsentDefaultsOnce;
|
|
85
|
+
private _subscribeCaptured;
|
|
86
|
+
/**
|
|
87
|
+
* Handle one captured event. Only `__remote_config_loaded === false` uses
|
|
88
|
+
* `_pendingCaptures`. After remote commits, we boot the gtag pipeline on
|
|
89
|
+
* demand (`startIfEnabled`) and forward — the dataLayer shim buffers until
|
|
90
|
+
* `gtag.js` loads.
|
|
91
|
+
*/
|
|
92
|
+
private _handleCaptured;
|
|
93
|
+
private _subscribeConsent;
|
|
94
|
+
private _record;
|
|
95
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gtag.js loader.
|
|
3
|
+
*
|
|
4
|
+
* Loads `gtag/js?id=<primary>` from the vTilt gateway (`/gt`) so that every
|
|
5
|
+
* subsequent measurement request — `gtag/js`, `/g/collect`, and
|
|
6
|
+
* `/pagead/conversion` — stays on the first-party origin. Installs the
|
|
7
|
+
* standard `window.dataLayer` queue and `window.gtag` shim exactly the way
|
|
8
|
+
* Google's official snippet does, so any third-party integration that
|
|
9
|
+
* expects them (Ads editor, Tag Assistant, CMP libraries) keeps working.
|
|
10
|
+
*/
|
|
11
|
+
import type { GoogleTagClientConfig } from "../../types";
|
|
12
|
+
import type { GtagFn } from "./consent-bridge";
|
|
13
|
+
export interface GtagLoaderOptions {
|
|
14
|
+
/** Origin to load gtag.js from. Must include protocol. */
|
|
15
|
+
origin: string;
|
|
16
|
+
/** Path under `origin` that hosts the gateway. Defaults to `/gt`. */
|
|
17
|
+
gatewayPath: string;
|
|
18
|
+
/** Primary tag id used in the loader URL (first `G-*`, else first id). */
|
|
19
|
+
primaryTagId: string;
|
|
20
|
+
/** All tag ids to `gtag('config', id, ...)` after load. */
|
|
21
|
+
tagIds: string[];
|
|
22
|
+
/** Extra per-tag config. Currently only used to set `server_container_url`. */
|
|
23
|
+
configParams?: Record<string, unknown>;
|
|
24
|
+
/** Whether gtag.js should send its own `page_view` when configured. */
|
|
25
|
+
sendPageView?: boolean;
|
|
26
|
+
/** Enable `debug_mode` on gtag configs. */
|
|
27
|
+
debugMode?: boolean;
|
|
28
|
+
/** Enable `conversion_linker` (default true in gtag.js). */
|
|
29
|
+
conversionLinker?: boolean;
|
|
30
|
+
/** Domains for the `linker` feature (cross-domain). */
|
|
31
|
+
linkerDomains?: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface LoadedGtag {
|
|
34
|
+
gtag: GtagFn;
|
|
35
|
+
dataLayer: unknown[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Ensure a single `window.dataLayer` + `window.gtag` pair exists. Safe to
|
|
39
|
+
* call multiple times — returns the same references. Matches Google's
|
|
40
|
+
* reference snippet:
|
|
41
|
+
*
|
|
42
|
+
* window.dataLayer = window.dataLayer || [];
|
|
43
|
+
* function gtag(){ dataLayer.push(arguments); }
|
|
44
|
+
*/
|
|
45
|
+
export declare function ensureDataLayer(): LoadedGtag;
|
|
46
|
+
/**
|
|
47
|
+
* Inject the gtag.js <script> tag pointed at the vTilt gateway. The loader
|
|
48
|
+
* script is fire-and-forget; the dataLayer/gtag shim queues any calls we
|
|
49
|
+
* make before the script finishes.
|
|
50
|
+
*/
|
|
51
|
+
export declare function loadGtagScript(options: GtagLoaderOptions, onLoaded?: () => void, onError?: (err: Error) => void): HTMLScriptElement | null;
|
|
52
|
+
/**
|
|
53
|
+
* Initialize gtag (`'js'` + per-id `'config'`) and — if provided — seed
|
|
54
|
+
* conversion linker and cross-domain `linker` params. This runs through
|
|
55
|
+
* the dataLayer shim, so calls made before `gtag.js` finishes loading are
|
|
56
|
+
* replayed automatically once the script boots.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installGtagConfigs(gtag: GtagFn, options: GtagLoaderOptions): void;
|
|
59
|
+
/**
|
|
60
|
+
* Derive loader options from the server-provided feature config.
|
|
61
|
+
*
|
|
62
|
+
* There are exactly two proxy resolutions:
|
|
63
|
+
*
|
|
64
|
+
* | proxyMode | base | path |
|
|
65
|
+
* | ---------- | ------------------------------------------------- | ----- |
|
|
66
|
+
* | `proxied` | `api_host` verbatim (or `location.origin`) | `/gt` |
|
|
67
|
+
* | `direct` | `https://www.googletagmanager.com` | `""` |
|
|
68
|
+
*
|
|
69
|
+
* In proxied mode `api_host` is treated as a verbatim URL prefix — matching
|
|
70
|
+
* how the rest of the SDK builds URLs (`capture`, array loader, chat). It
|
|
71
|
+
* may be any of:
|
|
72
|
+
*
|
|
73
|
+
* - a full URL with a path, e.g. `https://proxy.example.com/v1`
|
|
74
|
+
* - a bare origin, e.g. `https://proxy.example.com`
|
|
75
|
+
* - a relative path, e.g. `/vt` (first-party reverse proxy)
|
|
76
|
+
*
|
|
77
|
+
* For `transport_url` we still need an absolute URL, so a relative
|
|
78
|
+
* `api_host` is expanded against `window.location.origin` in that one
|
|
79
|
+
* place. The loader `<script>` `src` itself keeps the original relative
|
|
80
|
+
* form and is resolved by the browser against the document.
|
|
81
|
+
*
|
|
82
|
+
* Customers who self-host a proxy simply point `api_host` at it — the
|
|
83
|
+
* proxy must implement `/gt/*` under that prefix.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildLoaderOptions(config: GoogleTagClientConfig, apiHost: string | undefined): GtagLoaderOptions | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { GoogleTagGateway, type GoogleTagGatewayFeatureConfig, type DeliveryLogEntry, } from "./google-tag-gateway";
|
|
2
|
+
export { createPublicGtagApi, type PublicGtagApi } from "./public-api";
|
|
3
|
+
export { forwardEvent, buildConversionParams } from "./event-bridge";
|
|
4
|
+
export { ensureDataLayer, loadGtagScript, installGtagConfigs, buildLoaderOptions, type GtagLoaderOptions, } from "./gtag-loader";
|
|
5
|
+
export { pushConsentDefault, pushConsentUpdate, buildConsentPayload, type GtagFn, type GoogleConsentPayload, } from "./consent-bridge";
|
|
6
|
+
export { buildHashedUserData, type HashedUserData, type HashedUserAddress, type RawUserData, } from "./enhanced-conversions";
|
|
7
|
+
export { normalizeEmail, normalizePhone, normalizeBasic, normalizePostalCode, getByPath, coerceNumber, coerceString, } from "./normalize";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google-specific normalization for Enhanced Conversions.
|
|
3
|
+
*
|
|
4
|
+
* Follows the rules documented in
|
|
5
|
+
* https://support.google.com/google-ads/answer/13262500 — phone numbers must be
|
|
6
|
+
* in E.164 format, emails lowercased with provider-specific dot stripping on
|
|
7
|
+
* gmail/googlemail, and names/addresses normalized to lowercase without
|
|
8
|
+
* leading/trailing whitespace.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeEmail(raw: string | null | undefined): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a phone number to E.164. If the input has no leading '+' we
|
|
13
|
+
* cannot reliably infer the country code, so we return null rather than
|
|
14
|
+
* send an unhashable/ambiguous value — Enhanced Conversions treats missing
|
|
15
|
+
* fields as acceptable.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizePhone(raw: string | null | undefined): string | null;
|
|
18
|
+
export declare function normalizeBasic(raw: string | null | undefined): string | null;
|
|
19
|
+
/** Postal codes: collapse whitespace, uppercase (some ISO formats are alpha). */
|
|
20
|
+
export declare function normalizePostalCode(raw: string | null | undefined): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Read a nested path from an object using dot notation. Used by the
|
|
23
|
+
* event-bridge to extract `value`/`currency`/`transaction_id` from the
|
|
24
|
+
* captured event payload based on the admin-configured path.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getByPath(source: unknown, path: string | null | undefined): unknown;
|
|
27
|
+
export declare function coerceNumber(value: unknown): number | undefined;
|
|
28
|
+
export declare function coerceString(value: unknown): string | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vt.gtag(...)` escape hatch.
|
|
3
|
+
*
|
|
4
|
+
* Power users may need to call `gtag` directly (e.g. to fire a custom
|
|
5
|
+
* conversion from a third-party widget or to set a debug param). We expose
|
|
6
|
+
* a flat passthrough that:
|
|
7
|
+
*
|
|
8
|
+
* - queues calls made before the SDK has booted (keeps them in FIFO order),
|
|
9
|
+
* - queues calls made before `gtag.js` has loaded (forwarded via the
|
|
10
|
+
* `dataLayer` shim, which naturally buffers until the real script runs),
|
|
11
|
+
* - records every call so tests + the delivery log can see them.
|
|
12
|
+
*/
|
|
13
|
+
import type { GtagFn } from "./consent-bridge";
|
|
14
|
+
export type GtagCall = unknown[];
|
|
15
|
+
export interface PublicGtagApi {
|
|
16
|
+
/** The flat function exposed as `vt.gtag(...)` */
|
|
17
|
+
call: GtagFn;
|
|
18
|
+
/** Flush queued calls to a now-ready gtag implementation. */
|
|
19
|
+
flush(target: GtagFn): void;
|
|
20
|
+
/** Observability: recent calls sent through this API (ring buffer). */
|
|
21
|
+
getRecentCalls(): GtagCall[];
|
|
22
|
+
}
|
|
23
|
+
export declare function createPublicGtagApi(): PublicGtagApi;
|
|
@@ -10,8 +10,6 @@ import type { VTilt } from "../vtilt";
|
|
|
10
10
|
import type { VTiltConfig } from "../types";
|
|
11
11
|
import type { Feature, FeatureConfig } from "../feature";
|
|
12
12
|
export interface HistoryAutocaptureConfig extends FeatureConfig {
|
|
13
|
-
/** Whether to capture pageviews on history changes */
|
|
14
|
-
enabled?: boolean;
|
|
15
13
|
}
|
|
16
14
|
/**
|
|
17
15
|
* History Autocapture Feature
|
|
@@ -39,6 +37,8 @@ export declare class HistoryAutocapture implements Feature {
|
|
|
39
37
|
onConfigUpdate(config: VTiltConfig): void;
|
|
40
38
|
private _start;
|
|
41
39
|
private _patchHistoryMethods;
|
|
40
|
+
/** $pageleave must run while the current URL still reflects the page being left. */
|
|
41
|
+
private _emitPageleaveIfPathWillChange;
|
|
42
42
|
private _setupPopstateListener;
|
|
43
43
|
private _capturePageview;
|
|
44
44
|
}
|
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
export { SessionRecordingWrapper, LAZY_LOADING, type SessionRecordingStatus, type LazyLoadedSessionRecordingInterface, } from "./session-recording-wrapper";
|
|
11
11
|
export { LazyLoadedSessionRecording } from "./session-recording";
|
|
12
12
|
export type { SessionRecordingConfig, SessionStartReason, RecordOptions, RRWebRecord, SnapshotBuffer, CanvasRecordingConfig, NetworkPayloadCaptureConfig, MaskingConfig, TriggerType, } from "./types";
|
|
13
|
-
export {
|
|
13
|
+
export { estimateSize, truncateLargeConsoleLogs, splitBuffer, isSessionIdleEvent, isRecordingPausedEvent, isInteractiveEvent, RECORDING_MAX_EVENT_SIZE, RECORDING_BUFFER_TIMEOUT, RECORDING_IDLE_THRESHOLD_MS, } from "./session-recording-utils";
|
|
@@ -4,63 +4,33 @@
|
|
|
4
4
|
* Utility functions for session recording.
|
|
5
5
|
* Based on PostHog's sessionrecording-utils.ts
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { IncrementalSource, eventWithTime } from "@rrweb/types";
|
|
8
8
|
import type { SnapshotBuffer } from "./types";
|
|
9
9
|
export declare const INCREMENTAL_SNAPSHOT_EVENT_TYPE = 3;
|
|
10
10
|
export declare const ONE_KB = 1024;
|
|
11
11
|
export declare const ONE_MB: number;
|
|
12
12
|
export declare const SEVEN_MB: number;
|
|
13
|
-
export declare const PARTIAL_COMPRESSION_THRESHOLD = 1024;
|
|
14
13
|
export declare const RECORDING_MAX_EVENT_SIZE: number;
|
|
15
14
|
export declare const RECORDING_BUFFER_TIMEOUT = 2000;
|
|
16
15
|
export declare const RECORDING_IDLE_THRESHOLD_MS: number;
|
|
17
|
-
/** Active interaction sources */
|
|
18
|
-
export declare const ACTIVE_SOURCES: IncrementalSource[];
|
|
19
16
|
/**
|
|
20
|
-
*
|
|
17
|
+
* Selectors passed to rrweb `blockSelector` by default so common non-visible
|
|
18
|
+
* template / screen-reader / Webflow CMS nodes are not serialized (they replay
|
|
19
|
+
* as same-sized placeholders). Reduces payload size and keeps hidden form copy
|
|
20
|
+
* out of recordings. Stylesheet-only `display:none` is not detectable here.
|
|
21
21
|
*/
|
|
22
|
-
export declare
|
|
22
|
+
export declare const DEFAULT_INVISIBLE_TEMPLATE_BLOCK_SELECTOR: string;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Merge comma-separated CSS selector lists with de-duplication.
|
|
25
|
+
* When `skipDefaults` is true, omit {@link DEFAULT_INVISIBLE_TEMPLATE_BLOCK_SELECTOR}.
|
|
25
26
|
*/
|
|
26
|
-
export declare function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
data: string;
|
|
30
|
-
}
|
|
31
|
-
export interface CompressedIncrementalSnapshotEvent {
|
|
32
|
-
type: typeof EventType.IncrementalSnapshot;
|
|
33
|
-
data: {
|
|
34
|
-
source: IncrementalSource;
|
|
35
|
-
texts: string;
|
|
36
|
-
attributes: string;
|
|
37
|
-
removes: string;
|
|
38
|
-
adds: string;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
export interface CompressedIncrementalStyleSnapshotEvent {
|
|
42
|
-
type: typeof EventType.IncrementalSnapshot;
|
|
43
|
-
data: {
|
|
44
|
-
source: typeof IncrementalSource.StyleSheetRule;
|
|
45
|
-
id?: number;
|
|
46
|
-
styleId?: number;
|
|
47
|
-
replace?: string;
|
|
48
|
-
replaceSync?: string;
|
|
49
|
-
adds?: string;
|
|
50
|
-
removes?: string;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
export type CompressedEvent = CompressedFullSnapshotEvent | CompressedIncrementalSnapshotEvent | CompressedIncrementalStyleSnapshotEvent;
|
|
54
|
-
export type CompressedEventWithTime = CompressedEvent & {
|
|
55
|
-
timestamp: number;
|
|
56
|
-
delay?: number;
|
|
57
|
-
cv: "2024-10";
|
|
58
|
-
};
|
|
27
|
+
export declare function mergeBlockSelectors(skipDefaults: boolean | undefined, ...sources: (string | undefined | null)[]): string | undefined;
|
|
28
|
+
/** Active interaction sources */
|
|
29
|
+
export declare const ACTIVE_SOURCES: IncrementalSource[];
|
|
59
30
|
/**
|
|
60
|
-
*
|
|
61
|
-
* Only compresses full snapshots and mutation events above threshold
|
|
31
|
+
* Estimate the size of an object in bytes
|
|
62
32
|
*/
|
|
63
|
-
export declare function
|
|
33
|
+
export declare function estimateSize(obj: unknown): number;
|
|
64
34
|
/**
|
|
65
35
|
* Truncate large console log payloads to prevent excessive data
|
|
66
36
|
*/
|
|
@@ -1,92 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session Recording Wrapper
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* loaded on demand.
|
|
4
|
+
* Extends ToggleableLazyFeature to handle lazy-loading the rrweb recorder
|
|
5
|
+
* script and delegating to the heavy LazyLoadedSessionRecording instance.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* Based on PostHog's sessionrecording-wrapper.ts
|
|
7
|
+
* @see docs/patterns/tracker-feature-lifecycle.md
|
|
10
8
|
*/
|
|
11
9
|
import type { VTilt } from "../../vtilt";
|
|
12
|
-
import type { VTiltConfig
|
|
13
|
-
import type { ToggleableFeature } from "../../feature";
|
|
10
|
+
import type { VTiltConfig } from "../../types";
|
|
14
11
|
import type { SessionRecordingConfig, SessionStartReason } from "./types";
|
|
12
|
+
import { ToggleableLazyFeature } from "../../feature";
|
|
15
13
|
import { LazyLoadedSessionRecordingInterface } from "../../utils/globals";
|
|
16
|
-
/** Status when lazy loading is in progress */
|
|
17
14
|
export declare const LAZY_LOADING: "lazy_loading";
|
|
18
|
-
/** Session recording status */
|
|
19
15
|
export type SessionRecordingStatus = "disabled" | "buffering" | "active" | "paused" | "sampled" | "trigger_pending" | typeof LAZY_LOADING;
|
|
20
16
|
export type { LazyLoadedSessionRecordingInterface };
|
|
21
|
-
|
|
22
|
-
* Session Recording Wrapper
|
|
23
|
-
*
|
|
24
|
-
* This is the lightweight class that lives in the main bundle.
|
|
25
|
-
* Implements ToggleableFeature for consistent lifecycle management.
|
|
26
|
-
*
|
|
27
|
-
* It handles:
|
|
28
|
-
* - Deciding if recording should be enabled
|
|
29
|
-
* - Lazy loading the actual recording code
|
|
30
|
-
* - Delegating to LazyLoadedSessionRecording
|
|
31
|
-
*/
|
|
32
|
-
export declare class SessionRecordingWrapper implements ToggleableFeature {
|
|
33
|
-
private readonly _instance;
|
|
17
|
+
export declare class SessionRecordingWrapper extends ToggleableLazyFeature<SessionRecordingConfig, LazyLoadedSessionRecordingInterface> {
|
|
34
18
|
readonly name = "SessionRecording";
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
private _isStarted;
|
|
38
|
-
constructor(_instance: VTilt, config?: SessionRecordingConfig);
|
|
39
|
-
/**
|
|
40
|
-
* Extract session recording config from VTiltConfig.
|
|
41
|
-
* Self-contained - vtilt.ts doesn't need to know config details.
|
|
42
|
-
*/
|
|
19
|
+
readonly scriptName = "recorder";
|
|
20
|
+
constructor(instance: VTilt, config?: SessionRecordingConfig);
|
|
43
21
|
static extractConfig(config: VTiltConfig): SessionRecordingConfig;
|
|
44
22
|
get isEnabled(): boolean;
|
|
45
23
|
get isStarted(): boolean;
|
|
46
|
-
/**
|
|
47
|
-
* Start if enabled (Feature interface).
|
|
48
|
-
* Use startIfEnabledOrStop for recording as it needs to actively stop.
|
|
49
|
-
*/
|
|
50
24
|
startIfEnabled(): void;
|
|
51
|
-
/**
|
|
52
|
-
* Start if enabled, or stop if disabled (ToggleableFeature interface).
|
|
53
|
-
* This is the primary method for session recording.
|
|
54
|
-
*/
|
|
55
25
|
startIfEnabledOrStop(trigger?: SessionStartReason): void;
|
|
56
|
-
/**
|
|
57
|
-
* Stop recording (Feature interface).
|
|
58
|
-
*/
|
|
59
26
|
stop(): void;
|
|
60
|
-
/**
|
|
61
|
-
* Handle config updates (Feature interface).
|
|
62
|
-
*/
|
|
63
27
|
onConfigUpdate(config: VTiltConfig): void;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*/
|
|
67
|
-
onRemoteConfig(remoteConfig: RemoteConfig): void;
|
|
28
|
+
protected _createLoaded(): LazyLoadedSessionRecordingInterface;
|
|
29
|
+
protected _onLoaded(loaded: LazyLoadedSessionRecordingInterface, trigger?: SessionStartReason): void;
|
|
68
30
|
get started(): boolean;
|
|
69
31
|
get status(): SessionRecordingStatus;
|
|
70
32
|
get sessionId(): string;
|
|
71
|
-
/**
|
|
72
|
-
* Alias for stop() for backwards compatibility.
|
|
73
|
-
*/
|
|
74
33
|
stopRecording(): void;
|
|
75
|
-
/**
|
|
76
|
-
* Log a message to the recording.
|
|
77
|
-
*/
|
|
78
34
|
log(message: string, level?: "log" | "warn" | "error"): void;
|
|
79
|
-
/**
|
|
80
|
-
* Update configuration.
|
|
81
|
-
*/
|
|
82
35
|
updateConfig(config: Partial<SessionRecordingConfig>): void;
|
|
83
|
-
private get _scriptName();
|
|
84
|
-
/**
|
|
85
|
-
* Lazy load the recording script and start.
|
|
86
|
-
*/
|
|
87
|
-
private _lazyLoadAndStart;
|
|
88
|
-
/**
|
|
89
|
-
* Called after the recording script is loaded.
|
|
90
|
-
*/
|
|
91
|
-
private _onScriptLoaded;
|
|
92
36
|
}
|
|
@@ -59,6 +59,7 @@ export declare class LazyLoadedSessionRecording implements LazyLoadedSessionReco
|
|
|
59
59
|
*/
|
|
60
60
|
updateConfig(config: Partial<SessionRecordingConfig>): void;
|
|
61
61
|
private _onBeforeUnload;
|
|
62
|
+
private _onPageHide;
|
|
62
63
|
private _onOffline;
|
|
63
64
|
private _onOnline;
|
|
64
65
|
private _onVisibilityChange;
|
|
@@ -72,12 +73,63 @@ export declare class LazyLoadedSessionRecording implements LazyLoadedSessionReco
|
|
|
72
73
|
private _updateWindowAndSessionIds;
|
|
73
74
|
private _clearBuffer;
|
|
74
75
|
private _flushBuffer;
|
|
76
|
+
/**
|
|
77
|
+
* Unconditionally flush the buffer, ignoring status checks.
|
|
78
|
+
* Used during stop() and session transitions to prevent data loss.
|
|
79
|
+
*/
|
|
80
|
+
private _forceFlushBuffer;
|
|
81
|
+
private _sendBufferContents;
|
|
75
82
|
private _captureSnapshotBuffered;
|
|
76
83
|
private _captureSnapshot;
|
|
84
|
+
/**
|
|
85
|
+
* Send a snapshot batch to the ingestion endpoint.
|
|
86
|
+
*
|
|
87
|
+
* Transport selection:
|
|
88
|
+
*
|
|
89
|
+
* - FullSnapshots, or batches over `BEACON_SIZE_LIMIT`, always use
|
|
90
|
+
* fetch (with retry). They're too important / too big for sendBeacon.
|
|
91
|
+
* - Smaller batches use `sendBeacon` first.
|
|
92
|
+
*
|
|
93
|
+
* Encoding selection (this is the part that fixes the server-side
|
|
94
|
+
* `Z_BUF_ERROR`):
|
|
95
|
+
*
|
|
96
|
+
* - **fetch path** sends the gzip bytes as a binary `Blob` and uses
|
|
97
|
+
* `?compression=gzip-js`. Reliable because the request lifetime is
|
|
98
|
+
* bounded by the JS context.
|
|
99
|
+
* - **sendBeacon path** sends the gzip bytes as a base64 *string* and
|
|
100
|
+
* uses `?compression=base64`. `sendBeacon(url, Blob([binary]))`
|
|
101
|
+
* races with tab discard / `pagehide`: the browser queues the
|
|
102
|
+
* beacon synchronously and serializes the body after JS is torn
|
|
103
|
+
* down, by which time the Uint8Array's backing `ArrayBuffer` may
|
|
104
|
+
* have been detached. A string-backed Blob owns its own UTF-8 copy
|
|
105
|
+
* and survives unload. The server already understands both flags.
|
|
106
|
+
*
|
|
107
|
+
* The query-param flag is *only* added once we know the encoded body
|
|
108
|
+
* actually carries that compression — never up front from configuration —
|
|
109
|
+
* so the URL can never claim compression while the body is empty/JSON.
|
|
110
|
+
*/
|
|
77
111
|
private _sendSnapshot;
|
|
112
|
+
/**
|
|
113
|
+
* fetch path — binary gzip body, `compression=gzip-js`. Auth travels
|
|
114
|
+
* in the `x-api-key` header (see `_sendSnapshot` for the rationale).
|
|
115
|
+
*/
|
|
116
|
+
private _sendViaFetch;
|
|
117
|
+
/**
|
|
118
|
+
* sendBeacon path — base64 text body, `compression=base64`. The token
|
|
119
|
+
* must be on the URL (caller pre-built `baseUrl` with
|
|
120
|
+
* `includeTokenQuery: true`) because beacons cannot carry headers.
|
|
121
|
+
* Falls back to fetch+keepalive — same URL, no header — if the beacon
|
|
122
|
+
* is refused, so the two transports look identical on the wire.
|
|
123
|
+
*/
|
|
124
|
+
private _sendViaBeacon;
|
|
78
125
|
/**
|
|
79
126
|
* Fetch with exponential backoff retry (PostHog-style)
|
|
80
|
-
* Retries on network errors and 5xx server errors
|
|
127
|
+
* Retries on network errors and 5xx server errors.
|
|
128
|
+
*
|
|
129
|
+
* When `token` is non-empty the request is authenticated via the
|
|
130
|
+
* `x-api-key` header (clean URL, harder to block). When empty the
|
|
131
|
+
* caller has already authenticated via `?token=` in the URL (used by
|
|
132
|
+
* the beacon-fallback path where the URL must match the beacon's).
|
|
81
133
|
*/
|
|
82
134
|
private _fetchWithRetry;
|
|
83
135
|
/**
|
|
@@ -139,6 +139,11 @@ export interface SessionRecordingConfig {
|
|
|
139
139
|
blockClass?: string;
|
|
140
140
|
/** Block selector for elements to hide */
|
|
141
141
|
blockSelector?: string;
|
|
142
|
+
/**
|
|
143
|
+
* When `true`, do not merge built-in selectors for off-screen / template DOM
|
|
144
|
+
* (`.sr-only`, Webflow `w-condition-invisible`, etc.). Default `false`.
|
|
145
|
+
*/
|
|
146
|
+
skipDefaultInvisibleBlocking?: boolean;
|
|
142
147
|
/** Ignore class for input masking */
|
|
143
148
|
ignoreClass?: string;
|
|
144
149
|
/** Mask text class */
|
|
@@ -153,7 +158,7 @@ export interface SessionRecordingConfig {
|
|
|
153
158
|
recordHeaders?: boolean;
|
|
154
159
|
/** Record body in network requests */
|
|
155
160
|
recordBody?: boolean;
|
|
156
|
-
/**
|
|
161
|
+
/** Gzip-compress the request body before sending (transport-level) */
|
|
157
162
|
compressEvents?: boolean;
|
|
158
163
|
/** Internal: Mutation throttler refill rate */
|
|
159
164
|
__mutationThrottlerRefillRate?: number;
|