@callforge/tracking-client 0.3.1 → 0.4.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 CHANGED
@@ -2,7 +2,7 @@
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.3.0** - Added automatic GA4 integration for sending call events to Google Analytics 4.
5
+ **v0.4.1** - Simplified GA4 capture: uses gtag callback when `ga4MeasurementId` configured, no polling fallback.
6
6
 
7
7
  ## Installation
8
8
 
@@ -53,19 +53,26 @@ client.onReady((session) => {
53
53
  });
54
54
  ```
55
55
 
56
- ### 3. GA4 Integration (automatic)
56
+ ### 3. GA4 Integration
57
57
 
58
- The client automatically captures the GA4 client ID from the `_ga` cookie 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.
58
+ To enable GA4 call event tracking, provide your GA4 Measurement ID:
59
+
60
+ ```typescript
61
+ const client = CallForge.init({
62
+ categoryId: 'your-category-id',
63
+ ga4MeasurementId: 'G-XXXXXXXXXX', // Required for GA4 integration
64
+ });
65
+ ```
59
66
 
60
67
  **Requirements:**
61
- 1. Google Analytics 4 must be installed on your site
62
- 2. Configure GA4 credentials in CallForge dashboard (Categories → Edit → GA4 tab)
68
+ 1. Google Analytics 4 must be installed on your site (`gtag.js`)
69
+ 2. Provide `ga4MeasurementId` in client config
70
+ 3. Configure GA4 credentials in CallForge dashboard (Categories → Edit → GA4 tab)
63
71
 
64
72
  **How it works:**
65
- - On initialization, the client checks for the `_ga` cookie
66
- - If not found immediately (GA4 script may load async), polls every 500ms for up to 10 seconds
67
- - Once found, extracts the client ID and sends it to CallForge via `setParams()`
68
- - If session already exists, uses PATCH to update with the GA4 client ID
73
+ - Uses `gtag('get', measurementId, 'client_id', callback)` to capture the GA4 client ID
74
+ - Callback fires when GA4 is ready - no polling needed
75
+ - Client ID is automatically sent to CallForge for call event attribution
69
76
 
70
77
  **Manual override:** If you need to pass a custom GA4 client ID:
71
78
 
@@ -75,14 +82,6 @@ client.setParams({
75
82
  });
76
83
  ```
77
84
 
78
- **Extract GA4 client ID yourself:**
79
-
80
- ```typescript
81
- import { getGA4ClientId } from '@callforge/tracking-client';
82
-
83
- const clientId = getGA4ClientId(); // "1234567890.1234567890" or null
84
- ```
85
-
86
85
  ### 4. Track conversion parameters (optional)
87
86
 
88
87
  The client automatically captures ad platform click IDs from the URL:
@@ -114,8 +113,10 @@ Initialize the tracking client.
114
113
 
115
114
  ```typescript
116
115
  interface CallForgeConfig {
117
- categoryId: string; // Required - which number pool to use
118
- endpoint?: string; // Optional - defaults to 'https://tracking.callforge.io'
116
+ categoryId: string; // Required - which number pool to use
117
+ endpoint?: string; // Optional - defaults to 'https://tracking.callforge.io'
118
+ ga4MeasurementId?: string; // Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX")
119
+ // Enables gtag callback instead of cookie polling
119
120
  }
120
121
  ```
121
122
 
@@ -182,35 +183,21 @@ const html = getPreloadSnippet({
182
183
  });
