@v-tilt/browser 1.10.10 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/all-external-dependencies.js +1 -1
  2. package/dist/all-external-dependencies.js.map +1 -1
  3. package/dist/array.full.js +1 -1
  4. package/dist/array.full.js.map +1 -1
  5. package/dist/array.js +1 -1
  6. package/dist/array.js.map +1 -1
  7. package/dist/array.no-external.js +1 -1
  8. package/dist/array.no-external.js.map +1 -1
  9. package/dist/autocapture-types.d.ts +17 -0
  10. package/dist/autocapture-utils.d.ts +24 -1
  11. package/dist/autocapture.d.ts +94 -5
  12. package/dist/chat.js +1 -1
  13. package/dist/chat.js.map +1 -1
  14. package/dist/config.d.ts +8 -1
  15. package/dist/constants.d.ts +19 -13
  16. package/dist/core/capture.d.ts +15 -5
  17. package/dist/core/config-utils.d.ts +15 -0
  18. package/dist/core/consent.d.ts +62 -0
  19. package/dist/core/event-buffer.d.ts +60 -0
  20. package/dist/core/fb-cookies.d.ts +32 -0
  21. package/dist/core/feature-manager.d.ts +61 -69
  22. package/dist/core/fifo-queue.d.ts +23 -0
  23. package/dist/core/identity.d.ts +23 -33
  24. package/dist/core/index.d.ts +7 -1
  25. package/dist/core/page-lifecycle.d.ts +41 -0
  26. package/dist/core/remote-config.d.ts +14 -17
  27. package/dist/extensions/chat/bubble-drag.d.ts +30 -0
  28. package/dist/extensions/chat/chat-api.d.ts +15 -0
  29. package/dist/extensions/chat/chat-styles.d.ts +27 -0
  30. package/dist/extensions/chat/chat-wrapper.d.ts +20 -145
  31. package/dist/extensions/chat/chat.d.ts +129 -12
  32. package/dist/extensions/chat/message-content-styles.d.ts +1 -0
  33. package/dist/extensions/chat/message-html.d.ts +6 -0
  34. package/dist/extensions/chat/message-markdown.d.ts +8 -0
  35. package/dist/extensions/chat/normalize-send-content.d.ts +24 -0
  36. package/dist/extensions/chat/types.d.ts +19 -57
  37. package/dist/extensions/chat/widget-registry.d.ts +53 -0
  38. package/dist/extensions/chat/widgets/collect-email.d.ts +6 -0
  39. package/dist/extensions/chat/widgets/escalate-to-human.d.ts +6 -0
  40. package/dist/extensions/ga4-proxy.d.ts +59 -0
  41. package/dist/extensions/google-tag-gateway/consent-bridge.d.ts +27 -0
  42. package/dist/extensions/google-tag-gateway/enhanced-conversions.d.ts +35 -0
  43. package/dist/extensions/google-tag-gateway/event-bridge.d.ts +74 -0
  44. package/dist/extensions/google-tag-gateway/google-tag-gateway.d.ts +95 -0
  45. package/dist/extensions/google-tag-gateway/gtag-loader.d.ts +85 -0
  46. package/dist/extensions/google-tag-gateway/index.d.ts +7 -0
  47. package/dist/extensions/google-tag-gateway/normalize.d.ts +28 -0
  48. package/dist/extensions/google-tag-gateway/public-api.d.ts +23 -0
  49. package/dist/extensions/history-autocapture.d.ts +2 -2
  50. package/dist/extensions/replay/index.d.ts +1 -1
  51. package/dist/extensions/replay/session-recording-utils.d.ts +13 -43
  52. package/dist/extensions/replay/session-recording-wrapper.d.ts +10 -66
  53. package/dist/extensions/replay/session-recording.d.ts +53 -1
  54. package/dist/extensions/replay/types.d.ts +6 -1
  55. package/dist/extensions/web-vitals/web-vitals-manager.d.ts +14 -43
  56. package/dist/external-scripts-loader.js +1 -1
  57. package/dist/external-scripts-loader.js.map +1 -1
  58. package/dist/feature.d.ts +54 -172
  59. package/dist/main.js +1 -1
  60. package/dist/main.js.map +1 -1
  61. package/dist/module.d.ts +728 -753
  62. package/dist/module.js +1 -1
  63. package/dist/module.js.map +1 -1
  64. package/dist/module.no-external.d.ts +728 -753
  65. package/dist/module.no-external.js +1 -1
  66. package/dist/module.no-external.js.map +1 -1
  67. package/dist/rate-limiter.d.ts +0 -1
  68. package/dist/recorder.js +1 -1
  69. package/dist/recorder.js.map +1 -1
  70. package/dist/request.d.ts +34 -20
  71. package/dist/scroll-depth-tracker.d.ts +42 -0
  72. package/dist/server.d.ts +114 -0
  73. package/dist/server.js +1 -1
  74. package/dist/server.js.map +1 -1
  75. package/dist/session.d.ts +12 -0
  76. package/dist/types.d.ts +204 -9
  77. package/dist/user-manager.d.ts +26 -52
  78. package/dist/utils/base64.d.ts +30 -0
  79. package/dist/utils/bot-detection.d.ts +28 -0
  80. package/dist/utils/endpoint-url.d.ts +36 -0
  81. package/dist/utils/event-emitter.d.ts +1 -0
  82. package/dist/utils/globals.d.ts +71 -2
  83. package/dist/utils/index.d.ts +20 -5
  84. package/dist/utils/logger.d.ts +66 -0
  85. package/dist/utils/request-utils.d.ts +5 -0
  86. package/dist/utils/safewrap.d.ts +6 -1
  87. package/dist/utils/transport-health.d.ts +55 -0
  88. package/dist/utils/type-guards.d.ts +1 -1
  89. package/dist/vtilt.d.ts +85 -25
  90. package/dist/web-vitals.js.map +1 -1
  91. package/package.json +9 -4
