@callforge/tracking-client 0.6.0 → 0.6.2
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 +68 -99
- package/dist/index.d.mts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.js +85 -4
- package/dist/index.mjs +85 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @callforge/tracking-client
|
|
2
2
|
|
|
3
|
-
Lightweight client library for the CallForge tracking API. Handles location-aware phone number assignment with aggressive caching and preload optimization.
|
|
4
|
-
|
|
5
|
-
**v0.6.0** - Adds `createCallIntent()` helper for click/callback deterministic attribution and fixes ESM/CJS exports.
|
|
3
|
+
Lightweight client library for the CallForge tracking API. Handles location-aware phone number assignment with aggressive caching and optional preload optimization.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
@@ -26,12 +24,13 @@ const snippet = getPreloadSnippet({ categoryId: 'your-category-id' });
|
|
|
26
24
|
```
|
|
27
25
|
|
|
28
26
|
Generated HTML:
|
|
27
|
+
|
|
29
28
|
```html
|
|
30
29
|
<link rel="preconnect" href="https://tracking.callforge.io">
|
|
31
30
|
<script>/* preload script */</script>
|
|
32
31
|
```
|
|
33
32
|
|
|
34
|
-
### 2. Initialize and
|
|
33
|
+
### 2. Initialize and fetch a tracking session
|
|
35
34
|
|
|
36
35
|
```typescript
|
|
37
36
|
import { CallForge } from '@callforge/tracking-client';
|
|
@@ -41,40 +40,53 @@ const client = CallForge.init({
|
|
|
41
40
|
// endpoint: 'https://tracking-dev.callforge.io', // Optional: override for dev
|
|
42
41
|
});
|
|
43
42
|
|
|
44
|
-
// Promise style
|
|
45
43
|
const session = await client.getSession();
|
|
46
44
|
console.log(session.phoneNumber); // "+17705550000" or null
|
|
47
45
|
console.log(session.location); // { city: "Woodstock", state: "Georgia", stateCode: "GA" } or null
|
|
46
|
+
```
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
### 3. Deterministic click/callback attribution (optional)
|
|
49
|
+
|
|
50
|
+
If you initiate calls programmatically (click-to-call / callback), you can request a short-lived `callIntentToken`. CallForge will consume this once to deterministically map the call back to the web session that requested it.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// In the browser:
|
|
54
|
+
const { callIntentToken } = await client.createCallIntent();
|
|
55
|
+
|
|
56
|
+
// Send to your backend and attach to the call you initiate
|
|
57
|
+
await fetch('/api/callback', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ callIntentToken }),
|
|
53
61
|
});
|
|
54
62
|
```
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
Notes:
|
|
65
|
+
- `callIntentToken` is short-lived and single-use.
|
|
66
|
+
- Treat it as an opaque secret (do not log it).
|
|
67
|
+
|
|
68
|
+
### 4. GA4 Integration
|
|
57
69
|
|
|
58
|
-
To enable GA4 call event tracking,
|
|
70
|
+
To enable GA4 call event tracking, CallForge needs the GA4 `client_id` for the visitor (from the `_ga` cookie).
|
|
71
|
+
Optionally provide your GA4 Measurement ID to improve client ID capture reliability when Google Analytics loads late.
|
|
59
72
|
|
|
60
73
|
```typescript
|
|
61
74
|
const client = CallForge.init({
|
|
62
75
|
categoryId: 'your-category-id',
|
|
63
|
-
ga4MeasurementId: 'G-XXXXXXXXXX',
|
|
76
|
+
ga4MeasurementId: 'G-XXXXXXXXXX', // Recommended
|
|
64
77
|
});
|
|
65
78
|
```
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
1. Google Analytics 4 must be installed on your site (
|
|
69
|
-
2.
|
|
70
|
-
3. Configure GA4 credentials in CallForge dashboard (Categories → Edit → GA4 tab)
|
|
80
|
+
Requirements:
|
|
81
|
+
1. Google Analytics 4 must be installed on your site and allowed to set the `_ga` cookie (gtag.js or GTM).
|
|
82
|
+
2. Configure GA4 credentials in CallForge dashboard (Measurement ID + API secret).
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
84
|
+
How it works:
|
|
85
|
+
- Extracts the GA4 `client_id` from the `_ga` cookie and sends it to CallForge as `ga4ClientId`.
|
|
86
|
+
- If `ga4MeasurementId` is configured and `gtag` is available, also uses `gtag('get', measurementId, 'client_id', ...)` (with a short retry window).
|
|
87
|
+
- If `ga4ClientId` becomes available after a session is created, the client will refresh once to sync it to CallForge.
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
Manual override:
|
|
78
90
|
|
|
79
91
|
```typescript
|
|
80
92
|
client.setParams({
|
|
@@ -82,7 +94,7 @@ client.setParams({
|
|
|
82
94
|
});
|
|
83
95
|
```
|
|
84
96
|
|
|
85
|
-
###
|
|
97
|
+
### 5. Track conversion parameters (optional)
|
|
86
98
|
|
|
87
99
|
The client automatically captures ad platform click IDs from the URL:
|
|
88
100
|
|
|
@@ -95,14 +107,13 @@ The client automatically captures ad platform click IDs from the URL:
|
|
|
95
107
|
You can also add custom parameters:
|
|
96
108
|
|
|
97
109
|
```typescript
|
|
98
|
-
// Add custom tracking parameters
|
|
99
110
|
client.setParams({
|
|
100
111
|
customerId: '456',
|
|
101
112
|
landingPage: 'pricing',
|
|
102
113
|
});
|
|
103
114
|
|
|
104
115
|
// Parameters are automatically sent with every API request
|
|
105
|
-
|
|
116
|
+
await client.getSession();
|
|
106
117
|
```
|
|
107
118
|
|
|
108
119
|
## API Reference
|
|
@@ -115,18 +126,19 @@ Initialize the tracking client.
|
|
|
115
126
|
interface CallForgeConfig {
|
|
116
127
|
categoryId: string; // Required - which number pool to use
|
|
117
128
|
endpoint?: string; // Optional - defaults to 'https://tracking.callforge.io'
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
siteKey?: string; // Optional - cache partition key (defaults to window.location.hostname)
|
|
130
|
+
ga4MeasurementId?: string; // Optional - GA4 Measurement ID (e.g., 'G-XXXXXXXXXX')
|
|
120
131
|
}
|
|
121
132
|
```
|
|
122
133
|
|
|
123
134
|
### `client.getSession()`
|
|
124
135
|
|
|
125
|
-
Get tracking session data. Returns cached data if valid, otherwise fetches from API.
|
|
136
|
+
Get tracking session data. Returns cached data if valid, otherwise fetches from the API.
|
|
126
137
|
|
|
127
138
|
```typescript
|
|
128
139
|
interface TrackingSession {
|
|
129
|
-
|
|
140
|
+
sessionToken: string; // Signed, opaque token used to refresh the session
|
|
141
|
+
leaseId: string | null; // Deterministic assignment lease ID (when available)
|
|
130
142
|
phoneNumber: string | null;
|
|
131
143
|
location: {
|
|
132
144
|
city: string;
|
|
@@ -136,11 +148,20 @@ interface TrackingSession {
|
|
|
136
148
|
}
|
|
137
149
|
```
|
|
138
150
|
|
|
139
|
-
|
|
140
|
-
- Returns
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
- Throws on network errors or API errors
|
|
151
|
+
Behavior:
|
|
152
|
+
- Returns cached data if valid.
|
|
153
|
+
- Fetches fresh data when cache is missing/expired.
|
|
154
|
+
- If `loc_physical_ms` is present in the URL, cached sessions are only reused when it matches the cached `locId`.
|
|
155
|
+
- Throws on network errors or API errors.
|
|
156
|
+
|
|
157
|
+
### `client.createCallIntent()`
|
|
158
|
+
|
|
159
|
+
Create a short-lived call intent token for click/callback deterministic attribution.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
const intent = await client.createCallIntent();
|
|
163
|
+
console.log(intent.callIntentToken);
|
|
164
|
+
```
|
|
144
165
|
|
|
145
166
|
### `client.onReady(callback)`
|
|
146
167
|
|
|
@@ -164,11 +185,10 @@ client.setParams({
|
|
|
164
185
|
});
|
|
165
186
|
```
|
|
166
187
|
|
|
167
|
-
|
|
168
|
-
- Merges with existing params (later calls override earlier values)
|
|
169
|
-
- Parameters are sent with every `getSession()` API request
|
|
170
|
-
- Persisted in localStorage alongside session data
|
|
171
|
-
- Auto-captured params (gclid, etc.) are included automatically
|
|
188
|
+
Behavior:
|
|
189
|
+
- Merges with existing params (later calls override earlier values).
|
|
190
|
+
- Parameters are sent with every `getSession()` API request.
|
|
191
|
+
- Persisted in localStorage alongside session data.
|
|
172
192
|
|
|
173
193
|
### `getPreloadSnippet(config)`
|
|
174
194
|
|
|
@@ -180,6 +200,7 @@ import { getPreloadSnippet } from '@callforge/tracking-client';
|
|
|
180
200
|
const html = getPreloadSnippet({
|
|
181
201
|
categoryId: 'your-category-id',
|
|
182
202
|
endpoint: 'https://tracking.callforge.io', // Optional
|
|
203
|
+
siteKey: 'example.com', // Optional
|
|
183
204
|
});
|
|
184
205
|
```
|
|
185
206
|
|
|
@@ -194,32 +215,22 @@ The client automatically extracts these parameters from the URL:
|
|
|
194
215
|
| `gclid` | Google Ads Click ID |
|
|
195
216
|
| `gbraid` | Google app-to-web (iOS 14+) |
|
|
196
217
|
| `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.
|
|
201
|
-
|
|
202
|
-
### Persistence
|
|
203
|
-
|
|
204
|
-
- Parameters are stored in localStorage alongside the session
|
|
205
|
-
- On subsequent visits, cached params are used (URL may no longer have them)
|
|
206
|
-
- Custom params via `setParams()` merge with auto-captured params
|
|
207
|
-
- Later values override earlier ones (custom > cached > auto-captured)
|
|
218
|
+
| `msclkid` | Microsoft/Bing Ads Click ID |
|
|
219
|
+
| `fbclid` | Facebook/Meta Ads Click ID |
|
|
208
220
|
|
|
209
221
|
### API Request Format
|
|
210
222
|
|
|
211
|
-
Parameters are sent as sorted query string for cache consistency:
|
|
223
|
+
Parameters are sent as a sorted query string for cache consistency:
|
|
212
224
|
|
|
213
225
|
```
|
|
214
|
-
/v1/tracking/session?categoryId=cat-123&fbclid=456&gclid=abc&loc_physical_ms=1014221
|
|
226
|
+
/v1/tracking/session?categoryId=cat-123&fbclid=456&gclid=abc&loc_physical_ms=1014221&sessionToken=...
|
|
215
227
|
```
|
|
216
228
|
|
|
217
229
|
## Caching Behavior
|
|
218
230
|
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
- **Invalidation:** Cache clears when location changes
|
|
231
|
+
- Cache key: `cf_tracking_v1_<siteKey>_<categoryId>`
|
|
232
|
+
- TTL: controlled by the server `expiresAt` response (currently 30 minutes)
|
|
233
|
+
- Storage: localStorage (falls back to memory if unavailable)
|
|
223
234
|
|
|
224
235
|
## Error Handling
|
|
225
236
|
|
|
@@ -229,7 +240,7 @@ try {
|
|
|
229
240
|
if (session.phoneNumber) {
|
|
230
241
|
// Use phone number
|
|
231
242
|
} else {
|
|
232
|
-
// No phone number available
|
|
243
|
+
// No phone number available
|
|
233
244
|
}
|
|
234
245
|
} catch (err) {
|
|
235
246
|
// Network error or API error
|
|
@@ -248,52 +259,10 @@ import type {
|
|
|
248
259
|
TrackingLocation,
|
|
249
260
|
TrackingParams,
|
|
250
261
|
ReadyCallback,
|
|
262
|
+
CallIntentResponse,
|
|
251
263
|
} from '@callforge/tracking-client';
|
|
252
264
|
```
|
|
253
265
|
|
|
254
|
-
### TrackingParams
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
interface TrackingParams {
|
|
258
|
-
gclid?: string; // Google Ads Click ID
|
|
259
|
-
gbraid?: string; // Google app-to-web (iOS 14+)
|
|
260
|
-
wbraid?: string; // Google web-to-app
|
|
261
|
-
msclkid?: string; // Microsoft/Bing Ads
|
|
262
|
-
fbclid?: string; // Facebook/Meta Ads
|
|
263
|
-
ga4ClientId?: string; // Google Analytics 4 Client ID (auto-captured via gtag callback)
|
|
264
|
-
[key: string]: string | undefined; // Custom params
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
## GA4 Events
|
|
269
|
-
|
|
270
|
-
When GA4 is configured for a category, CallForge sends these events to Google Analytics 4 via the Measurement Protocol:
|
|
271
|
-
|
|
272
|
-
| Event Name | When Sent | Description |
|
|
273
|
-
|------------|-----------|-------------|
|
|
274
|
-
| `phone_call` | Call initiated | A phone call was placed to the tracking number |
|
|
275
|
-
| `call_conversion` | Call qualified | The call met conversion criteria (e.g., duration threshold) |
|
|
276
|
-
| `call_conversion_billable` | Call billable | The call was both qualified AND billable |
|
|
277
|
-
|
|
278
|
-
**Event Parameters:**
|
|
279
|
-
|
|
280
|
-
All events include:
|
|
281
|
-
- `session_id` - CallForge session ID
|
|
282
|
-
- `phone_number` - Tracking number dialed
|
|
283
|
-
- `category` - Category slug
|
|
284
|
-
|
|
285
|
-
`call_conversion` and `call_conversion_billable` also include:
|
|
286
|
-
- `call_duration` - Call duration in seconds
|
|
287
|
-
- `buyer` - Buyer the call was routed to (if applicable)
|
|
288
|
-
|
|
289
|
-
**Setup:**
|
|
290
|
-
1. In Google Analytics 4, go to Admin → Data Streams → your web stream
|
|
291
|
-
2. Copy the **Measurement ID** (e.g., `G-XXXXXXXXXX`)
|
|
292
|
-
3. Go to Admin → Data Streams → Measurement Protocol API secrets → Create
|
|
293
|
-
4. Copy the **API Secret**
|
|
294
|
-
5. In CallForge dashboard: Categories → Edit category → GA4 tab
|
|
295
|
-
6. Enable GA4, paste Measurement ID and API Secret, save
|
|
296
|
-
|
|
297
266
|
## Environment URLs
|
|
298
267
|
|
|
299
268
|
| Environment | Endpoint |
|
package/dist/index.d.mts
CHANGED
|
@@ -8,7 +8,7 @@ interface CallForgeConfig {
|
|
|
8
8
|
endpoint?: string;
|
|
9
9
|
/** Optional - explicit site key for cache partitioning. Defaults to window.location.hostname */
|
|
10
10
|
siteKey?: string;
|
|
11
|
-
/** Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX"). Enables gtag
|
|
11
|
+
/** Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX"). Enables gtag-based client_id capture for late-loading GA. */
|
|
12
12
|
ga4MeasurementId?: string;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -121,10 +121,20 @@ declare class CallForge {
|
|
|
121
121
|
*/
|
|
122
122
|
getQueuedParams(): TrackingParams;
|
|
123
123
|
/**
|
|
124
|
-
* Capture the GA4 client ID
|
|
125
|
-
*
|
|
124
|
+
* Capture the GA4 client ID.
|
|
125
|
+
*
|
|
126
|
+
* Prefer `_ga` cookie parsing (works even when gtag is not loaded yet).
|
|
127
|
+
* If ga4MeasurementId is configured and gtag is available, use gtag as a fallback.
|
|
126
128
|
*/
|
|
127
129
|
private captureGA4ClientId;
|
|
130
|
+
private startGA4ClientIdPolling;
|
|
131
|
+
/**
|
|
132
|
+
* Extract GA4 client_id from the `_ga` cookie.
|
|
133
|
+
*
|
|
134
|
+
* Example cookie value: `GA1.1.1234567890.1234567890`
|
|
135
|
+
* Returns: `1234567890.1234567890`
|
|
136
|
+
*/
|
|
137
|
+
private getGA4ClientIdFromCookie;
|
|
128
138
|
private fetchSession;
|
|
129
139
|
private getLocationId;
|
|
130
140
|
private getAutoParams;
|
|
@@ -132,6 +142,7 @@ declare class CallForge {
|
|
|
132
142
|
private saveToCache;
|
|
133
143
|
private formatSession;
|
|
134
144
|
private formatApiResponse;
|
|
145
|
+
private syncGA4ClientIdIfNeeded;
|
|
135
146
|
private buildUrl;
|
|
136
147
|
}
|
|
137
148
|
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ interface CallForgeConfig {
|
|
|
8
8
|
endpoint?: string;
|
|
9
9
|
/** Optional - explicit site key for cache partitioning. Defaults to window.location.hostname */
|
|
10
10
|
siteKey?: string;
|
|
11
|
-
/** Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX"). Enables gtag
|
|
11
|
+
/** Optional - GA4 Measurement ID (e.g., "G-XXXXXXXXXX"). Enables gtag-based client_id capture for late-loading GA. */
|
|
12
12
|
ga4MeasurementId?: string;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -121,10 +121,20 @@ declare class CallForge {
|
|
|
121
121
|
*/
|
|
122
122
|
getQueuedParams(): TrackingParams;
|
|
123
123
|
/**
|
|
124
|
-
* Capture the GA4 client ID
|
|
125
|
-
*
|
|
124
|
+
* Capture the GA4 client ID.
|
|
125
|
+
*
|
|
126
|
+
* Prefer `_ga` cookie parsing (works even when gtag is not loaded yet).
|
|
127
|
+
* If ga4MeasurementId is configured and gtag is available, use gtag as a fallback.
|
|
126
128
|
*/
|
|
127
129
|
private captureGA4ClientId;
|
|
130
|
+
private startGA4ClientIdPolling;
|
|
131
|
+
/**
|
|
132
|
+
* Extract GA4 client_id from the `_ga` cookie.
|
|
133
|
+
*
|
|
134
|
+
* Example cookie value: `GA1.1.1234567890.1234567890`
|
|
135
|
+
* Returns: `1234567890.1234567890`
|
|
136
|
+
*/
|
|
137
|
+
private getGA4ClientIdFromCookie;
|
|
128
138
|
private fetchSession;
|
|
129
139
|
private getLocationId;
|
|
130
140
|
private getAutoParams;
|
|
@@ -132,6 +142,7 @@ declare class CallForge {
|
|
|
132
142
|
private saveToCache;
|
|
133
143
|
private formatSession;
|
|
134
144
|
private formatApiResponse;
|
|
145
|
+
private syncGA4ClientIdIfNeeded;
|
|
135
146
|
private buildUrl;
|
|
136
147
|
}
|
|
137
148
|
|
package/dist/index.js
CHANGED
|
@@ -155,6 +155,7 @@ var CallForge = class _CallForge {
|
|
|
155
155
|
};
|
|
156
156
|
this.cache = new TrackingCache(config.categoryId, config.siteKey);
|
|
157
157
|
this.captureGA4ClientId();
|
|
158
|
+
this.startGA4ClientIdPolling();
|
|
158
159
|
}
|
|
159
160
|
/**
|
|
160
161
|
* Initialize the CallForge tracking client.
|
|
@@ -213,6 +214,20 @@ var CallForge = class _CallForge {
|
|
|
213
214
|
*/
|
|
214
215
|
async setParams(params) {
|
|
215
216
|
this.customParams = __spreadValues(__spreadValues({}, this.customParams), params);
|
|
217
|
+
if (params.ga4ClientId) {
|
|
218
|
+
const sync = async () => {
|
|
219
|
+
try {
|
|
220
|
+
await this.syncGA4ClientIdIfNeeded();
|
|
221
|
+
} catch (e) {
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
if (this.sessionPromise) {
|
|
225
|
+
void this.sessionPromise.then(sync).catch(() => {
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
void sync();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
216
231
|
}
|
|
217
232
|
/**
|
|
218
233
|
* Get the currently queued custom params.
|
|
@@ -221,25 +236,67 @@ var CallForge = class _CallForge {
|
|
|
221
236
|
return __spreadValues({}, this.customParams);
|
|
222
237
|
}
|
|
223
238
|
/**
|
|
224
|
-
* Capture the GA4 client ID
|
|
225
|
-
*
|
|
239
|
+
* Capture the GA4 client ID.
|
|
240
|
+
*
|
|
241
|
+
* Prefer `_ga` cookie parsing (works even when gtag is not loaded yet).
|
|
242
|
+
* If ga4MeasurementId is configured and gtag is available, use gtag as a fallback.
|
|
226
243
|
*/
|
|
227
244
|
captureGA4ClientId() {
|
|
228
|
-
if (
|
|
245
|
+
if (this.customParams.ga4ClientId) {
|
|
229
246
|
return;
|
|
230
247
|
}
|
|
231
|
-
|
|
248
|
+
const fromCookie = this.getGA4ClientIdFromCookie();
|
|
249
|
+
if (fromCookie) {
|
|
250
|
+
this.setParams({ ga4ClientId: fromCookie });
|
|
232
251
|
return;
|
|
233
252
|
}
|
|
253
|
+
if (!this.config.ga4MeasurementId) return;
|
|
254
|
+
if (typeof window === "undefined" || !window.gtag) return;
|
|
234
255
|
window.gtag("get", this.config.ga4MeasurementId, "client_id", (clientId) => {
|
|
235
256
|
if (clientId) {
|
|
236
257
|
this.setParams({ ga4ClientId: clientId });
|
|
237
258
|
}
|
|
238
259
|
});
|
|
239
260
|
}
|
|
261
|
+
startGA4ClientIdPolling() {
|
|
262
|
+
if (!this.config.ga4MeasurementId) return;
|
|
263
|
+
if (typeof window === "undefined") return;
|
|
264
|
+
if (this.customParams.ga4ClientId) return;
|
|
265
|
+
const MAX_ATTEMPTS = 20;
|
|
266
|
+
const INTERVAL_MS = 250;
|
|
267
|
+
let attempts = 0;
|
|
268
|
+
const poll = () => {
|
|
269
|
+
if (this.customParams.ga4ClientId) return;
|
|
270
|
+
this.captureGA4ClientId();
|
|
271
|
+
attempts += 1;
|
|
272
|
+
if (this.customParams.ga4ClientId) return;
|
|
273
|
+
if (attempts >= MAX_ATTEMPTS) return;
|
|
274
|
+
window.setTimeout(poll, INTERVAL_MS);
|
|
275
|
+
};
|
|
276
|
+
window.setTimeout(poll, INTERVAL_MS);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Extract GA4 client_id from the `_ga` cookie.
|
|
280
|
+
*
|
|
281
|
+
* Example cookie value: `GA1.1.1234567890.1234567890`
|
|
282
|
+
* Returns: `1234567890.1234567890`
|
|
283
|
+
*/
|
|
284
|
+
getGA4ClientIdFromCookie() {
|
|
285
|
+
if (typeof document === "undefined") return null;
|
|
286
|
+
const match = document.cookie.match(/(?:^|;\s*)_ga=([^;]+)/);
|
|
287
|
+
if (!match) return null;
|
|
288
|
+
const cookieValue = decodeURIComponent(match[1] || "");
|
|
289
|
+
const parts = cookieValue.split(".");
|
|
290
|
+
if (parts.length < 2) return null;
|
|
291
|
+
const partA = parts[parts.length - 2];
|
|
292
|
+
const partB = parts[parts.length - 1];
|
|
293
|
+
if (!/^\d+$/.test(partA) || !/^\d+$/.test(partB)) return null;
|
|
294
|
+
return `${partA}.${partB}`;
|
|
295
|
+
}
|
|
240
296
|
async fetchSession() {
|
|
241
297
|
var _a;
|
|
242
298
|
const locationId = this.getLocationId();
|
|
299
|
+
this.captureGA4ClientId();
|
|
243
300
|
if (typeof window !== "undefined" && window.__cfTracking) {
|
|
244
301
|
try {
|
|
245
302
|
const data2 = await window.__cfTracking;
|
|
@@ -254,6 +311,13 @@ var CallForge = class _CallForge {
|
|
|
254
311
|
}
|
|
255
312
|
const cached = this.cache.get(locationId);
|
|
256
313
|
if (cached) {
|
|
314
|
+
const autoParams2 = this.getAutoParams();
|
|
315
|
+
const params2 = __spreadValues(__spreadValues(__spreadValues({}, autoParams2), cached.params), this.customParams);
|
|
316
|
+
if (params2.ga4ClientId && cached.params.ga4ClientId !== params2.ga4ClientId) {
|
|
317
|
+
const data2 = await this.fetchFromApi(locationId, cached.sessionToken, params2);
|
|
318
|
+
this.saveToCache(locationId, data2, params2);
|
|
319
|
+
return this.formatApiResponse(data2);
|
|
320
|
+
}
|
|
257
321
|
return this.formatSession(cached);
|
|
258
322
|
}
|
|
259
323
|
const autoParams = this.getAutoParams();
|
|
@@ -327,6 +391,21 @@ var CallForge = class _CallForge {
|
|
|
327
391
|
location: data.location
|
|
328
392
|
};
|
|
329
393
|
}
|
|
394
|
+
async syncGA4ClientIdIfNeeded() {
|
|
395
|
+
const ga4ClientId = this.customParams.ga4ClientId;
|
|
396
|
+
if (!ga4ClientId) return;
|
|
397
|
+
const locationId = this.getLocationId();
|
|
398
|
+
const sessionToken = this.cache.getSessionToken(locationId);
|
|
399
|
+
if (!sessionToken) return;
|
|
400
|
+
const autoParams = this.getAutoParams();
|
|
401
|
+
const cachedParams = this.cache.getParams();
|
|
402
|
+
const params = __spreadValues(__spreadValues(__spreadValues({}, autoParams), cachedParams), this.customParams);
|
|
403
|
+
if (cachedParams.ga4ClientId === params.ga4ClientId) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const data = await this.fetchFromApi(locationId, sessionToken, params);
|
|
407
|
+
this.saveToCache(locationId, data, params);
|
|
408
|
+
}
|
|
330
409
|
buildUrl(locationId, sessionToken, params) {
|
|
331
410
|
const { categoryId, endpoint } = this.config;
|
|
332
411
|
const queryParams = {
|
|
@@ -371,6 +450,8 @@ var u=new URLSearchParams(location.search);
|
|
|
371
450
|
var loc=u.get('loc_physical_ms');
|
|
372
451
|
var ap=['gclid','gbraid','wbraid','msclkid','fbclid','gad_campaignid','gad_source'];
|
|
373
452
|
var p={};
|
|
453
|
+
var m=document.cookie.match(/(?:^|;\\s*)_ga=([^;]+)/);
|
|
454
|
+
if(m){try{var g=decodeURIComponent(m[1]||'');var s=g.split('.');if(s.length>=2){var a=s[s.length-2],b=s[s.length-1];if(/^\\d+$/.test(a)&&/^\\d+$/.test(b))p.ga4ClientId=a+'.'+b}}catch(e){}}
|
|
374
455
|
for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
|
|
375
456
|
var site='${config.siteKey || ""}'||location.hostname||'unknown-site';
|
|
376
457
|
var key='cf_tracking_v1_'+site+'_${categoryId}';
|
package/dist/index.mjs
CHANGED
|
@@ -131,6 +131,7 @@ var CallForge = class _CallForge {
|
|
|
131
131
|
};
|
|
132
132
|
this.cache = new TrackingCache(config.categoryId, config.siteKey);
|
|
133
133
|
this.captureGA4ClientId();
|
|
134
|
+
this.startGA4ClientIdPolling();
|
|
134
135
|
}
|
|
135
136
|
/**
|
|
136
137
|
* Initialize the CallForge tracking client.
|
|
@@ -189,6 +190,20 @@ var CallForge = class _CallForge {
|
|
|
189
190
|
*/
|
|
190
191
|
async setParams(params) {
|
|
191
192
|
this.customParams = __spreadValues(__spreadValues({}, this.customParams), params);
|
|
193
|
+
if (params.ga4ClientId) {
|
|
194
|
+
const sync = async () => {
|
|
195
|
+
try {
|
|
196
|
+
await this.syncGA4ClientIdIfNeeded();
|
|
197
|
+
} catch (e) {
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
if (this.sessionPromise) {
|
|
201
|
+
void this.sessionPromise.then(sync).catch(() => {
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
void sync();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
192
207
|
}
|
|
193
208
|
/**
|
|
194
209
|
* Get the currently queued custom params.
|
|
@@ -197,25 +212,67 @@ var CallForge = class _CallForge {
|
|
|
197
212
|
return __spreadValues({}, this.customParams);
|
|
198
213
|
}
|
|
199
214
|
/**
|
|
200
|
-
* Capture the GA4 client ID
|
|
201
|
-
*
|
|
215
|
+
* Capture the GA4 client ID.
|
|
216
|
+
*
|
|
217
|
+
* Prefer `_ga` cookie parsing (works even when gtag is not loaded yet).
|
|
218
|
+
* If ga4MeasurementId is configured and gtag is available, use gtag as a fallback.
|
|
202
219
|
*/
|
|
203
220
|
captureGA4ClientId() {
|
|
204
|
-
if (
|
|
221
|
+
if (this.customParams.ga4ClientId) {
|
|
205
222
|
return;
|
|
206
223
|
}
|
|
207
|
-
|
|
224
|
+
const fromCookie = this.getGA4ClientIdFromCookie();
|
|
225
|
+
if (fromCookie) {
|
|
226
|
+
this.setParams({ ga4ClientId: fromCookie });
|
|
208
227
|
return;
|
|
209
228
|
}
|
|
229
|
+
if (!this.config.ga4MeasurementId) return;
|
|
230
|
+
if (typeof window === "undefined" || !window.gtag) return;
|
|
210
231
|
window.gtag("get", this.config.ga4MeasurementId, "client_id", (clientId) => {
|
|
211
232
|
if (clientId) {
|
|
212
233
|
this.setParams({ ga4ClientId: clientId });
|
|
213
234
|
}
|
|
214
235
|
});
|
|
215
236
|
}
|
|
237
|
+
startGA4ClientIdPolling() {
|
|
238
|
+
if (!this.config.ga4MeasurementId) return;
|
|
239
|
+
if (typeof window === "undefined") return;
|
|
240
|
+
if (this.customParams.ga4ClientId) return;
|
|
241
|
+
const MAX_ATTEMPTS = 20;
|
|
242
|
+
const INTERVAL_MS = 250;
|
|
243
|
+
let attempts = 0;
|
|
244
|
+
const poll = () => {
|
|
245
|
+
if (this.customParams.ga4ClientId) return;
|
|
246
|
+
this.captureGA4ClientId();
|
|
247
|
+
attempts += 1;
|
|
248
|
+
if (this.customParams.ga4ClientId) return;
|
|
249
|
+
if (attempts >= MAX_ATTEMPTS) return;
|
|
250
|
+
window.setTimeout(poll, INTERVAL_MS);
|
|
251
|
+
};
|
|
252
|
+
window.setTimeout(poll, INTERVAL_MS);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Extract GA4 client_id from the `_ga` cookie.
|
|
256
|
+
*
|
|
257
|
+
* Example cookie value: `GA1.1.1234567890.1234567890`
|
|
258
|
+
* Returns: `1234567890.1234567890`
|
|
259
|
+
*/
|
|
260
|
+
getGA4ClientIdFromCookie() {
|
|
261
|
+
if (typeof document === "undefined") return null;
|
|
262
|
+
const match = document.cookie.match(/(?:^|;\s*)_ga=([^;]+)/);
|
|
263
|
+
if (!match) return null;
|
|
264
|
+
const cookieValue = decodeURIComponent(match[1] || "");
|
|
265
|
+
const parts = cookieValue.split(".");
|
|
266
|
+
if (parts.length < 2) return null;
|
|
267
|
+
const partA = parts[parts.length - 2];
|
|
268
|
+
const partB = parts[parts.length - 1];
|
|
269
|
+
if (!/^\d+$/.test(partA) || !/^\d+$/.test(partB)) return null;
|
|
270
|
+
return `${partA}.${partB}`;
|
|
271
|
+
}
|
|
216
272
|
async fetchSession() {
|
|
217
273
|
var _a;
|
|
218
274
|
const locationId = this.getLocationId();
|
|
275
|
+
this.captureGA4ClientId();
|
|
219
276
|
if (typeof window !== "undefined" && window.__cfTracking) {
|
|
220
277
|
try {
|
|
221
278
|
const data2 = await window.__cfTracking;
|
|
@@ -230,6 +287,13 @@ var CallForge = class _CallForge {
|
|
|
230
287
|
}
|
|
231
288
|
const cached = this.cache.get(locationId);
|
|
232
289
|
if (cached) {
|
|
290
|
+
const autoParams2 = this.getAutoParams();
|
|
291
|
+
const params2 = __spreadValues(__spreadValues(__spreadValues({}, autoParams2), cached.params), this.customParams);
|
|
292
|
+
if (params2.ga4ClientId && cached.params.ga4ClientId !== params2.ga4ClientId) {
|
|
293
|
+
const data2 = await this.fetchFromApi(locationId, cached.sessionToken, params2);
|
|
294
|
+
this.saveToCache(locationId, data2, params2);
|
|
295
|
+
return this.formatApiResponse(data2);
|
|
296
|
+
}
|
|
233
297
|
return this.formatSession(cached);
|
|
234
298
|
}
|
|
235
299
|
const autoParams = this.getAutoParams();
|
|
@@ -303,6 +367,21 @@ var CallForge = class _CallForge {
|
|
|
303
367
|
location: data.location
|
|
304
368
|
};
|
|
305
369
|
}
|
|
370
|
+
async syncGA4ClientIdIfNeeded() {
|
|
371
|
+
const ga4ClientId = this.customParams.ga4ClientId;
|
|
372
|
+
if (!ga4ClientId) return;
|
|
373
|
+
const locationId = this.getLocationId();
|
|
374
|
+
const sessionToken = this.cache.getSessionToken(locationId);
|
|
375
|
+
if (!sessionToken) return;
|
|
376
|
+
const autoParams = this.getAutoParams();
|
|
377
|
+
const cachedParams = this.cache.getParams();
|
|
378
|
+
const params = __spreadValues(__spreadValues(__spreadValues({}, autoParams), cachedParams), this.customParams);
|
|
379
|
+
if (cachedParams.ga4ClientId === params.ga4ClientId) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const data = await this.fetchFromApi(locationId, sessionToken, params);
|
|
383
|
+
this.saveToCache(locationId, data, params);
|
|
384
|
+
}
|
|
306
385
|
buildUrl(locationId, sessionToken, params) {
|
|
307
386
|
const { categoryId, endpoint } = this.config;
|
|
308
387
|
const queryParams = {
|
|
@@ -347,6 +426,8 @@ var u=new URLSearchParams(location.search);
|
|
|
347
426
|
var loc=u.get('loc_physical_ms');
|
|
348
427
|
var ap=['gclid','gbraid','wbraid','msclkid','fbclid','gad_campaignid','gad_source'];
|
|
349
428
|
var p={};
|
|
429
|
+
var m=document.cookie.match(/(?:^|;\\s*)_ga=([^;]+)/);
|
|
430
|
+
if(m){try{var g=decodeURIComponent(m[1]||'');var s=g.split('.');if(s.length>=2){var a=s[s.length-2],b=s[s.length-1];if(/^\\d+$/.test(a)&&/^\\d+$/.test(b))p.ga4ClientId=a+'.'+b}}catch(e){}}
|
|
350
431
|
for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
|
|
351
432
|
var site='${config.siteKey || ""}'||location.hostname||'unknown-site';
|
|
352
433
|
var key='cf_tracking_v1_'+site+'_${categoryId}';
|