183
184
  ```
184
185
 
185
- ### `getGA4ClientId()`
186
-
187
- Extract the GA4 client ID from the `_ga` cookie.
188
-
189
- ```typescript
190
- import { getGA4ClientId } from '@callforge/tracking-client';
191
-
192
- const clientId = getGA4ClientId();
193
- // Returns: "1234567890.1234567890" or null if cookie not found
194
- ```
195
-
196
- **Cookie format:** `GA1.1.1234567890.1234567890`
197
-
198
- The client ID is the last two dot-separated segments (timestamp.random).
199
-
200
186
  ## Tracking Parameters
201
187
 
202
188
  ### Auto-Capture
203
189
 
204
- The client automatically extracts these parameters:
190
+ The client automatically extracts these parameters from the URL:
205
191
 
206
- | Parameter | Source | Capture Method |
207
- |-----------|--------|----------------|
208
- | `gclid` | Google Ads Click ID | URL query param |
209
- | `gbraid` | Google app-to-web (iOS 14+) | URL query param |
210
- | `wbraid` | Google web-to-app | URL query param |
211
- | `msclkid` | Microsoft/Bing Ads | URL query param |
212
- | `fbclid` | Facebook/Meta Ads | URL query param |
213
- | `ga4ClientId` | Google Analytics 4 | `_ga` cookie (polled) |
192
+ | Parameter | Source |
193
+ |-----------|--------|
194
+ | `gclid` | Google Ads Click ID |
195
+ | `gbraid` | Google app-to-web (iOS 14+) |
196
+ | `wbraid` | Google web-to-app |
197
+ | `msclkid` | Microsoft/Bing Ads |
198
+ | `fbclid` | Facebook/Meta Ads |
199
+
200
+ **Note:** `ga4ClientId` is captured via `gtag('get')` callback when `ga4MeasurementId` is configured.
214
201
 
215
202
  ### Persistence
216
203
 
@@ -262,8 +249,6 @@ import type {
262
249
  TrackingParams,
263
250
  ReadyCallback,
264
251
  } from '@callforge/tracking-client';
265
-
266
- import { getGA4ClientId } from '@callforge/tracking-client';
267
252
  ```
268
253
 
269
254
  ### TrackingParams
@@ -275,7 +260,7 @@ interface TrackingParams {
275
260
  wbraid?: string; // Google web-to-app
276
261
  msclkid?: string; // Microsoft/Bing Ads
277
262
  fbclid?: string; // Facebook/Meta Ads
278
- ga4ClientId?: string; // Google Analytics 4 Client ID (auto-captured from _ga cookie)
263
+ ga4ClientId?: string; // Google Analytics 4 Client ID (auto-captured via gtag callback)
279
264
  [key: string]: string | undefined; // Custom params
280
265
  }