@@ -4,7 +4,9 @@
4
4
  * Type definitions for the chat widget extension.
5
5
  * Core types are re-exported from globals.ts for consistency.
6
6
  */
7
- export type { ChatMessage, ChatChannel, ChatChannelSummary, ChatConfig, ChatTheme, ChatWidgetView, LazyLoadedChatInterface, } from "../../utils/globals";
7
+ export type { ChatMessage, ChatChannel, ChatChannelSummary, ChatConfig, ChatTheme, ChatWidgetView, LazyLoadedChatInterface, SendChatMessageContent, SendChatMessageOptions, } from "../../utils/globals";
8
+ export { messageUsesRichRendering, normalizeSendChatMessageContent, } from "./normalize-send-content";
9
+ export type { NormalizedOutgoingMessage } from "./normalize-send-content";
8
10
  /**
9
11
  * Sender types for chat messages
10
12
  */
@@ -45,75 +47,35 @@ export interface PresenceStatus {
45
47
  current_page_url?: string;
46
48
  }
47
49
  /**
48
- * Chat event names for analytics tracking
50
+ * Normalized channel event names for analytics tracking
49
51
  *
50
52
  * Message events are tracked client-side via capture() for full enrichment
51
- * (person properties, geo, device, referrer, etc.).
53
+ * (session, page URL, device, person properties, etc.).
52
54
  * Widget UI events (open/close) are exposed via callbacks in ChatConfig.
53
55
  *
54
- * All chat message events use a single event type ($chat_message) with properties:
55
- * - $sender_type: 'user' | 'ai' | 'agent'
56
- * - $channel_id: Chat channel ID
57
- * - $message_id: Message ID
56
+ * All channel message events use $channel_message with normalized properties:
57
+ * - $channel_type: channel discriminator ('chat', 'email', 'sms')
58
+ * - $conversation_id: conversation identifier
59
+ * - $message_id: message identifier
60
+ * - $direction: 'inbound' | 'outbound'
61
+ * - $sender_type: 'user' | 'ai' | 'agent' | 'system'
62
+ * - $content_preview: first 100 chars of content
58
63
  *
59
64
  * Tracking is gated by remote config:
60
65
  * - chatTracking.trackUserMessages → captures when $sender_type=user
61
66
  * - chatTracking.trackAgentMessages → captures when $sender_type=ai|agent
62
67
  */
