@callforge/tracking-client 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Lightweight client library for the CallForge tracking API. Handles location-aware phone number assignment with aggressive caching and preload optimization.
4
4
 
5
+ **v0.4.0** - Added `ga4MeasurementId` config option for cleaner gtag callback instead of cookie polling.
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -51,7 +53,51 @@ client.onReady((session) => {
51
53
  });
52
54
  ```
53
55
 
54
- ### 3. Track conversion parameters (optional)
56
+ ### 3. GA4 Integration (automatic)
57
+
58
+ The client automatically captures the GA4 client ID and associates it with the tracking session. This enables CallForge to send call events (phone call, conversion, billable conversion) to your Google Analytics 4 property.
59
+
60
+ **Requirements:**
61
+ 1. Google Analytics 4 must be installed on your site
62
+ 2. Configure GA4 credentials in CallForge dashboard (Categories → Edit → GA4 tab)
63
+
64
+ **Recommended: Provide Measurement ID for callback-based capture:**
65
+
66
+ ```typescript
67
+ const client = CallForge.init({
68
+ categoryId: 'your-category-id',
69
+ ga4MeasurementId: 'G-XXXXXXXXXX', // Enables gtag('get') callback
70
+ });
71
+ ```
72
+
73
+ When `ga4MeasurementId` is provided, the client uses the official `gtag('get')` API callback to retrieve the client ID. This is cleaner and more reliable than cookie polling.
74
+
75
+ **How it works:**
76
+ 1. **If `ga4MeasurementId` is provided AND `window.gtag` exists:**
77
+ - Uses `gtag('get', measurementId, 'client_id', callback)` to get the client ID
78
+ - No polling needed - callback fires when GA4 is ready
79
+ 2. **Otherwise (fallback):**
80
+ - Checks for the `_ga` cookie immediately
81
+ - If not found, polls every 500ms for up to 10 seconds
82
+ - Handles async GA4 script loading
83
+
84
+ **Manual override:** If you need to pass a custom GA4 client ID:
85
+
86
+ ```typescript
87
+ client.setParams({
88
+ ga4ClientId: '1234567890.1234567890',
89
+ });
90
+ ```
91
+
92
+ **Extract GA4 client ID yourself:**
93
+
94
+ ```typescript
95
+ import { getGA4ClientId } from '@callforge/tracking-client';
96
+
97
+ const clientId = getGA4ClientId(); // "1234567890.1234567890" or null
98
+ ```
99
+
100
+ ### 4. Track conversion parameters (optional)
55
101
 
56
102
  The client automatically captures ad platform click IDs from the URL:
57
103
 
@@ -82,8 +128,10 @@ Initialize the tracking client.
82
128
 
83
129
  ```typescript
84
130
  interface CallForgeConfig {
85
- categoryId: string; // Required - which number pool to use
86
- endpoint?: string; // Optional - defaults to 'https://tracking.callforge.io'
131
+ categoryId: string; // Required - which number pool to use
132
+ endpoint?: string; // Optional - defaults to 'https://tracking.callforge.io'
133
+ ga4MeasurementId?: string; // Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX")
134
+ // Enables gtag callback instead of cookie polling
87
135
  }
88
136
  ```
89
137
 
@@ -150,19 +198,35 @@ const html = getPreloadSnippet({
150
198
  });
151
199
  ```
152
200
 
201
+ ### `getGA4ClientId()`
202
+
203
+ Extract the GA4 client ID from the `_ga` cookie.
204
+
205
+ ```typescript
206
+ import { getGA4ClientId } from '@callforge/tracking-client';
207
+
208
+ const clientId = getGA4ClientId();
209
+ // Returns: "1234567890.1234567890" or null if cookie not found
210
+ ```
211
+
212
+ **Cookie format:** `GA1.1.1234567890.1234567890`
213
+
214
+ The client ID is the last two dot-separated segments (timestamp.random).
215
+
153
216
  ## Tracking Parameters
154
217
 
155
218
  ### Auto-Capture
156
219
 
157
- The client automatically extracts these parameters from the URL on first visit:
220
+ The client automatically extracts these parameters:
158
221
 