281
266
  ```
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,27 +66,20 @@ 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
 
75
- /**
76
- * Extract GA4 client_id from the _ga cookie.
77
- * Cookie format: GA1.1.1234567890.1234567890
78
- * Client ID is the last two segments: 1234567890.1234567890
79
- */
80
- declare function getGA4ClientId(): string | null;
81
78
  declare class CallForge {
82
79
  private readonly config;
83
80
  private readonly cache;
84
81
  private sessionPromise;
85
82
  private customParams;
86
- private ga4PollInterval;
87
- private ga4PollTimeout;
88
83
  private sessionId;
89
84
  private sessionCreated;
90
85
  private constructor();
@@ -114,12 +109,10 @@ declare class CallForge {
114
109
  getQueuedParams(): TrackingParams;
115
110
  private patchSession;
116
111
  /**
117
- * Start polling for the GA4 _ga cookie.
118
- * If found immediately, calls setParams right away.
119
- * Otherwise polls every 500ms for up to 10 seconds.
112
+ * Capture the GA4 client ID using gtag callback.
113
+ * Only runs if ga4MeasurementId is configured.
120
114
  */
121
- startGA4CookiePolling(): void;
122
- private stopGA4CookiePolling;
115
+ private captureGA4ClientId;
123
116
  private fetchSession;
124
117
  private getLocationId;
125
118
  private getAutoParams;
@@ -147,4 +140,4 @@ declare class CallForge {
147
140
  */
148
141
  declare function getPreloadSnippet(config: CallForgeConfig): string;
149
142
 
150
- export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getGA4ClientId, getPreloadSnippet };
143
+ export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, 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,27 +66,20 @@ 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
 
75
- /**
76
- * Extract GA4 client_id from the _ga cookie.
77
- * Cookie format: GA1.1.1234567890.1234567890
78
- * Client ID is the last two segments: 1234567890.1234567890
79
- */
80
- declare function getGA4ClientId(): string | null;
81
78
  declare class CallForge {
82
79
  private readonly config;
83
80
  private readonly cache;
84
81
  private sessionPromise;
85
82
  private customParams;
86
- private ga4PollInterval;
87
- private ga4PollTimeout;
88
83
  private sessionId;
89
84
  private sessionCreated;
90
85
  private constructor();
@@ -114,12 +109,10 @@ declare class CallForge {
114
109
  getQueuedParams(): TrackingParams;
115
110
  private patchSession;
116
111
  /**
117
- * Start polling for the GA4 _ga cookie.
118
- * If found immediately, calls setParams right away.
119
- * Otherwise polls every 500ms for up to 10 seconds.
112
+ * Capture the GA4 client ID using gtag callback.
113
+ * Only runs if ga4MeasurementId is configured.
120
114
  */
121
- startGA4CookiePolling(): void;
122
- private stopGA4CookiePolling;
115
+ private captureGA4ClientId;
123
116
  private fetchSession;
124
117
  private getLocationId;
125
118
  private getAutoParams;
@@ -147,4 +140,4 @@ declare class CallForge {
147
140
  */
148
141
  declare function getPreloadSnippet(config: CallForgeConfig): string;
149
142
 
150
- export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getGA4ClientId, getPreloadSnippet };
143
+ export { CallForge, type CallForgeConfig, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getPreloadSnippet };
package/dist/index.js CHANGED
@@ -35,7 +35,6 @@ 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,
39
38
  getPreloadSnippet: () => getPreloadSnippet
40
39
  });
41
40
  module.exports = __toCommonJS(index_exports);
@@ -130,27 +129,21 @@ var TrackingCache = class {
130
129
 
131
130
  // src/client.ts
132
131
  var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
133
- function getGA4ClientId() {
134
- if (typeof document === "undefined") return null;
135
- const match = document.cookie.match(/_ga=GA\d+\.\d+\.(.+?)(?:;|$)/);
136
- return match ? match[1] : null;
137
- }
138
132
  var FETCH_TIMEOUT_MS = 1e4;
139
133
  var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid"];
140
134
  var CallForge = class _CallForge {
141
135
  constructor(config) {
142
136
  this.sessionPromise = null;
143
137
  this.customParams = {};
144
- this.ga4PollInterval = null;
145
- this.ga4PollTimeout = null;
146
138
  this.sessionId = null;
147
139
  this.sessionCreated = false;
148
140
  this.config = {
149
141
  categoryId: config.categoryId,
150
- endpoint: config.endpoint || DEFAULT_ENDPOINT
142
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
143
+ ga4MeasurementId: config.ga4MeasurementId
151
144
  };
152
145
  this.cache = new TrackingCache(config.categoryId);
153
- this.startGA4CookiePolling();
146
+ this.captureGA4ClientId();
154
147
  }
155
148
  /**
156
149
  * Initialize the CallForge tracking client.
@@ -212,39 +205,21 @@ var CallForge = class _CallForge {
212
205
  }
213
206
  }
214
207
  /**
215
- * Start polling for the GA4 _ga cookie.
216
- * If found immediately, calls setParams right away.
217
- * Otherwise polls every 500ms for up to 10 seconds.
208
+ * Capture the GA4 client ID using gtag callback.
209
+ * Only runs if ga4MeasurementId is configured.
218
210
  */
219
- startGA4CookiePolling() {
220
- if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
211
+ captureGA4ClientId() {
212
+ if (!this.config.ga4MeasurementId) {
221
213
  return;
222
214
  }
223
- const clientId = getGA4ClientId();
224
- if (clientId) {
225
- this.setParams({ ga4ClientId: clientId });
215
+ if (typeof window === "undefined" || !window.gtag) {
226
216
  return;
227
217
  }
228
- this.ga4PollInterval = setInterval(() => {
229
- const clientId2 = getGA4ClientId();
230
- if (clientId2) {
231
- this.setParams({ ga4ClientId: clientId2 });
232
- this.stopGA4CookiePolling();
218
+ window.gtag("get", this.config.ga4MeasurementId, "client_id", (clientId) => {
219
+ if (clientId) {
220
+ this.setParams({ ga4ClientId: clientId });
233
221
  }
234
- }, 500);
235
- this.ga4PollTimeout = setTimeout(() => {
236
- this.stopGA4CookiePolling();
237
- }, 1e4);
238
- }
239
- stopGA4CookiePolling() {
240
- if (this.ga4PollInterval) {
241
- clearInterval(this.ga4PollInterval);
242
- this.ga4PollInterval = null;
243
- }
244
- if (this.ga4PollTimeout) {
245
- clearTimeout(this.ga4PollTimeout);
246
- this.ga4PollTimeout = null;
247
- }
222
+ });
248
223
  }
249
224
  async fetchSession() {
250
225
  const locationId = this.getLocationId();
@@ -405,6 +380,5 @@ window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.js
405
380
  // Annotate the CommonJS export names for ESM import in node:
406
381
  0 && (module.exports = {
407
382
  CallForge,
408
- getGA4ClientId,
409
383
  getPreloadSnippet
410
384
  });
package/dist/index.mjs CHANGED
@@ -105,27 +105,21 @@ var TrackingCache = class {
105
105
 
106
106
  // src/client.ts
107
107
  var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
108
- function getGA4ClientId() {
109
- if (typeof document === "undefined") return null;
110
- const match = document.cookie.match(/_ga=GA\d+\.\d+\.(.+?)(?:;|$)/);
111
- return match ? match[1] : null;
112
- }
113
108
  var FETCH_TIMEOUT_MS = 1e4;
114
109
  var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid"];
115
110
  var CallForge = class _CallForge {
116
111
  constructor(config) {
117
112
  this.sessionPromise = null;
118
113
  this.customParams = {};
119
- this.ga4PollInterval = null;
120
- this.ga4PollTimeout = null;
121
114
  this.sessionId = null;
122
115
  this.sessionCreated = false;
123
116
  this.config = {
124
117
  categoryId: config.categoryId,
125
- endpoint: config.endpoint || DEFAULT_ENDPOINT
118
+ endpoint: config.endpoint || DEFAULT_ENDPOINT,
119
+ ga4MeasurementId: config.ga4MeasurementId
126
120
  };
127
121
  this.cache = new TrackingCache(config.categoryId);
128
- this.startGA4CookiePolling();
122
+ this.captureGA4ClientId();
129
123
  }
130
124
  /**
131
125
  * Initialize the CallForge tracking client.
@@ -187,39 +181,21 @@ var CallForge = class _CallForge {
187
181
  }
188
182
  }
189
183
  /**
190
- * Start polling for the GA4 _ga cookie.
191
- * If found immediately, calls setParams right away.
192
- * Otherwise polls every 500ms for up to 10 seconds.
184
+ * Capture the GA4 client ID using gtag callback.
185
+ * Only runs if ga4MeasurementId is configured.
193
186
  */
194
- startGA4CookiePolling() {
195
- if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
187
+ captureGA4ClientId() {
188
+ if (!this.config.ga4MeasurementId) {
196
189
  return;
197
190
  }
198
- const clientId = getGA4ClientId();
199
- if (clientId) {
200
- this.setParams({ ga4ClientId: clientId });
191
+ if (typeof window === "undefined" || !window.gtag) {
201
192
  return;
202
193
  }
203
- this.ga4PollInterval = setInterval(() => {
204
- const clientId2 = getGA4ClientId();
205
- if (clientId2) {
206
- this.setParams({ ga4ClientId: clientId2 });
207
- this.stopGA4CookiePolling();
194
+ window.gtag("get", this.config.ga4MeasurementId, "client_id", (clientId) => {
195
+ if (clientId) {
196
+ this.setParams({ ga4ClientId: clientId });
208
197
  }
209
- }, 500);
210
- this.ga4PollTimeout = setTimeout(() => {
211
- this.stopGA4CookiePolling();
212
- }, 1e4);
213
- }
214
- stopGA4CookiePolling() {
215
- if (this.ga4PollInterval) {
216
- clearInterval(this.ga4PollInterval);
217
- this.ga4PollInterval = null;
218
- }
219
- if (this.ga4PollTimeout) {
220
- clearTimeout(this.ga4PollTimeout);
221
- this.ga4PollTimeout = null;
222
- }
198
+ });
223
199
  }
224
200
  async fetchSession() {
225
201
  const locationId = this.getLocationId();
@@ -379,6 +355,5 @@ window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.js
379
355
  }
380
356
  export {
381
357
  CallForge,
382
- getGA4ClientId,
383
358
  getPreloadSnippet
384
359
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callforge/tracking-client",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",