@callforge/tracking-client 0.6.3 → 0.7.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 +63 -11
- package/dist/index.d.mts +43 -6
- package/dist/index.d.ts +43 -6
- package/dist/index.js +182 -6
- package/dist/index.mjs +182 -6
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Generated HTML:
|
|
|
30
30
|
<script>/* preload script */</script>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
### 2. Initialize and
|
|
33
|
+
### 2. Initialize and start session + location requests
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
36
|
import { CallForge } from '@callforge/tracking-client';
|
|
@@ -40,9 +40,19 @@ const client = CallForge.init({
|
|
|
40
40
|
// endpoint: 'https://tracking-dev.callforge.io', // Optional: override for dev
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
const session =
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const { session, location } = client.getSessionAsync();
|
|
44
|
+
|
|
45
|
+
// Location is delivered independently (often faster than phone number assignment)
|
|
46
|
+
console.log(await location);
|
|
47
|
+
// {
|
|
48
|
+
// city: "Woodstock",
|
|
49
|
+
// state: "Georgia",
|
|
50
|
+
// stateCode: "GA",
|
|
51
|
+
// zipOptions: ["30188", "30189", "30066", ...] // may be []
|
|
52
|
+
// } or null
|
|
53
|
+
|
|
54
|
+
// Phone session data (deterministic token + phone number)
|
|
55
|
+
console.log(await session); // { sessionToken, leaseId, phoneNumber }
|
|
46
56
|
```
|
|
47
57
|
|
|
48
58
|
### 3. Deterministic click/callback attribution (optional)
|
|
@@ -134,18 +144,13 @@ interface CallForgeConfig {
|
|
|
134
144
|
|
|
135
145
|
### `client.getSession()`
|
|
136
146
|
|
|
137
|
-
Get tracking session data. Returns cached data if valid, otherwise fetches from the API.
|
|
147
|
+
Get tracking session data (phone number + deterministic session token). Returns cached data if valid, otherwise fetches from the API.
|
|
138
148
|
|
|
139
149
|
```typescript
|
|
140
150
|
interface TrackingSession {
|
|
141
151
|
sessionToken: string; // Signed, opaque token used to refresh the session
|
|
142
152
|
leaseId: string | null; // Deterministic assignment lease ID (when available)
|
|
143
153
|
phoneNumber: string | null;
|
|
144
|
-
location: {
|
|
145
|
-
city: string;
|
|
146
|
-
state: string; // Full name: "Georgia"
|
|
147
|
-
stateCode: string; // Abbreviation: "GA"
|
|
148
|
-
} | null;
|
|
149
154
|
}
|
|
150
155
|
```
|
|
151
156
|
|
|
@@ -155,6 +160,41 @@ Behavior:
|
|
|
155
160
|
- If `loc_physical_ms` is present in the URL, cached sessions are only reused when it matches the cached `locId`.
|
|
156
161
|
- Throws on network errors or API errors.
|
|
157
162
|
|
|
163
|
+
### `client.getLocation()`
|
|
164
|
+
|
|
165
|
+
Get location data only. Returns cached data if valid, otherwise fetches from the API.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const location = await client.getLocation();
|
|
169
|
+
// { city, state, stateCode, zipOptions } or null
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface TrackingLocation {
|
|
174
|
+
city: string;
|
|
175
|
+
state: string;
|
|
176
|
+
stateCode: string;
|
|
177
|
+
zipOptions?: string[]; // ordered by proximity, may be []
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `client.getSessionAsync()`
|
|
182
|
+
|
|
183
|
+
Kick off both requests and use each as soon as it resolves.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const { session, location } = client.getSessionAsync();
|
|
187
|
+
|
|
188
|
+
location.then((loc) => {
|
|
189
|
+
// show city/state ASAP
|
|
190
|
+
// optionally render loc?.zipOptions in a ZIP picker
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
session.then((sess) => {
|
|
194
|
+
// show phone number when ready
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
158
198
|
### `client.createCallIntent()`
|
|
159
199
|
|
|
160
200
|
Create a short-lived call intent token for click/callback deterministic attribution.
|
|
@@ -174,6 +214,16 @@ client.onReady((session) => {
|
|
|
174
214
|
});
|
|
175
215
|
```
|
|
176
216
|
|
|
217
|
+
### `client.onLocationReady(callback)`
|
|
218
|
+
|
|
219
|
+
Subscribe to location ready event. Callback is called once location data is available.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
client.onLocationReady((location) => {
|
|
223
|
+
// location is { city, state, stateCode, zipOptions } or null
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
177
227
|
### `client.setParams(params)`
|
|
178
228
|
|
|
179
229
|
Set custom tracking parameters for conversion attribution.
|
|
@@ -230,7 +280,8 @@ Parameters are sent as a sorted query string for cache consistency:
|
|
|
230
280
|
|
|
231
281
|
## Caching Behavior
|
|
232
282
|
|
|
233
|
-
-
|
|
283
|
+
- Session cache key: `cf_tracking_v1_<siteKey>_<categoryId>`
|
|
284
|
+
- Location cache key: `cf_location_v1_<siteKey>`
|
|
234
285
|
- TTL: controlled by the server `expiresAt` response (currently 30 minutes)
|
|
235
286
|
- Storage: localStorage (falls back to memory if unavailable)
|
|
236
287
|
|
|
@@ -261,6 +312,7 @@ import type {
|
|
|
261
312
|
TrackingLocation,
|
|
262
313
|
TrackingParams,
|
|
263
314
|
ReadyCallback,
|
|
315
|
+
LocationReadyCallback,
|
|
264
316
|
CallIntentResponse,
|
|
265
317
|
} from '@callforge/tracking-client';
|
|
266
318
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -21,7 +21,10 @@ interface TrackingLocation {
|
|
|
21
21
|
state: string;
|
|
22
22
|
/** State abbreviation (e.g., "GA") */
|
|
23
23
|
stateCode: string;
|
|
24
|
+
/** Suggested ZIPs ordered by proximity (may be empty) */
|
|
25
|
+
zipOptions?: string[];
|
|
24
26
|
}
|
|
27
|
+
type TrackingLocationSource = 'google_criteria' | 'cloudflare_geo';
|
|
25
28
|
/**
|
|
26
29
|
* Tracking parameters for attribution (auto-captured + custom).
|
|
27
30
|
*/
|
|
@@ -49,21 +52,27 @@ interface TrackingSession {
|
|
|
49
52
|
leaseId: string | null;
|
|
50
53
|
/** Assigned phone number, or null if no number available */
|
|
51
54
|
phoneNumber: string | null;
|
|
52
|
-
/** Location data, or null if location could not be resolved */
|
|
53
|
-
location: TrackingLocation | null;
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
|
-
*
|
|
57
|
+
* Session API response format (includes expiresAt).
|
|
57
58
|
*/
|
|
58
|
-
interface
|
|
59
|
+
interface ApiSessionResponse {
|
|
59
60
|
sessionToken: string;
|
|
60
61
|
leaseId: string | null;
|
|
61
62
|
phoneNumber: string | null;
|
|
63
|
+
expiresAt: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Location API response format (includes expiresAt).
|
|
67
|
+
*/
|
|
68
|
+
interface ApiLocationResponse {
|
|
62
69
|
location: {
|
|
63
70
|
city: string;
|
|
64
71
|
state: string;
|
|
65
72
|
stateCode: string;
|
|
73
|
+
source: TrackingLocationSource;
|
|
66
74
|
} | null;
|
|
75
|
+
zipOptions?: string[];
|
|
67
76
|
expiresAt: number;
|
|
68
77
|
}
|
|
69
78
|
interface CallIntentResponse {
|
|
@@ -78,12 +87,14 @@ interface CallIntentResponse {
|
|
|
78
87
|
* Callback function for onReady subscription.
|
|
79
88
|
*/
|
|
80
89
|
type ReadyCallback = (session: TrackingSession) => void;
|
|
90
|
+
type LocationReadyCallback = (location: TrackingLocation | null) => void;
|
|
81
91
|
/**
|
|
82
92
|
* Global window extension for preload promise and gtag.
|
|
83
93
|
*/
|
|
84
94
|
declare global {
|
|
85
95
|
interface Window {
|
|
86
|
-
__cfTracking?: Promise<
|
|
96
|
+
__cfTracking?: Promise<ApiSessionResponse>;
|
|
97
|
+
__cfTrackingLocation?: Promise<ApiLocationResponse>;
|
|
87
98
|
gtag?: (command: 'get', targetId: string, fieldName: string, callback: (value: string) => void) => void;
|
|
88
99
|
}
|
|
89
100
|
}
|
|
@@ -91,7 +102,9 @@ declare global {
|
|
|
91
102
|
declare class CallForge {
|
|
92
103
|
private readonly config;
|
|
93
104
|
private readonly cache;
|
|
105
|
+
private readonly locationCache;
|
|
94
106
|
private sessionPromise;
|
|
107
|
+
private locationPromise;
|
|
95
108
|
private customParams;
|
|
96
109
|
private constructor();
|
|
97
110
|
/**
|
|
@@ -103,6 +116,19 @@ declare class CallForge {
|
|
|
103
116
|
* Returns cached data if valid, otherwise fetches from API.
|
|
104
117
|
*/
|
|
105
118
|
getSession(): Promise<TrackingSession>;
|
|
119
|
+
/**
|
|
120
|
+
* Get location data only.
|
|
121
|
+
* Returns cached data if valid, otherwise fetches from API.
|
|
122
|
+
*/
|
|
123
|
+
getLocation(): Promise<TrackingLocation | null>;
|
|
124
|
+
/**
|
|
125
|
+
* Kick off both session and location requests.
|
|
126
|
+
* Returns separate Promises so consumers can use each as soon as it resolves.
|
|
127
|
+
*/
|
|
128
|
+
getSessionAsync(): {
|
|
129
|
+
session: Promise<TrackingSession>;
|
|
130
|
+
location: Promise<TrackingLocation | null>;
|
|
131
|
+
};
|
|
106
132
|
/**
|
|
107
133
|
* Create a short-lived call intent token for click/callback deterministic attribution.
|
|
108
134
|
*/
|
|
@@ -112,6 +138,11 @@ declare class CallForge {
|
|
|
112
138
|
* Callback is called once session data is available.
|
|
113
139
|
*/
|
|
114
140
|
onReady(callback: ReadyCallback): void;
|
|
141
|
+
/**
|
|
142
|
+
* Subscribe to location ready event.
|
|
143
|
+
* Callback is called once location data is available.
|
|
144
|
+
*/
|
|
145
|
+
onLocationReady(callback: LocationReadyCallback): void;
|
|
115
146
|
/**
|
|
116
147
|
* Set custom tracking parameters for attribution.
|
|
117
148
|
*
|
|
@@ -139,14 +170,20 @@ declare class CallForge {
|
|
|
139
170
|
*/
|
|
140
171
|
private getGA4ClientIdFromCookie;
|
|
141
172
|
private fetchSession;
|
|
173
|
+
private fetchLocation;
|
|
142
174
|
private getLocationId;
|
|
143
175
|
private getAutoParams;
|
|
144
176
|
private fetchFromApi;
|
|
177
|
+
private fetchLocationFromApi;
|
|
145
178
|
private saveToCache;
|
|
179
|
+
private saveLocationToCache;
|
|
180
|
+
private toTrackingLocation;
|
|
181
|
+
private normalizeZipOptions;
|
|
146
182
|
private formatSession;
|
|
147
183
|
private formatApiResponse;
|
|
148
184
|
private syncParamsToCallForgeIfPossible;
|
|
149
185
|
private buildUrl;
|
|
186
|
+
private buildLocationUrl;
|
|
150
187
|
}
|
|
151
188
|
|
|
152
189
|
/**
|
|
@@ -155,4 +192,4 @@ declare class CallForge {
|
|
|
155
192
|
*/
|
|
156
193
|
declare function getPreloadSnippet(config: CallForgeConfig): string;
|
|
157
194
|
|
|
158
|
-
export { CallForge, type CallForgeConfig, type CallIntentResponse, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getPreloadSnippet };
|
|
195
|
+
export { CallForge, type CallForgeConfig, type CallIntentResponse, type LocationReadyCallback, type ReadyCallback, type TrackingLocation, type TrackingLocationSource, type TrackingParams, type TrackingSession, getPreloadSnippet };
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,10 @@ interface TrackingLocation {
|
|
|
21
21
|
state: string;
|
|
22
22
|
/** State abbreviation (e.g., "GA") */
|
|
23
23
|
stateCode: string;
|
|
24
|
+
/** Suggested ZIPs ordered by proximity (may be empty) */
|
|
25
|
+
zipOptions?: string[];
|
|
24
26
|
}
|
|
27
|
+
type TrackingLocationSource = 'google_criteria' | 'cloudflare_geo';
|
|
25
28
|
/**
|
|
26
29
|
* Tracking parameters for attribution (auto-captured + custom).
|
|
27
30
|
*/
|
|
@@ -49,21 +52,27 @@ interface TrackingSession {
|
|
|
49
52
|
leaseId: string | null;
|
|
50
53
|
/** Assigned phone number, or null if no number available */
|
|
51
54
|
phoneNumber: string | null;
|
|
52
|
-
/** Location data, or null if location could not be resolved */
|
|
53
|
-
location: TrackingLocation | null;
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
|
-
*
|
|
57
|
+
* Session API response format (includes expiresAt).
|
|
57
58
|
*/
|
|
58
|
-
interface
|
|
59
|
+
interface ApiSessionResponse {
|
|
59
60
|
sessionToken: string;
|
|
60
61
|
leaseId: string | null;
|
|
61
62
|
phoneNumber: string | null;
|
|
63
|
+
expiresAt: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Location API response format (includes expiresAt).
|
|
67
|
+
*/
|
|
68
|
+
interface ApiLocationResponse {
|
|
62
69
|
location: {
|
|
63
70
|
city: string;
|
|
64
71
|
state: string;
|
|
65
72
|
stateCode: string;
|
|
73
|
+
source: TrackingLocationSource;
|
|
66
74
|
} | null;
|
|
75
|
+
zipOptions?: string[];
|
|
67
76
|
expiresAt: number;
|
|
68
77
|
}
|
|
69
78
|
interface CallIntentResponse {
|
|
@@ -78,12 +87,14 @@ interface CallIntentResponse {
|
|
|
78
87
|
* Callback function for onReady subscription.
|
|
79
88
|
*/
|
|
80
89
|
type ReadyCallback = (session: TrackingSession) => void;
|
|
90
|
+
type LocationReadyCallback = (location: TrackingLocation | null) => void;
|
|
81
91
|
/**
|
|
82
92
|
* Global window extension for preload promise and gtag.
|
|
83
93
|
*/
|
|
84
94
|
declare global {
|
|
85
95
|
interface Window {
|
|
86
|
-
__cfTracking?: Promise<
|
|
96
|
+
__cfTracking?: Promise<ApiSessionResponse>;
|
|
97
|
+
__cfTrackingLocation?: Promise<ApiLocationResponse>;
|
|
87
98
|
gtag?: (command: 'get', targetId: string, fieldName: string, callback: (value: string) => void) => void;
|
|
88
99
|
}
|
|
89
100
|
}
|
|
@@ -91,7 +102,9 @@ declare global {
|
|
|
91
102
|
declare class CallForge {
|
|
92
103
|
private readonly config;
|
|
93
104
|
private readonly cache;
|
|
105
|
+
private readonly locationCache;
|
|
94
106
|
private sessionPromise;
|
|
107
|
+
private locationPromise;
|
|
95
108
|
private customParams;
|
|
96
109
|
private constructor();
|
|
97
110
|
/**
|
|
@@ -103,6 +116,19 @@ declare class CallForge {
|
|
|
103
116
|
* Returns cached data if valid, otherwise fetches from API.
|
|
104
117
|
*/
|
|
105
118
|
getSession(): Promise<TrackingSession>;
|
|
119
|
+
/**
|
|
120
|
+
* Get location data only.
|
|
121
|
+
* Returns cached data if valid, otherwise fetches from API.
|
|
122
|
+
*/
|
|
123
|
+
getLocation(): Promise<TrackingLocation | null>;
|
|
124
|
+
/**
|
|
125
|
+
* Kick off both session and location requests.
|
|
126
|
+
* Returns separate Promises so consumers can use each as soon as it resolves.
|
|
127
|
+
*/
|
|
128
|
+
getSessionAsync(): {
|
|
129
|
+
session: Promise<TrackingSession>;
|
|
130
|
+
location: Promise<TrackingLocation | null>;
|
|
131
|
+
};
|
|
106
132
|
/**
|
|
107
133
|
* Create a short-lived call intent token for click/callback deterministic attribution.
|
|
108
134
|
*/
|
|
@@ -112,6 +138,11 @@ declare class CallForge {
|
|
|
112
138
|
* Callback is called once session data is available.
|
|
113
139
|
*/
|
|
114
140
|
onReady(callback: ReadyCallback): void;
|
|
141
|
+
/**
|
|
142
|
+
* Subscribe to location ready event.
|
|
143
|
+
* Callback is called once location data is available.
|
|
144
|
+
*/
|
|
145
|
+
onLocationReady(callback: LocationReadyCallback): void;
|
|
115
146
|
/**
|
|
116
147
|
* Set custom tracking parameters for attribution.
|
|
117
148
|
*
|
|
@@ -139,14 +170,20 @@ declare class CallForge {
|
|
|
139
170
|
*/
|
|
140
171
|
private getGA4ClientIdFromCookie;
|
|
141
172
|
private fetchSession;
|
|
173
|
+
private fetchLocation;
|
|
142
174
|
private getLocationId;
|
|
143
175
|
private getAutoParams;
|
|
144
176
|
private fetchFromApi;
|
|
177
|
+
private fetchLocationFromApi;
|
|
145
178
|
private saveToCache;
|
|
179
|
+
private saveLocationToCache;
|
|
180
|
+
private toTrackingLocation;
|
|
181
|
+
private normalizeZipOptions;
|
|
146
182
|
private formatSession;
|
|
147
183
|
private formatApiResponse;
|
|
148
184
|
private syncParamsToCallForgeIfPossible;
|
|
149
185
|
private buildUrl;
|
|
186
|
+
private buildLocationUrl;
|
|
150
187
|
}
|
|
151
188
|
|
|
152
189
|
/**
|
|
@@ -155,4 +192,4 @@ declare class CallForge {
|
|
|
155
192
|
*/
|
|
156
193
|
declare function getPreloadSnippet(config: CallForgeConfig): string;
|
|
157
194
|
|
|
158
|
-
export { CallForge, type CallForgeConfig, type CallIntentResponse, type ReadyCallback, type TrackingLocation, type TrackingParams, type TrackingSession, getPreloadSnippet };
|
|
195
|
+
export { CallForge, type CallForgeConfig, type CallIntentResponse, type LocationReadyCallback, type ReadyCallback, type TrackingLocation, type TrackingLocationSource, type TrackingParams, type TrackingSession, getPreloadSnippet };
|
package/dist/index.js
CHANGED
|
@@ -138,6 +138,67 @@ var TrackingCache = class {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
// src/location-cache.ts
|
|
142
|
+
var EXPIRY_BUFFER_MS2 = 3e4;
|
|
143
|
+
var LocationCache = class {
|
|
144
|
+
constructor(siteKey) {
|
|
145
|
+
this.memoryCache = null;
|
|
146
|
+
this.useMemory = false;
|
|
147
|
+
const resolvedSiteKey = siteKey || (typeof window !== "undefined" ? window.location.hostname : "unknown-site");
|
|
148
|
+
this.key = `cf_location_v1_${resolvedSiteKey}`;
|
|
149
|
+
this.useMemory = !this.isLocalStorageAvailable();
|
|
150
|
+
}
|
|
151
|
+
isLocalStorageAvailable() {
|
|
152
|
+
try {
|
|
153
|
+
localStorage.setItem("__cf_test__", "1");
|
|
154
|
+
localStorage.removeItem("__cf_test__");
|
|
155
|
+
return true;
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
get(locationId) {
|
|
161
|
+
const cached = this.read();
|
|
162
|
+
if (!cached) return null;
|
|
163
|
+
if (cached.expiresAt - EXPIRY_BUFFER_MS2 <= Date.now()) return null;
|
|
164
|
+
if (!locationId) return cached;
|
|
165
|
+
if (cached.locId !== locationId) return null;
|
|
166
|
+
return cached;
|
|
167
|
+
}
|
|
168
|
+
set(value) {
|
|
169
|
+
if (this.useMemory) {
|
|
170
|
+
this.memoryCache = value;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
localStorage.setItem(this.key, JSON.stringify(value));
|
|
175
|
+
} catch (e) {
|
|
176
|
+
this.memoryCache = value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
clear() {
|
|
180
|
+
this.memoryCache = null;
|
|
181
|
+
if (!this.useMemory) {
|
|
182
|
+
try {
|
|
183
|
+
localStorage.removeItem(this.key);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
read() {
|
|
189
|
+
if (this.useMemory) {
|
|
190
|
+
return this.memoryCache;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const raw = localStorage.getItem(this.key);
|
|
194
|
+
if (!raw) return null;
|
|
195
|
+
return JSON.parse(raw);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return this.memoryCache;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
141
202
|
// src/client.ts
|
|
142
203
|
var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
|
|
143
204
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -146,6 +207,7 @@ var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid", "gad_campai
|
|
|
146
207
|
var CallForge = class _CallForge {
|
|
147
208
|
constructor(config) {
|
|
148
209
|
this.sessionPromise = null;
|
|
210
|
+
this.locationPromise = null;
|
|
149
211
|
this.customParams = {};
|
|
150
212
|
this.config = {
|
|
151
213
|
categoryId: config.categoryId,
|
|
@@ -154,6 +216,7 @@ var CallForge = class _CallForge {
|
|
|
154
216
|
siteKey: config.siteKey
|
|
155
217
|
};
|
|
156
218
|
this.cache = new TrackingCache(config.categoryId, config.siteKey);
|
|
219
|
+
this.locationCache = new LocationCache(config.siteKey);
|
|
157
220
|
this.captureGA4ClientId();
|
|
158
221
|
this.startGA4ClientIdPolling();
|
|
159
222
|
}
|
|
@@ -174,6 +237,27 @@ var CallForge = class _CallForge {
|
|
|
174
237
|
this.sessionPromise = this.fetchSession();
|
|
175
238
|
return this.sessionPromise;
|
|
176
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Get location data only.
|
|
242
|
+
* Returns cached data if valid, otherwise fetches from API.
|
|
243
|
+
*/
|
|
244
|
+
async getLocation() {
|
|
245
|
+
if (this.locationPromise) {
|
|
246
|
+
return this.locationPromise;
|
|
247
|
+
}
|
|
248
|
+
this.locationPromise = this.fetchLocation();
|
|
249
|
+
return this.locationPromise;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Kick off both session and location requests.
|
|
253
|
+
* Returns separate Promises so consumers can use each as soon as it resolves.
|
|
254
|
+
*/
|
|
255
|
+
getSessionAsync() {
|
|
256
|
+
return {
|
|
257
|
+
session: this.getSession(),
|
|
258
|
+
location: this.getLocation()
|
|
259
|
+
};
|
|
260
|
+
}
|
|
177
261
|
/**
|
|
178
262
|
* Create a short-lived call intent token for click/callback deterministic attribution.
|
|
179
263
|
*/
|
|
@@ -209,6 +293,14 @@ var CallForge = class _CallForge {
|
|
|
209
293
|
this.getSession().then(callback).catch(() => {
|
|
210
294
|
});
|
|
211
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Subscribe to location ready event.
|
|
298
|
+
* Callback is called once location data is available.
|
|
299
|
+
*/
|
|
300
|
+
onLocationReady(callback) {
|
|
301
|
+
this.getLocation().then(callback).catch(() => {
|
|
302
|
+
});
|
|
303
|
+
}
|
|
212
304
|
/**
|
|
213
305
|
* Set custom tracking parameters for attribution.
|
|
214
306
|
*
|
|
@@ -331,6 +423,29 @@ var CallForge = class _CallForge {
|
|
|
331
423
|
this.saveToCache(locationId, data, params);
|
|
332
424
|
return this.formatApiResponse(data);
|
|
333
425
|
}
|
|
426
|
+
async fetchLocation() {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
const locationId = this.getLocationId();
|
|
429
|
+
if (typeof window !== "undefined" && window.__cfTrackingLocation) {
|
|
430
|
+
try {
|
|
431
|
+
const data2 = await window.__cfTrackingLocation;
|
|
432
|
+
const dataWithExtras = data2;
|
|
433
|
+
const effectiveLocId = (_a = dataWithExtras.locId) != null ? _a : locationId;
|
|
434
|
+
const location2 = this.toTrackingLocation(data2.location, dataWithExtras.zipOptions);
|
|
435
|
+
this.saveLocationToCache(effectiveLocId, location2, data2.expiresAt);
|
|
436
|
+
return location2;
|
|
437
|
+
} catch (e) {
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const cached = this.locationCache.get(locationId);
|
|
441
|
+
if (cached) {
|
|
442
|
+
return this.toTrackingLocation(cached.location, (_b = cached.location) == null ? void 0 : _b.zipOptions);
|
|
443
|
+
}
|
|
444
|
+
const data = await this.fetchLocationFromApi(locationId);
|
|
445
|
+
const location = this.toTrackingLocation(data.location, data.zipOptions);
|
|
446
|
+
this.saveLocationToCache(locationId, location, data.expiresAt);
|
|
447
|
+
return location;
|
|
448
|
+
}
|
|
334
449
|
getLocationId() {
|
|
335
450
|
if (typeof window === "undefined") return null;
|
|
336
451
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -365,33 +480,76 @@ var CallForge = class _CallForge {
|
|
|
365
480
|
clearTimeout(timeoutId);
|
|
366
481
|
}
|
|
367
482
|
}
|
|
483
|
+
async fetchLocationFromApi(locationId) {
|
|
484
|
+
const url = this.buildLocationUrl(locationId);
|
|
485
|
+
const controller = new AbortController();
|
|
486
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
487
|
+
try {
|
|
488
|
+
const response = await fetch(url, {
|
|
489
|
+
credentials: "omit",
|
|
490
|
+
signal: controller.signal
|
|
491
|
+
});
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
494
|
+
}
|
|
495
|
+
return await response.json();
|
|
496
|
+
} finally {
|
|
497
|
+
clearTimeout(timeoutId);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
368
500
|
saveToCache(locationId, data, params) {
|
|
369
501
|
const cached = {
|
|
370
502
|
locId: locationId != null ? locationId : null,
|
|
371
503
|
sessionToken: data.sessionToken,
|
|
372
504
|
leaseId: data.leaseId,
|
|
373
505
|
phoneNumber: data.phoneNumber,
|
|
374
|
-
location: data.location,
|
|
375
506
|
expiresAt: data.expiresAt,
|
|
376
507
|
tokenVersion: "v1",
|
|
377
508
|
params
|
|
378
509
|
};
|
|
379
510
|
this.cache.set(cached);
|
|
380
511
|
}
|
|
512
|
+
saveLocationToCache(locationId, location, expiresAt) {
|
|
513
|
+
this.locationCache.set({
|
|
514
|
+
locId: locationId != null ? locationId : null,
|
|
515
|
+
location,
|
|
516
|
+
expiresAt,
|
|
517
|
+
tokenVersion: "v1"
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
toTrackingLocation(location, zipOptions) {
|
|
521
|
+
if (!location) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
city: location.city,
|
|
526
|
+
state: location.state,
|
|
527
|
+
stateCode: location.stateCode,
|
|
528
|
+
zipOptions: this.normalizeZipOptions(zipOptions)
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
normalizeZipOptions(value) {
|
|
532
|
+
if (!Array.isArray(value)) {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
return Array.from(
|
|
536
|
+
new Set(
|
|
537
|
+
value.filter((zip) => typeof zip === "string" && /^\d{5}$/.test(zip))
|
|
538
|
+
)
|
|
539
|
+
);
|
|
540
|
+
}
|
|
381
541
|
formatSession(cached) {
|
|
382
542
|
return {
|
|
383
543
|
sessionToken: cached.sessionToken,
|
|
384
544
|
leaseId: cached.leaseId,
|
|
385
|
-
phoneNumber: cached.phoneNumber
|
|
386
|
-
location: cached.location
|
|
545
|
+
phoneNumber: cached.phoneNumber
|
|
387
546
|
};
|
|
388
547
|
}
|
|
389
548
|
formatApiResponse(data) {
|
|
390
549
|
return {
|
|
391
550
|
sessionToken: data.sessionToken,
|
|
392
551
|
leaseId: data.leaseId,
|
|
393
|
-
phoneNumber: data.phoneNumber
|
|
394
|
-
location: data.location
|
|
552
|
+
phoneNumber: data.phoneNumber
|
|
395
553
|
};
|
|
396
554
|
}
|
|
397
555
|
async syncParamsToCallForgeIfPossible() {
|
|
@@ -424,6 +582,13 @@ var CallForge = class _CallForge {
|
|
|
424
582
|
const qs = sorted.map((k) => `${k}=${encodeURIComponent(queryParams[k])}`).join("&");
|
|
425
583
|
return `${endpoint}/v1/tracking/session?${qs}`;
|
|
426
584
|
}
|
|
585
|
+
buildLocationUrl(locationId) {
|
|
586
|
+
const { endpoint } = this.config;
|
|
587
|
+
if (!locationId) {
|
|
588
|
+
return `${endpoint}/v1/tracking/location`;
|
|
589
|
+
}
|
|
590
|
+
return `${endpoint}/v1/tracking/location?loc_physical_ms=${encodeURIComponent(locationId)}`;
|
|
591
|
+
}
|
|
427
592
|
};
|
|
428
593
|
|
|
429
594
|
// src/preload.ts
|
|
@@ -453,6 +618,17 @@ if(m){try{var g=decodeURIComponent(m[1]||'');var s=g.split('.');if(s.length>=2){
|
|
|
453
618
|
for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
|
|
454
619
|
var site='${config.siteKey || ""}'||location.hostname||'unknown-site';
|
|
455
620
|
var key='cf_tracking_v1_'+site+'_${categoryId}';
|
|
621
|
+
var lkey='cf_location_v1_'+site;
|
|
622
|
+
try{
|
|
623
|
+
var cl=JSON.parse(localStorage.getItem(lkey));
|
|
624
|
+
if(cl&&cl.expiresAt>Date.now()+30000){
|
|
625
|
+
if(!loc||(loc&&cl.locId===loc)){if(cl.location&&!Array.isArray(cl.location.zipOptions))cl.location.zipOptions=[];window.__cfTrackingLocation=Promise.resolve(cl)}
|
|
626
|
+
}}catch(e){}
|
|
627
|
+
if(!window.__cfTrackingLocation){
|
|
628
|
+
var lurl='${endpoint}/v1/tracking/location';
|
|
629
|
+
if(loc)lurl+='?loc_physical_ms='+encodeURIComponent(loc);
|
|
630
|
+
window.__cfTrackingLocation=fetch(lurl,{credentials:'omit'}).then(function(r){if(!r.ok)throw new Error('tracking preload location failed');return r.json()}).then(function(d){var z=Array.isArray(d.zipOptions)?d.zipOptions.filter(function(x){return /^\\d{5}$/.test(x)}):[];d.locId=loc;d.zipOptions=z;try{localStorage.setItem(lkey,JSON.stringify({locId:loc,location:d.location?{city:d.location.city,state:d.location.state,stateCode:d.location.stateCode,zipOptions:z}:null,expiresAt:d.expiresAt,tokenVersion:'v1'}))}catch(e){}return d});
|
|
631
|
+
}
|
|
456
632
|
var token=null;
|
|
457
633
|
try{
|
|
458
634
|
var c=JSON.parse(localStorage.getItem(key));
|
|
@@ -467,7 +643,7 @@ if(loc)url+='&loc_physical_ms='+loc;
|
|
|
467
643
|
if(token)url+='&sessionToken='+encodeURIComponent(token);
|
|
468
644
|
var ks=Object.keys(p).sort();
|
|
469
645
|
for(var j=0;j<ks.length;j++)url+='&'+ks[j]+'='+encodeURIComponent(p[ks[j]]);
|
|
470
|
-
window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){if(!r.ok)throw new Error('tracking preload failed');return r.json()}).then(function(d){d.params=p;d.locId=loc;try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionToken:d.sessionToken,leaseId:d.leaseId,phoneNumber:d.phoneNumber,
|
|
646
|
+
window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){if(!r.ok)throw new Error('tracking preload failed');return r.json()}).then(function(d){d.params=p;d.locId=loc;try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionToken:d.sessionToken,leaseId:d.leaseId,phoneNumber:d.phoneNumber,expiresAt:d.expiresAt,tokenVersion:'v1',params:p}))}catch(e){}return d});
|
|
471
647
|
})();`.replace(/\n/g, "");
|
|
472
648
|
return `<link rel="preconnect" href="${endpoint}">
|
|
473
649
|
<script>${script}</script>`;
|
package/dist/index.mjs
CHANGED
|
@@ -114,6 +114,67 @@ var TrackingCache = class {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
// src/location-cache.ts
|
|
118
|
+
var EXPIRY_BUFFER_MS2 = 3e4;
|
|
119
|
+
var LocationCache = class {
|
|
120
|
+
constructor(siteKey) {
|
|
121
|
+
this.memoryCache = null;
|
|
122
|
+
this.useMemory = false;
|
|
123
|
+
const resolvedSiteKey = siteKey || (typeof window !== "undefined" ? window.location.hostname : "unknown-site");
|
|
124
|
+
this.key = `cf_location_v1_${resolvedSiteKey}`;
|
|
125
|
+
this.useMemory = !this.isLocalStorageAvailable();
|
|
126
|
+
}
|
|
127
|
+
isLocalStorageAvailable() {
|
|
128
|
+
try {
|
|
129
|
+
localStorage.setItem("__cf_test__", "1");
|
|
130
|
+
localStorage.removeItem("__cf_test__");
|
|
131
|
+
return true;
|
|
132
|
+
} catch (e) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
get(locationId) {
|
|
137
|
+
const cached = this.read();
|
|
138
|
+
if (!cached) return null;
|
|
139
|
+
if (cached.expiresAt - EXPIRY_BUFFER_MS2 <= Date.now()) return null;
|
|
140
|
+
if (!locationId) return cached;
|
|
141
|
+
if (cached.locId !== locationId) return null;
|
|
142
|
+
return cached;
|
|
143
|
+
}
|
|
144
|
+
set(value) {
|
|
145
|
+
if (this.useMemory) {
|
|
146
|
+
this.memoryCache = value;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
localStorage.setItem(this.key, JSON.stringify(value));
|
|
151
|
+
} catch (e) {
|
|
152
|
+
this.memoryCache = value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
clear() {
|
|
156
|
+
this.memoryCache = null;
|
|
157
|
+
if (!this.useMemory) {
|
|
158
|
+
try {
|
|
159
|
+
localStorage.removeItem(this.key);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
read() {
|
|
165
|
+
if (this.useMemory) {
|
|
166
|
+
return this.memoryCache;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const raw = localStorage.getItem(this.key);
|
|
170
|
+
if (!raw) return null;
|
|
171
|
+
return JSON.parse(raw);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return this.memoryCache;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
117
178
|
// src/client.ts
|
|
118
179
|
var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
|
|
119
180
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -122,6 +183,7 @@ var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid", "gad_campai
|
|
|
122
183
|
var CallForge = class _CallForge {
|
|
123
184
|
constructor(config) {
|
|
124
185
|
this.sessionPromise = null;
|
|
186
|
+
this.locationPromise = null;
|
|
125
187
|
this.customParams = {};
|
|
126
188
|
this.config = {
|
|
127
189
|
categoryId: config.categoryId,
|
|
@@ -130,6 +192,7 @@ var CallForge = class _CallForge {
|
|
|
130
192
|
siteKey: config.siteKey
|
|
131
193
|
};
|
|
132
194
|
this.cache = new TrackingCache(config.categoryId, config.siteKey);
|
|
195
|
+
this.locationCache = new LocationCache(config.siteKey);
|
|
133
196
|
this.captureGA4ClientId();
|
|
134
197
|
this.startGA4ClientIdPolling();
|
|
135
198
|
}
|
|
@@ -150,6 +213,27 @@ var CallForge = class _CallForge {
|
|
|
150
213
|
this.sessionPromise = this.fetchSession();
|
|
151
214
|
return this.sessionPromise;
|
|
152
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Get location data only.
|
|
218
|
+
* Returns cached data if valid, otherwise fetches from API.
|
|
219
|
+
*/
|
|
220
|
+
async getLocation() {
|
|
221
|
+
if (this.locationPromise) {
|
|
222
|
+
return this.locationPromise;
|
|
223
|
+
}
|
|
224
|
+
this.locationPromise = this.fetchLocation();
|
|
225
|
+
return this.locationPromise;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Kick off both session and location requests.
|
|
229
|
+
* Returns separate Promises so consumers can use each as soon as it resolves.
|
|
230
|
+
*/
|
|
231
|
+
getSessionAsync() {
|
|
232
|
+
return {
|
|
233
|
+
session: this.getSession(),
|
|
234
|
+
location: this.getLocation()
|
|
235
|
+
};
|
|
236
|
+
}
|
|
153
237
|
/**
|
|
154
238
|
* Create a short-lived call intent token for click/callback deterministic attribution.
|
|
155
239
|
*/
|
|
@@ -185,6 +269,14 @@ var CallForge = class _CallForge {
|
|
|
185
269
|
this.getSession().then(callback).catch(() => {
|
|
186
270
|
});
|
|
187
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Subscribe to location ready event.
|
|
274
|
+
* Callback is called once location data is available.
|
|
275
|
+
*/
|
|
276
|
+
onLocationReady(callback) {
|
|
277
|
+
this.getLocation().then(callback).catch(() => {
|
|
278
|
+
});
|
|
279
|
+
}
|
|
188
280
|
/**
|
|
189
281
|
* Set custom tracking parameters for attribution.
|
|
190
282
|
*
|
|
@@ -307,6 +399,29 @@ var CallForge = class _CallForge {
|
|
|
307
399
|
this.saveToCache(locationId, data, params);
|
|
308
400
|
return this.formatApiResponse(data);
|
|
309
401
|
}
|
|
402
|
+
async fetchLocation() {
|
|
403
|
+
var _a, _b;
|
|
404
|
+
const locationId = this.getLocationId();
|
|
405
|
+
if (typeof window !== "undefined" && window.__cfTrackingLocation) {
|
|
406
|
+
try {
|
|
407
|
+
const data2 = await window.__cfTrackingLocation;
|
|
408
|
+
const dataWithExtras = data2;
|
|
409
|
+
const effectiveLocId = (_a = dataWithExtras.locId) != null ? _a : locationId;
|
|
410
|
+
const location2 = this.toTrackingLocation(data2.location, dataWithExtras.zipOptions);
|
|
411
|
+
this.saveLocationToCache(effectiveLocId, location2, data2.expiresAt);
|
|
412
|
+
return location2;
|
|
413
|
+
} catch (e) {
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const cached = this.locationCache.get(locationId);
|
|
417
|
+
if (cached) {
|
|
418
|
+
return this.toTrackingLocation(cached.location, (_b = cached.location) == null ? void 0 : _b.zipOptions);
|
|
419
|
+
}
|
|
420
|
+
const data = await this.fetchLocationFromApi(locationId);
|
|
421
|
+
const location = this.toTrackingLocation(data.location, data.zipOptions);
|
|
422
|
+
this.saveLocationToCache(locationId, location, data.expiresAt);
|
|
423
|
+
return location;
|
|
424
|
+
}
|
|
310
425
|
getLocationId() {
|
|
311
426
|
if (typeof window === "undefined") return null;
|
|
312
427
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -341,33 +456,76 @@ var CallForge = class _CallForge {
|
|
|
341
456
|
clearTimeout(timeoutId);
|
|
342
457
|
}
|
|
343
458
|
}
|
|
459
|
+
async fetchLocationFromApi(locationId) {
|
|
460
|
+
const url = this.buildLocationUrl(locationId);
|
|
461
|
+
const controller = new AbortController();
|
|
462
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
463
|
+
try {
|
|
464
|
+
const response = await fetch(url, {
|
|
465
|
+
credentials: "omit",
|
|
466
|
+
signal: controller.signal
|
|
467
|
+
});
|
|
468
|
+
if (!response.ok) {
|
|
469
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
470
|
+
}
|
|
471
|
+
return await response.json();
|
|
472
|
+
} finally {
|
|
473
|
+
clearTimeout(timeoutId);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
344
476
|
saveToCache(locationId, data, params) {
|
|
345
477
|
const cached = {
|
|
346
478
|
locId: locationId != null ? locationId : null,
|
|
347
479
|
sessionToken: data.sessionToken,
|
|
348
480
|
leaseId: data.leaseId,
|
|
349
481
|
phoneNumber: data.phoneNumber,
|
|
350
|
-
location: data.location,
|
|
351
482
|
expiresAt: data.expiresAt,
|
|
352
483
|
tokenVersion: "v1",
|
|
353
484
|
params
|
|
354
485
|
};
|
|
355
486
|
this.cache.set(cached);
|
|
356
487
|
}
|
|
488
|
+
saveLocationToCache(locationId, location, expiresAt) {
|
|
489
|
+
this.locationCache.set({
|
|
490
|
+
locId: locationId != null ? locationId : null,
|
|
491
|
+
location,
|
|
492
|
+
expiresAt,
|
|
493
|
+
tokenVersion: "v1"
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
toTrackingLocation(location, zipOptions) {
|
|
497
|
+
if (!location) {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
city: location.city,
|
|
502
|
+
state: location.state,
|
|
503
|
+
stateCode: location.stateCode,
|
|
504
|
+
zipOptions: this.normalizeZipOptions(zipOptions)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
normalizeZipOptions(value) {
|
|
508
|
+
if (!Array.isArray(value)) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
return Array.from(
|
|
512
|
+
new Set(
|
|
513
|
+
value.filter((zip) => typeof zip === "string" && /^\d{5}$/.test(zip))
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
357
517
|
formatSession(cached) {
|
|
358
518
|
return {
|
|
359
519
|
sessionToken: cached.sessionToken,
|
|
360
520
|
leaseId: cached.leaseId,
|
|
361
|
-
phoneNumber: cached.phoneNumber
|
|
362
|
-
location: cached.location
|
|
521
|
+
phoneNumber: cached.phoneNumber
|
|
363
522
|
};
|
|
364
523
|
}
|
|
365
524
|
formatApiResponse(data) {
|
|
366
525
|
return {
|
|
367
526
|
sessionToken: data.sessionToken,
|
|
368
527
|
leaseId: data.leaseId,
|
|
369
|
-
phoneNumber: data.phoneNumber
|
|
370
|
-
location: data.location
|
|
528
|
+
phoneNumber: data.phoneNumber
|
|
371
529
|
};
|
|
372
530
|
}
|
|
373
531
|
async syncParamsToCallForgeIfPossible() {
|
|
@@ -400,6 +558,13 @@ var CallForge = class _CallForge {
|
|
|
400
558
|
const qs = sorted.map((k) => `${k}=${encodeURIComponent(queryParams[k])}`).join("&");
|
|
401
559
|
return `${endpoint}/v1/tracking/session?${qs}`;
|
|
402
560
|
}
|
|
561
|
+
buildLocationUrl(locationId) {
|
|
562
|
+
const { endpoint } = this.config;
|
|
563
|
+
if (!locationId) {
|
|
564
|
+
return `${endpoint}/v1/tracking/location`;
|
|
565
|
+
}
|
|
566
|
+
return `${endpoint}/v1/tracking/location?loc_physical_ms=${encodeURIComponent(locationId)}`;
|
|
567
|
+
}
|
|
403
568
|
};
|
|
404
569
|
|
|
405
570
|
// src/preload.ts
|
|
@@ -429,6 +594,17 @@ if(m){try{var g=decodeURIComponent(m[1]||'');var s=g.split('.');if(s.length>=2){
|
|
|
429
594
|
for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
|
|
430
595
|
var site='${config.siteKey || ""}'||location.hostname||'unknown-site';
|
|
431
596
|
var key='cf_tracking_v1_'+site+'_${categoryId}';
|
|
597
|
+
var lkey='cf_location_v1_'+site;
|
|
598
|
+
try{
|
|
599
|
+
var cl=JSON.parse(localStorage.getItem(lkey));
|
|
600
|
+
if(cl&&cl.expiresAt>Date.now()+30000){
|
|
601
|
+
if(!loc||(loc&&cl.locId===loc)){if(cl.location&&!Array.isArray(cl.location.zipOptions))cl.location.zipOptions=[];window.__cfTrackingLocation=Promise.resolve(cl)}
|
|
602
|
+
}}catch(e){}
|
|
603
|
+
if(!window.__cfTrackingLocation){
|
|
604
|
+
var lurl='${endpoint}/v1/tracking/location';
|
|
605
|
+
if(loc)lurl+='?loc_physical_ms='+encodeURIComponent(loc);
|
|
606
|
+
window.__cfTrackingLocation=fetch(lurl,{credentials:'omit'}).then(function(r){if(!r.ok)throw new Error('tracking preload location failed');return r.json()}).then(function(d){var z=Array.isArray(d.zipOptions)?d.zipOptions.filter(function(x){return /^\\d{5}$/.test(x)}):[];d.locId=loc;d.zipOptions=z;try{localStorage.setItem(lkey,JSON.stringify({locId:loc,location:d.location?{city:d.location.city,state:d.location.state,stateCode:d.location.stateCode,zipOptions:z}:null,expiresAt:d.expiresAt,tokenVersion:'v1'}))}catch(e){}return d});
|
|
607
|
+
}
|
|
432
608
|
var token=null;
|
|
433
609
|
try{
|
|
434
610
|
var c=JSON.parse(localStorage.getItem(key));
|
|
@@ -443,7 +619,7 @@ if(loc)url+='&loc_physical_ms='+loc;
|
|
|
443
619
|
if(token)url+='&sessionToken='+encodeURIComponent(token);
|
|
444
620
|
var ks=Object.keys(p).sort();
|
|
445
621
|
for(var j=0;j<ks.length;j++)url+='&'+ks[j]+'='+encodeURIComponent(p[ks[j]]);
|
|
446
|
-
window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){if(!r.ok)throw new Error('tracking preload failed');return r.json()}).then(function(d){d.params=p;d.locId=loc;try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionToken:d.sessionToken,leaseId:d.leaseId,phoneNumber:d.phoneNumber,
|
|
622
|
+
window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){if(!r.ok)throw new Error('tracking preload failed');return r.json()}).then(function(d){d.params=p;d.locId=loc;try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionToken:d.sessionToken,leaseId:d.leaseId,phoneNumber:d.phoneNumber,expiresAt:d.expiresAt,tokenVersion:'v1',params:p}))}catch(e){}return d});
|
|
447
623
|
})();`.replace(/\n/g, "");
|
|
448
624
|
return `<link rel="preconnect" href="${endpoint}">
|
|
449
625
|
<script>${script}</script>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@callforge/tracking-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
|
-
"devDependencies": {
|
|
21
|
-
"jsdom": "^27.4.0",
|
|
22
|
-
"tsup": "^8.0.0",
|
|
23
|
-
"typescript": "^5.3.0",
|
|
24
|
-
"vitest": "^1.6.0",
|
|
25
|
-
"@callforge/tsconfig": "0.0.0"
|
|
26
|
-
},
|
|
27
20
|
"scripts": {
|
|
28
21
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
29
22
|
"clean": "rm -rf dist",
|
|
30
23
|
"test": "vitest run",
|
|
31
24
|
"test:watch": "vitest"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@callforge/tsconfig": "workspace:*",
|
|
28
|
+
"jsdom": "^27.4.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.3.0",
|
|
31
|
+
"vitest": "^1.6.0"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|