159
- | Parameter | Source |
160
- |-----------|--------|
161
- | `gclid` | Google Ads Click ID |
162
- | `gbraid` | Google app-to-web (iOS 14+) |
163
- | `wbraid` | Google web-to-app |
164
- | `msclkid` | Microsoft/Bing Ads |
165
- | `fbclid` | Facebook/Meta Ads |
222
+ | Parameter | Source | Capture Method |
223
+ |-----------|--------|----------------|
224
+ | `gclid` | Google Ads Click ID | URL query param |
225
+ | `gbraid` | Google app-to-web (iOS 14+) | URL query param |
226
+ | `wbraid` | Google web-to-app | URL query param |
227
+ | `msclkid` | Microsoft/Bing Ads | URL query param |
228
+ | `fbclid` | Facebook/Meta Ads | URL query param |
229
+ | `ga4ClientId` | Google Analytics 4 | `_ga` cookie (polled) |
166
230
 
167
231
  ### Persistence
168
232
 
@@ -214,21 +278,53 @@ import type {
214
278
  TrackingParams,
215
279
  ReadyCallback,
216
280
  } from '@callforge/tracking-client';
281
+
282
+ import { getGA4ClientId } from '@callforge/tracking-client';
217
283
  ```
218
284
 
219
285
  ### TrackingParams
220
286
 
221
287
  ```typescript
222
288
  interface TrackingParams {
223
- gclid?: string; // Google Ads Click ID
224
- gbraid?: string; // Google app-to-web (iOS 14+)
225
- wbraid?: string; // Google web-to-app
226
- msclkid?: string; // Microsoft/Bing Ads
227
- fbclid?: string; // Facebook/Meta Ads
289
+ gclid?: string; // Google Ads Click ID
290
+ gbraid?: string; // Google app-to-web (iOS 14+)
291
+ wbraid?: string; // Google web-to-app
292
+ msclkid?: string; // Microsoft/Bing Ads
293
+ fbclid?: string; // Facebook/Meta Ads
294
+ ga4ClientId?: string; // Google Analytics 4 Client ID (auto-captured from _ga cookie)
228
295
  [key: string]: string | undefined; // Custom params
229
296
  }