63
- export declare const CHAT_EVENTS: {
64
- /** Tracked client-side for all messages (user, AI, agent) */
65
- readonly MESSAGE: "$chat_message";
68
+ export declare const CHANNEL_EVENTS: {
69
+ readonly MESSAGE: "$channel_message";
66
70
  };
67
- /**
68
- * Chat event properties for $chat_message (server-side)
69
- */
70
- export interface ChatMessageEventProperties {
71
- $channel_id: string;
72
- $message_id: string;
73
- $sender_type: SenderType;
74
- $content_preview: string;
75
- $ai_mode: boolean;
76
- $word_count: number;
77
- }
78
- /**
79
- * @deprecated Use ChatMessageEventProperties instead
80
- */
81
- export interface ChatMessageSentEventProperties {
82
- $channel_id: string;
71
+ export interface ChannelMessageEventProperties {
72
+ $channel_type: string;
73
+ $conversation_id: string;
83
74
  $message_id: string;
84
- $content_preview: string;
75
+ $direction: "inbound" | "outbound";
85
76
  $sender_type: SenderType;
86
- $ai_mode: boolean;
87
- $word_count: number;
88
- }
89
- /**
90
- * @deprecated Use ChatMessageEventProperties instead
91
- */
92
- export interface ChatMessageReceivedEventProperties {
93
- $channel_id: string;
94
- $message_id: string;
95
77
  $content_preview: string;
96
- $sender_type: SenderType;
97
- $response_time_ms?: number;
98
- }
99
- /**
100
- * Chat event properties for $chat_started
101
- */
102
- export interface ChatStartedEventProperties {
103
- $channel_id: string;
104
- $initiated_by: "user" | "agent";
105
- $ai_mode: boolean;
106
- }
107
- /**
108
- * Chat event properties for $chat_closed
109
- */
110
- export interface ChatClosedEventProperties {
111
- $channel_id: string;
112
- $resolved_by: "agent" | "user" | "system" | "auto";
113
- $resolution: "resolved" | "unresolved" | "abandoned";
114
- $message_count: number;
115
- $duration_seconds: number;
116
- $ai_only: boolean;
78
+ $ai_mode?: boolean;
117
79
  }
118
80
  /**
119
81
  * Internal widget state
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Chat Widget Registry - Extensible widget system for AI-triggered UI.
3
+ * Tool calls from the AI are mapped to registered widgets; each widget provides render() and onAction().
4
+ */
5
+ export interface WidgetContext {
6
+ channelId: string;
7
+ distinctId: string;
8
+ primaryColor: string;
9
+ /** @deprecated Use buildEndpointUrl() instead */
10
+ apiBase: string;
11
+ token: string;
12
+ /** The ID of the message containing this widget */
13
+ messageId: string;
14
+ /** Build a full URL for a given endpoint path, respecting api_host */
15
+ buildEndpointUrl(path: string): string;
16
+ }
17
+ export interface WidgetActionResult {
18
+ /** Whether the action succeeded — drives metadata-based re-render */
19
+ success?: boolean;
20
+ /** @deprecated Use metadata-driven rendering instead. Kept for custom widget compat. */
21
+ replaceHTML?: string;
22
+ }
23
+ export interface ChatWidgetDefinition {
24
+ /** Unique type identifier, e.g. "collect_email", "schedule_meeting" */
25
+ type: string;
26
+ /** AI tool description (sent to LLM so it knows when to invoke) */
27
+ toolDescription: string;
28
+ /** JSON schema shape for the tool parameters */
29
+ parameters: Record<string, {
30
+ type: string;
31
+ description: string;
32
+ }>;
33
+ /** Returns an HTML string to render inline in the chat bubble */
34
+ render(params: Record<string, unknown>, context: WidgetContext): string;
35
+ /** Called when the widget form is submitted or button clicked */
36
+ onAction(action: string, data: Record<string, unknown>, context: WidgetContext): Promise<WidgetActionResult>;
37
+ }
38
+ export declare class ChatWidgetRegistry {
39
+ private _widgets;
40
+ register(definition: ChatWidgetDefinition): void;
41
+ get(type: string): ChatWidgetDefinition | undefined;
42
+ getAll(): ChatWidgetDefinition[];
43
+ /**
44
+ * Build tool definitions for the AI from all registered widgets.
45
+ */
46
+ getToolDefinitions(): Record<string, {
47
+ description: string;
48
+ parameters: Record<string, {
49
+ type: string;
50
+ description: string;
51
+ }>;
52
+ }>;
53
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Built-in collect_email widget: renders name + email inputs, POSTs to widget actions API.
3
+ * UI state is metadata-driven — submitted state is rendered by _renderWidgets().
4
+ */
5
+ import type { ChatWidgetDefinition } from "../widget-registry";
6
+ export declare const collectEmailWidget: ChatWidgetDefinition;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Built-in escalate_to_human widget: shows a "Connect with a human" button.
3
+ * UI state is metadata-driven — submitted state is rendered by _renderWidgets().
4
+ */
5
+ import type { ChatWidgetDefinition } from "../widget-registry";
6
+ export declare const escalateToHumanWidget: ChatWidgetDefinition;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * GA4 Proxy Feature
3
+ *
4
+ * Automatically configures gtag.js to send through vTilt's domain.
5
+ * When enabled, either reconfigures an existing gtag.js or loads a new one
6
+ * with server_container_url pointing to vTilt's proxy routes.
7
+ *
8
+ * Config sources (init config takes priority over remote config):
9
+ * vt.init({ ga4: { measurementId: 'G-XXX' } })
10
+ * /api/decide → { ga4: { measurementId: 'G-XXX' } }
11
+ */
12
+ import type { VTiltConfig } from "../types";
13
+ import type { Feature, FeatureConfig } from "../feature";
14
+ export interface GA4ProxyConfig extends FeatureConfig {
15
+ measurementId?: string;
16
+ debugMode?: boolean;
17
+ }
18
+ export declare class GA4Proxy implements Feature {
19
+ readonly name = "GA4Proxy";
20
+ private _instance;
21
+ private _config;
22
+ private _isStarted;
23
+ private _pendingGtagCallbacks;
24
+ /** Fires once both client_id and session_id gtag callbacks complete. */
25
+ onIdentityResolved?: () => void;
26
+ constructor(instance: {
27
+ getConfig(): VTiltConfig;
28
+ }, config?: GA4ProxyConfig);
29
+ static extractConfig(config: VTiltConfig): GA4ProxyConfig;
30
+ get isEnabled(): boolean;
31
+ get isStarted(): boolean;
32
+ startIfEnabled(): void;
33
+ stop(): void;
34
+ /**
35
+ * Sync user_id to gtag.js so both proxy and MP event streams share
36
+ * the same identity after identify(). GA4 Realtime uses user_id as
37
+ * the highest-priority identifier; without this, proxy events (no
38
+ * user_id) and MP events (with user_id) count as separate users.
39
+ */
40
+ setUserId(userId: string | null): void;
41
+ onConfigUpdate(config: VTiltConfig): void;
42
+ private _start;
43
+ private _getApiBase;
44
+ private _hasGtag;
45
+ private _reconfigureExistingGtag;
46
+ private _loadAndConfigureGtag;
47
+ /**
48
+ * Use gtag('get', ...) to obtain the authoritative client_id and
49
+ * session_id from gtag.js. Fires onIdentityResolved once BOTH
50
+ * callbacks have completed (counter-based).
51
+ */
52
+ private _resolveGtagIdentity;
53
+ /**
54
+ * Fires after each gtag('get', ...) callback. When all pending callbacks
55
+ * have settled, notifies the host (EventBuffer) so buffered MP events
56
+ * can flush with stable GA4 IDs (same client_id as gtag.js).
57
+ */
58
+ private _onGtagCallbackSettled;
59
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Consent Mode v2 bridge.
3
+ *
4
+ * Maps vTilt's 3-boolean consent (analytics / marketing / advertising) to
5
+ * Google's 4-key Consent Mode v2 payload (ad_storage, ad_user_data,
6
+ * ad_personalization, analytics_storage) and pushes it through gtag.
7
+ *
8
+ * Mapping (default):
9
+ * advertising -> ad_storage + ad_user_data + ad_personalization
10
+ * analytics -> analytics_storage
11
+ * marketing is not sent to Google by default (it covers email/SMS, not ads).
12
+ *
13
+ * Admin UI may override individual keys (`consentOverride`) to hard-wire a
14
+ * specific value regardless of user consent — useful for jurisdictions or
15
+ * projects with fixed policies.
16
+ */
17
+ import type { ConsentState } from "../../core/consent";
18
+ import type { GoogleConsentOverride } from "../../types";
19
+ export type GtagFn = (...args: unknown[]) => void;
20
+ export type GoogleConsentPayload = Record<"ad_storage" | "ad_user_data" | "ad_personalization" | "analytics_storage", "granted" | "denied">;
21
+ export declare function buildConsentPayload(consent: ConsentState, override?: GoogleConsentOverride): GoogleConsentPayload;
22
+ /**
23
+ * Send the default consent state BEFORE gtag.js loads so that every
24
+ * subsequent request carries the right flags. Call once at feature start.
25
+ */
26
+ export declare function pushConsentDefault(gtag: GtagFn, consent: ConsentState, override?: GoogleConsentOverride): void;
27
+ export declare function pushConsentUpdate(gtag: GtagFn, consent: ConsentState, override?: GoogleConsentOverride): void;
@@ -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 { compressEvent, estimateSize, truncateLargeConsoleLogs, splitBuffer, isSessionIdleEvent, isRecordingPausedEvent, isInteractiveEvent, RECORDING_MAX_EVENT_SIZE, RECORDING_BUFFER_TIMEOUT, RECORDING_IDLE_THRESHOLD_MS, } from "./session-recording-utils";
13
+ export { estimateSize, truncateLargeConsoleLogs, splitBuffer, isSessionIdleEvent, isRecordingPausedEvent, isInteractiveEvent, RECORDING_MAX_EVENT_SIZE, RECORDING_BUFFER_TIMEOUT, RECORDING_IDLE_THRESHOLD_MS, } from "./session-recording-utils";