230
297
  ```
231
298
 
299
+ ## GA4 Events
300
+
301
+ When GA4 is configured for a category, CallForge sends these events to Google Analytics 4 via the Measurement Protocol:
302
+
303
+ | Event Name | When Sent | Description |
304
+ |------------|-----------|-------------|
305
+ | `phone_call` | Call initiated | A phone call was placed to the tracking number |
306
+ | `call_conversion` | Call qualified | The call met conversion criteria (e.g., duration threshold) |
307
+ | `call_conversion_billable` | Call billable | The call was both qualified AND billable |
308
+
309
+ **Event Parameters:**
310
+
311
+ All events include:
312
+ - `session_id` - CallForge session ID
313
+ - `phone_number` - Tracking number dialed
314
+ - `category` - Category slug
315
+
316
+ `call_conversion` and `call_conversion_billable` also include:
317
+ - `call_duration` - Call duration in seconds
318
+ - `buyer` - Buyer the call was routed to (if applicable)
319
+
320
+ **Setup:**
321
+ 1. In Google Analytics 4, go to Admin → Data Streams → your web stream
322
+ 2. Copy the **Measurement ID** (e.g., `G-XXXXXXXXXX`)
323
+ 3. Go to Admin → Data Streams → Measurement Protocol API secrets → Create
324
+ 4. Copy the **API Secret**
325
+ 5. In CallForge dashboard: Categories → Edit category → GA4 tab
326
+ 6. Enable GA4, paste Measurement ID and API Secret, save
327
+
232
328
  ## Environment URLs
233
329
 
234
330
  | Environment | Endpoint |
package/dist/index.d.mts CHANGED
@@ -6,6 +6,8 @@ interface CallForgeConfig {
6
6
  categoryId: string;
7
7
  /** Optional - API endpoint. Defaults to 'https://tracking.callforge.io' */
8
8
  endpoint?: string;
9
+ /** Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX"). Enables gtag callback instead of cookie polling. */
10
+ ga4MeasurementId?: string;
9
11
  }
10
12
  /**
11
13
  * Location data returned by the tracking API.
@@ -64,14 +66,21 @@ interface ApiResponse {
64
66
  */
65
67
  type ReadyCallback = (session: TrackingSession) => void;
66
68
  /**
67
- * Global window extension for preload promise.
69
+ * Global window extension for preload promise and gtag.
68
70
  */
69
71
  declare global {
70
72
  interface Window {
71
73
  __cfTracking?: Promise<ApiResponse>;
74
+ gtag?: (command: 'get', targetId: string, fieldName: string, callback: (value: string) => void) => void;
72
75
  }
73
76
  }
74
77
 
78
+ /**
79
+ * Extract GA4 client_id from the _ga cookie.
80
+ * Cookie format: GA1.1.1234567890.1234567890
81
+ * Client ID is the last two segments: 1234567890.1234567890
82
+ */
83
+ declare function getGA4ClientId(): string | null;
75
84
  declare class CallForge {
76
85
  private readonly config;
77
86
  private readonly cache;
@@ -107,12 +116,18 @@ declare class CallForge {
107
116
  */
108
117
  getQueuedParams(): TrackingParams;
109
118
  private patchSession;
119
+ /**
120
+ * Start capturing the GA4 client ID.
121
+ * If ga4MeasurementId is configured, uses gtag('get') callback.
122
+ * Otherwise falls back to polling the _ga cookie.
123
+ */
124
+ startGA4ClientIdCapture(): void;
110
125
  /**
111
126
  * Start polling for the GA4 _ga cookie.
112
127
  * If found immediately, calls setParams right away.
113
128
  * Otherwise polls every 500ms for up to 10 seconds.
114
129
  */
115
- startGA4CookiePolling(): void;
130
+ private startGA4CookiePolling;
116
131
  private stopGA4CookiePolling;
117
132
  private fetchSession;
118
133
  private getLocationId;
@@ -141,4 +156,4 @@ declare class CallForge {
141
156
  */
142
157
  declare function getPreloadSnippet(config: CallForgeConfig): string;
143
158
 
144
- export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getPreloadSnippet };
159
+ export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getGA4ClientId, getPreloadSnippet };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ interface CallForgeConfig {
6
6
  categoryId: string;
7
7
  /** Optional - API endpoint. Defaults to 'https://tracking.callforge.io' */
8
8
  endpoint?: string;
9
+ /** Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX"). Enables gtag callback instead of cookie polling. */
10
+ ga4MeasurementId?: string;
9
11
  }
10
12
  /**
11
13
  * Location data returned by the tracking API.
@@ -64,14 +66,21 @@ interface ApiResponse {
64
66
  */
65
67
  type ReadyCallback = (session: TrackingSession) => void;
66
68
  /**
67
- * Global window extension for preload promise.
69
+ * Global window extension for preload promise and gtag.
68
70
  */
69
71
  declare global {
70
72
  interface Window {
71
73
  __cfTracking?: Promise<ApiResponse>;
74
+ gtag?: (command: 'get', targetId: string, fieldName: string, callback: (value: string) => void) => void;
72
75
  }
73
76
  }
74
77
 
78
+ /**
79
+ * Extract GA4 client_id from the _ga cookie.
80
+ * Cookie format: GA1.1.1234567890.1234567890
81
+ * Client ID is the last two segments: 1234567890.1234567890
82
+ */
83
+ declare function getGA4ClientId(): string | null;
75
84
  declare class CallForge {
76
85
  private readonly config;
77
86
  private readonly cache;
@@ -107,12 +116,18 @@ declare class CallForge {
107
116
  */
108
117
  getQueuedParams(): TrackingParams;
109
118
  private patchSession;
119
+ /**
120
+ * Start capturing the GA4 client ID.
121
+ * If ga4MeasurementId is configured, uses gtag('get') callback.
122
+ * Otherwise falls back to polling the _ga cookie.
123
+ */
124
+ startGA4ClientIdCapture(): void;
110
125
  /**
111
126
  * Start polling for the GA4 _ga cookie.
112
127
  * If found immediately, calls setParams right away.
113
128
  * Otherwise polls every 500ms for up to 10 seconds.
114
129
  */
115
- startGA4CookiePolling(): void;
130
+ private startGA4CookiePolling;
116
131
  private stopGA4CookiePolling;
117
132
  private fetchSession;
118
133
  private getLocationId;
@@ -141,4 +156,4 @@ declare class CallForge {
141
156
  */
142
157
  declare function getPreloadSnippet(config: CallForgeConfig): string;
143
158
 
144
- export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getPreloadSnippet };
159
+ export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getGA4ClientId, getPreloadSnippet };
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
35
35
  var index_exports = {};
36
36
  __export(index_exports, {
37
37
  CallForge: () => CallForge,
38
+ getGA4ClientId: () => getGA4ClientId,
38
39
  getPreloadSnippet: () => getPreloadSnippet
39
40
  });
40
41
  module.exports = __toCommonJS(index_exports);
@@ -146,10 +147,11 @@ var CallForge = class _CallForge {
146
147
  this.sessionCreated = false;
147
148
  this.config = {
148
149
  categoryId: config.categoryId,
149
- endpoint: config.endpoint || DEFAULT_ENDPOINT
150
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
151
+ ga4MeasurementId: config.ga4MeasurementId
150
152
  };
151
153
  this.cache = new TrackingCache(config.categoryId);
152
- this.startGA4CookiePolling();
154
+ this.startGA4ClientIdCapture();
153
155
  }
154
156
  /**
155
157
  * Initialize the CallForge tracking client.
@@ -210,15 +212,31 @@ var CallForge = class _CallForge {
210
212
  console.warn("[CallForge] Failed to patch session:", error);
211
213
  }
212
214
  }
215
+ /**
216
+ * Start capturing the GA4 client ID.
217
+ * If ga4MeasurementId is configured, uses gtag('get') callback.
218
+ * Otherwise falls back to polling the _ga cookie.
219
+ */
220
+ startGA4ClientIdCapture() {
221
+ if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
222
+ return;
223
+ }
224
+ if (this.config.ga4MeasurementId && typeof window !== "undefined" && window.gtag) {
225
+ window.gtag("get", this.config.ga4MeasurementId, "client_id", (clientId) => {
226
+ if (clientId) {
227
+ this.setParams({ ga4ClientId: clientId });
228
+ }
229
+ });
230
+ return;
231
+ }
232
+ this.startGA4CookiePolling();
233
+ }
213
234
  /**
214
235
  * Start polling for the GA4 _ga cookie.
215
236
  * If found immediately, calls setParams right away.
216
237
  * Otherwise polls every 500ms for up to 10 seconds.
217
238
  */
218
239
  startGA4CookiePolling() {
219
- if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
220
- return;
221
- }
222
240
  const clientId = getGA4ClientId();
223
241
  if (clientId) {
224
242
  this.setParams({ ga4ClientId: clientId });
@@ -404,5 +422,6 @@ window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.js
404
422
  // Annotate the CommonJS export names for ESM import in node:
405
423
  0 && (module.exports = {
406
424
  CallForge,
425
+ getGA4ClientId,
407
426
  getPreloadSnippet
408
427
  });
package/dist/index.mjs CHANGED
@@ -122,10 +122,11 @@ var CallForge = class _CallForge {
122
122
  this.sessionCreated = false;
123
123
  this.config = {
124
124
  categoryId: config.categoryId,
125
- endpoint: config.endpoint || DEFAULT_ENDPOINT
125
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
126
+ ga4MeasurementId: config.ga4MeasurementId
126
127
  };
127
128
  this.cache = new TrackingCache(config.categoryId);
128
- this.startGA4CookiePolling();
129
+ this.startGA4ClientIdCapture();
129
130
  }
130
131
  /**
131
132
  * Initialize the CallForge tracking client.
@@ -186,15 +187,31 @@ var CallForge = class _CallForge {
186
187
  console.warn("[CallForge] Failed to patch session:", error);
187
188
  }
188
189
  }
190
+ /**
191
+ * Start capturing the GA4 client ID.
192
+ * If ga4MeasurementId is configured, uses gtag('get') callback.
193
+ * Otherwise falls back to polling the _ga cookie.
194
+ */
195
+ startGA4ClientIdCapture() {
196
+ if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
197
+ return;
198
+ }
199
+ if (this.config.ga4MeasurementId && typeof window !== "undefined" && window.gtag) {
200
+ window.gtag("get", this.config.ga4MeasurementId, "client_id", (clientId) => {
201
+ if (clientId) {
202
+ this.setParams({ ga4ClientId: clientId });
203
+ }
204
+ });
205
+ return;
206
+ }
207
+ this.startGA4CookiePolling();
208
+ }
189
209
  /**
190
210
  * Start polling for the GA4 _ga cookie.
191
211
  * If found immediately, calls setParams right away.
192
212
  * Otherwise polls every 500ms for up to 10 seconds.
193
213
  */
194
214
  startGA4CookiePolling() {
195
- if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
196
- return;
197
- }
198
215
  const clientId = getGA4ClientId();
199
216
  if (clientId) {
200
217
  this.setParams({ ga4ClientId: clientId });
@@ -379,5 +396,6 @@ window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.js
379
396
  }
380
397
  export {
381
398
  CallForge,
399
+ getGA4ClientId,
382
400
  getPreloadSnippet
383
401
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callforge/tracking-client",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",