@callforge/tracking-client 0.7.1 → 0.8.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 +14 -3
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +98 -5
- package/dist/index.mjs +98 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +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
|
|
3
|
+
Lightweight client library for the CallForge tracking API. Handles location-aware phone number assignment with aggressive caching and preload optimization.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,11 +10,20 @@ npm install @callforge/tracking-client
|
|
|
10
10
|
pnpm add @callforge/tracking-client
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Lease Hardening Migration (v0.8+)
|
|
14
|
+
|
|
15
|
+
This release adds bootstrap-token support for lease hardening and bot suppression.
|
|
16
|
+
|
|
17
|
+
Client integration requirements:
|
|
18
|
+
- Add the generated preload snippet in `<head>`. This prefetches bootstrap tokens and keeps session lookup fast.
|
|
19
|
+
- Keep handling `phoneNumber` and `leaseId` separately. A request can return a phone number with `leaseId: null` when lease assignment is intentionally suppressed.
|
|
20
|
+
- For attribution/scale metrics, treat `leaseId` as the source of truth for lease-backed traffic.
|
|
21
|
+
|
|
13
22
|
## Quick Start
|
|
14
23
|
|
|
15
|
-
### 1. Add preload snippet to `<head>` (
|
|
24
|
+
### 1. Add preload snippet to `<head>` (required for deterministic leases)
|
|
16
25
|
|
|
17
|
-
For optimal performance
|
|
26
|
+
For optimal performance and lease hardening, add this snippet to your HTML `<head>`:
|
|
18
27
|
|
|
19
28
|
```typescript
|
|
20
29
|
import { getPreloadSnippet } from '@callforge/tracking-client';
|
|
@@ -158,6 +167,7 @@ Behavior:
|
|
|
158
167
|
- Returns cached data if valid.
|
|
159
168
|
- Fetches fresh data when cache is missing/expired.
|
|
160
169
|
- If `loc_physical_ms` is present in the URL, cached sessions are only reused when it matches the cached `locId`.
|
|
170
|
+
- If lease assignment is suppressed (for example bot traffic or missing/invalid bootstrap), `phoneNumber` may be present while `leaseId` is `null`.
|
|
161
171
|
- Throws on network errors or API errors.
|
|
162
172
|
|
|
163
173
|
### `client.getLocation()`
|
|
@@ -282,6 +292,7 @@ Parameters are sent as a sorted query string for cache consistency:
|
|
|
282
292
|
|
|
283
293
|
- Session cache key: `cf_tracking_v1_<siteKey>_<categoryId>`
|
|
284
294
|
- Location cache key: `cf_location_v1_<siteKey>`
|
|
295
|
+
- Bootstrap cache key: `cf_bootstrap_v1_<siteKey>_<categoryId>`
|
|
285
296
|
- TTL: controlled by the server `expiresAt` response (currently 30 minutes)
|
|
286
297
|
- Storage: localStorage (falls back to memory if unavailable)
|
|
287
298
|
|
package/dist/index.d.mts
CHANGED
|
@@ -103,6 +103,8 @@ declare class CallForge {
|
|
|
103
103
|
private readonly config;
|
|
104
104
|
private readonly cache;
|
|
105
105
|
private readonly locationCache;
|
|
106
|
+
private readonly bootstrapCacheKey;
|
|
107
|
+
private bootstrapMemoryCache;
|
|
106
108
|
private sessionPromise;
|
|
107
109
|
private locationPromise;
|
|
108
110
|
private customParams;
|
|
@@ -174,7 +176,10 @@ declare class CallForge {
|
|
|
174
176
|
private getLocationId;
|
|
175
177
|
private getAutoParams;
|
|
176
178
|
private fetchFromApi;
|
|
179
|
+
private getBootstrapToken;
|
|
177
180
|
private fetchLocationFromApi;
|
|
181
|
+
private getCachedBootstrapToken;
|
|
182
|
+
private saveBootstrapToken;
|
|
178
183
|
private saveToCache;
|
|
179
184
|
private saveLocationToCache;
|
|
180
185
|
private toTrackingLocation;
|
|
@@ -183,6 +188,7 @@ declare class CallForge {
|
|
|
183
188
|
private formatApiResponse;
|
|
184
189
|
private syncParamsToCallForgeIfPossible;
|
|
185
190
|
private buildUrl;
|
|
191
|
+
private buildBootstrapUrl;
|
|
186
192
|
private buildLocationUrl;
|
|
187
193
|
}
|
|
188
194
|
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,8 @@ declare class CallForge {
|
|
|
103
103
|
private readonly config;
|
|
104
104
|
private readonly cache;
|
|
105
105
|
private readonly locationCache;
|
|
106
|
+
private readonly bootstrapCacheKey;
|
|
107
|
+
private bootstrapMemoryCache;
|
|
106
108
|
private sessionPromise;
|
|
107
109
|
private locationPromise;
|
|
108
110
|
private customParams;
|
|
@@ -174,7 +176,10 @@ declare class CallForge {
|
|
|
174
176
|
private getLocationId;
|
|
175
177
|
private getAutoParams;
|
|
176
178
|
private fetchFromApi;
|
|
179
|
+
private getBootstrapToken;
|
|
177
180
|
private fetchLocationFromApi;
|
|
181
|
+
private getCachedBootstrapToken;
|
|
182
|
+
private saveBootstrapToken;
|
|
178
183
|
private saveToCache;
|
|
179
184
|
private saveLocationToCache;
|
|
180
185
|
private toTrackingLocation;
|
|
@@ -183,6 +188,7 @@ declare class CallForge {
|
|
|
183
188
|
private formatApiResponse;
|
|
184
189
|
private syncParamsToCallForgeIfPossible;
|
|
185
190
|
private buildUrl;
|
|
191
|
+
private buildBootstrapUrl;
|
|
186
192
|
private buildLocationUrl;
|
|
187
193
|
}
|
|
188
194
|
|
package/dist/index.js
CHANGED
|
@@ -203,9 +203,11 @@ var LocationCache = class {
|
|
|
203
203
|
var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
|
|
204
204
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
205
205
|
var CALL_INTENT_TIMEOUT_MS = 8e3;
|
|
206
|
+
var BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS = 1e4;
|
|
206
207
|
var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid", "gad_campaignid", "gad_source"];
|
|
207
208
|
var CallForge = class _CallForge {
|
|
208
209
|
constructor(config) {
|
|
210
|
+
this.bootstrapMemoryCache = null;
|
|
209
211
|
this.sessionPromise = null;
|
|
210
212
|
this.locationPromise = null;
|
|
211
213
|
this.customParams = {};
|
|
@@ -215,8 +217,10 @@ var CallForge = class _CallForge {
|
|
|
215
217
|
ga4MeasurementId: config.ga4MeasurementId,
|
|
216
218
|
siteKey: config.siteKey
|
|
217
219
|
};
|
|
220
|
+
const resolvedSiteKey = config.siteKey || (typeof window !== "undefined" ? window.location.hostname : "unknown-site");
|
|
218
221
|
this.cache = new TrackingCache(config.categoryId, config.siteKey);
|
|
219
222
|
this.locationCache = new LocationCache(config.siteKey);
|
|
223
|
+
this.bootstrapCacheKey = `cf_bootstrap_v1_${resolvedSiteKey}_${config.categoryId}`;
|
|
220
224
|
this.captureGA4ClientId();
|
|
221
225
|
this.startGA4ClientIdPolling();
|
|
222
226
|
}
|
|
@@ -464,7 +468,43 @@ var CallForge = class _CallForge {
|
|
|
464
468
|
return params;
|
|
465
469
|
}
|
|
466
470
|
async fetchFromApi(locationId, sessionToken, params) {
|
|
467
|
-
const
|
|
471
|
+
const controller = new AbortController();
|
|
472
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
473
|
+
try {
|
|
474
|
+
let bootstrapToken = this.getCachedBootstrapToken();
|
|
475
|
+
let response = await fetch(
|
|
476
|
+
this.buildUrl(locationId, sessionToken, params, bootstrapToken),
|
|
477
|
+
{
|
|
478
|
+
credentials: "omit",
|
|
479
|
+
signal: controller.signal
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
if (response.status === 401) {
|
|
483
|
+
bootstrapToken = await this.getBootstrapToken(true);
|
|
484
|
+
if (bootstrapToken) {
|
|
485
|
+
response = await fetch(
|
|
486
|
+
this.buildUrl(locationId, sessionToken, params, bootstrapToken),
|
|
487
|
+
{
|
|
488
|
+
credentials: "omit",
|
|
489
|
+
signal: controller.signal
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!response.ok) {
|
|
495
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
496
|
+
}
|
|
497
|
+
return await response.json();
|
|
498
|
+
} finally {
|
|
499
|
+
clearTimeout(timeoutId);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async getBootstrapToken(forceRefresh = false) {
|
|
503
|
+
const cached = this.getCachedBootstrapToken();
|
|
504
|
+
if (!forceRefresh && cached) {
|
|
505
|
+
return cached;
|
|
506
|
+
}
|
|
507
|
+
const url = this.buildBootstrapUrl();
|
|
468
508
|
const controller = new AbortController();
|
|
469
509
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
470
510
|
try {
|
|
@@ -473,9 +513,14 @@ var CallForge = class _CallForge {
|
|
|
473
513
|
signal: controller.signal
|
|
474
514
|
});
|
|
475
515
|
if (!response.ok) {
|
|
476
|
-
|
|
516
|
+
return null;
|
|
477
517
|
}
|
|
478
|
-
|
|
518
|
+
const data = await response.json();
|
|
519
|
+
if (typeof data.bootstrapToken !== "string" || typeof data.expiresAt !== "number") {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
this.saveBootstrapToken(data.bootstrapToken, data.expiresAt);
|
|
523
|
+
return data.bootstrapToken;
|
|
479
524
|
} finally {
|
|
480
525
|
clearTimeout(timeoutId);
|
|
481
526
|
}
|
|
@@ -497,6 +542,40 @@ var CallForge = class _CallForge {
|
|
|
497
542
|
clearTimeout(timeoutId);
|
|
498
543
|
}
|
|
499
544
|
}
|
|
545
|
+
getCachedBootstrapToken() {
|
|
546
|
+
var _a;
|
|
547
|
+
const now = Date.now();
|
|
548
|
+
const fromMemory = this.bootstrapMemoryCache;
|
|
549
|
+
if (fromMemory && fromMemory.expiresAt - BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS > now) {
|
|
550
|
+
return fromMemory.token;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
const raw = localStorage.getItem(this.bootstrapCacheKey);
|
|
554
|
+
if (!raw) return null;
|
|
555
|
+
const cached = JSON.parse(raw);
|
|
556
|
+
if (typeof cached.token !== "string" || typeof cached.expiresAt !== "number") {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
if (cached.expiresAt - BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS <= now) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
return cached.token;
|
|
563
|
+
} catch (e) {
|
|
564
|
+
return (_a = fromMemory == null ? void 0 : fromMemory.token) != null ? _a : null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
saveBootstrapToken(token, expiresAt) {
|
|
568
|
+
const cached = {
|
|
569
|
+
token,
|
|
570
|
+
expiresAt,
|
|
571
|
+
tokenVersion: "b1"
|
|
572
|
+
};
|
|
573
|
+
this.bootstrapMemoryCache = cached;
|
|
574
|
+
try {
|
|
575
|
+
localStorage.setItem(this.bootstrapCacheKey, JSON.stringify(cached));
|
|
576
|
+
} catch (e) {
|
|
577
|
+
}
|
|
578
|
+
}
|
|
500
579
|
saveToCache(locationId, data, params) {
|
|
501
580
|
const cached = {
|
|
502
581
|
locId: locationId != null ? locationId : null,
|
|
@@ -562,7 +641,7 @@ var CallForge = class _CallForge {
|
|
|
562
641
|
const data = await this.fetchFromApi(locationId, sessionToken, params);
|
|
563
642
|
this.saveToCache(locationId, data, params);
|
|
564
643
|
}
|
|
565
|
-
buildUrl(locationId, sessionToken, params) {
|
|
644
|
+
buildUrl(locationId, sessionToken, params, bootstrapToken) {
|
|
566
645
|
const { categoryId, endpoint } = this.config;
|
|
567
646
|
const queryParams = {
|
|
568
647
|
categoryId
|
|
@@ -573,6 +652,9 @@ var CallForge = class _CallForge {
|
|
|
573
652
|
if (sessionToken) {
|
|
574
653
|
queryParams.sessionToken = sessionToken;
|
|
575
654
|
}
|
|
655
|
+
if (bootstrapToken) {
|
|
656
|
+
queryParams.bootstrapToken = bootstrapToken;
|
|
657
|
+
}
|
|
576
658
|
for (const [key, value] of Object.entries(params)) {
|
|
577
659
|
if (value !== void 0) {
|
|
578
660
|
queryParams[key] = value;
|
|
@@ -582,6 +664,10 @@ var CallForge = class _CallForge {
|
|
|
582
664
|
const qs = sorted.map((k) => `${k}=${encodeURIComponent(queryParams[k])}`).join("&");
|
|
583
665
|
return `${endpoint}/v1/tracking/session?${qs}`;
|
|
584
666
|
}
|
|
667
|
+
buildBootstrapUrl() {
|
|
668
|
+
const { endpoint, categoryId } = this.config;
|
|
669
|
+
return `${endpoint}/v1/tracking/bootstrap?categoryId=${encodeURIComponent(categoryId)}`;
|
|
670
|
+
}
|
|
585
671
|
buildLocationUrl(locationId) {
|
|
586
672
|
const { endpoint } = this.config;
|
|
587
673
|
if (!locationId) {
|
|
@@ -619,6 +705,7 @@ for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
|
|
|
619
705
|
var site='${config.siteKey || ""}'||location.hostname||'unknown-site';
|
|
620
706
|
var key='cf_tracking_v1_'+site+'_${categoryId}';
|
|
621
707
|
var lkey='cf_location_v1_'+site;
|
|
708
|
+
var bkey='cf_bootstrap_v1_'+site+'_${categoryId}';
|
|
622
709
|
try{
|
|
623
710
|
var cl=JSON.parse(localStorage.getItem(lkey));
|
|
624
711
|
if(cl&&cl.expiresAt>Date.now()+30000){
|
|
@@ -638,12 +725,18 @@ token=(!loc||c.locId===loc)?c.sessionToken:null;
|
|
|
638
725
|
var cp=c.params||{};
|
|
639
726
|
p=Object.assign({},cp,p);
|
|
640
727
|
}}catch(e){}
|
|
728
|
+
var bt=null;
|
|
729
|
+
try{
|
|
730
|
+
var cb=JSON.parse(localStorage.getItem(bkey));
|
|
731
|
+
if(cb&&typeof cb.token==='string'&&cb.expiresAt>Date.now()+10000)bt=cb.token;
|
|
732
|
+
}catch(e){}
|
|
733
|
+
var bp=bt?Promise.resolve({bootstrapToken:bt}):fetch('${endpoint}/v1/tracking/bootstrap?categoryId=${categoryId}',{credentials:'omit'}).then(function(r){if(!r.ok)return null;return r.json()}).then(function(b){if(!b||typeof b.bootstrapToken!=='string'||typeof b.expiresAt!=='number')return null;try{localStorage.setItem(bkey,JSON.stringify({token:b.bootstrapToken,expiresAt:b.expiresAt,tokenVersion:'b1'}))}catch(e){}return b}).catch(function(){return null});
|
|
641
734
|
var url='${endpoint}/v1/tracking/session?categoryId=${categoryId}';
|
|
642
735
|
if(loc)url+='&loc_physical_ms='+loc;
|
|
643
736
|
if(token)url+='&sessionToken='+encodeURIComponent(token);
|
|
644
737
|
var ks=Object.keys(p).sort();
|
|
645
738
|
for(var j=0;j<ks.length;j++)url+='&'+ks[j]+'='+encodeURIComponent(p[ks[j]]);
|
|
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});
|
|
739
|
+
window.__cfTracking=bp.then(function(b){if(b&&b.bootstrapToken)url+='&bootstrapToken='+encodeURIComponent(b.bootstrapToken);return 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});
|
|
647
740
|
})();`.replace(/\n/g, "");
|
|
648
741
|
return `<link rel="preconnect" href="${endpoint}">
|
|
649
742
|
<script>${script}</script>`;
|
package/dist/index.mjs
CHANGED
|
@@ -179,9 +179,11 @@ var LocationCache = class {
|
|
|
179
179
|
var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
|
|
180
180
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
181
181
|
var CALL_INTENT_TIMEOUT_MS = 8e3;
|
|
182
|
+
var BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS = 1e4;
|
|
182
183
|
var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid", "gad_campaignid", "gad_source"];
|
|
183
184
|
var CallForge = class _CallForge {
|
|
184
185
|
constructor(config) {
|
|
186
|
+
this.bootstrapMemoryCache = null;
|
|
185
187
|
this.sessionPromise = null;
|
|
186
188
|
this.locationPromise = null;
|
|
187
189
|
this.customParams = {};
|
|
@@ -191,8 +193,10 @@ var CallForge = class _CallForge {
|
|
|
191
193
|
ga4MeasurementId: config.ga4MeasurementId,
|
|
192
194
|
siteKey: config.siteKey
|
|
193
195
|
};
|
|
196
|
+
const resolvedSiteKey = config.siteKey || (typeof window !== "undefined" ? window.location.hostname : "unknown-site");
|
|
194
197
|
this.cache = new TrackingCache(config.categoryId, config.siteKey);
|
|
195
198
|
this.locationCache = new LocationCache(config.siteKey);
|
|
199
|
+
this.bootstrapCacheKey = `cf_bootstrap_v1_${resolvedSiteKey}_${config.categoryId}`;
|
|
196
200
|
this.captureGA4ClientId();
|
|
197
201
|
this.startGA4ClientIdPolling();
|
|
198
202
|
}
|
|
@@ -440,7 +444,43 @@ var CallForge = class _CallForge {
|
|
|
440
444
|
return params;
|
|
441
445
|
}
|
|
442
446
|
async fetchFromApi(locationId, sessionToken, params) {
|
|
443
|
-
const
|
|
447
|
+
const controller = new AbortController();
|
|
448
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
449
|
+
try {
|
|
450
|
+
let bootstrapToken = this.getCachedBootstrapToken();
|
|
451
|
+
let response = await fetch(
|
|
452
|
+
this.buildUrl(locationId, sessionToken, params, bootstrapToken),
|
|
453
|
+
{
|
|
454
|
+
credentials: "omit",
|
|
455
|
+
signal: controller.signal
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
if (response.status === 401) {
|
|
459
|
+
bootstrapToken = await this.getBootstrapToken(true);
|
|
460
|
+
if (bootstrapToken) {
|
|
461
|
+
response = await fetch(
|
|
462
|
+
this.buildUrl(locationId, sessionToken, params, bootstrapToken),
|
|
463
|
+
{
|
|
464
|
+
credentials: "omit",
|
|
465
|
+
signal: controller.signal
|
|
466
|
+
}
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (!response.ok) {
|
|
471
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
472
|
+
}
|
|
473
|
+
return await response.json();
|
|
474
|
+
} finally {
|
|
475
|
+
clearTimeout(timeoutId);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async getBootstrapToken(forceRefresh = false) {
|
|
479
|
+
const cached = this.getCachedBootstrapToken();
|
|
480
|
+
if (!forceRefresh && cached) {
|
|
481
|
+
return cached;
|
|
482
|
+
}
|
|
483
|
+
const url = this.buildBootstrapUrl();
|
|
444
484
|
const controller = new AbortController();
|
|
445
485
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
446
486
|
try {
|
|
@@ -449,9 +489,14 @@ var CallForge = class _CallForge {
|
|
|
449
489
|
signal: controller.signal
|
|
450
490
|
});
|
|
451
491
|
if (!response.ok) {
|
|
452
|
-
|
|
492
|
+
return null;
|
|
453
493
|
}
|
|
454
|
-
|
|
494
|
+
const data = await response.json();
|
|
495
|
+
if (typeof data.bootstrapToken !== "string" || typeof data.expiresAt !== "number") {
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
this.saveBootstrapToken(data.bootstrapToken, data.expiresAt);
|
|
499
|
+
return data.bootstrapToken;
|
|
455
500
|
} finally {
|
|
456
501
|
clearTimeout(timeoutId);
|
|
457
502
|
}
|
|
@@ -473,6 +518,40 @@ var CallForge = class _CallForge {
|
|
|
473
518
|
clearTimeout(timeoutId);
|
|
474
519
|
}
|
|
475
520
|
}
|
|
521
|
+
getCachedBootstrapToken() {
|
|
522
|
+
var _a;
|
|
523
|
+
const now = Date.now();
|
|
524
|
+
const fromMemory = this.bootstrapMemoryCache;
|
|
525
|
+
if (fromMemory && fromMemory.expiresAt - BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS > now) {
|
|
526
|
+
return fromMemory.token;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const raw = localStorage.getItem(this.bootstrapCacheKey);
|
|
530
|
+
if (!raw) return null;
|
|
531
|
+
const cached = JSON.parse(raw);
|
|
532
|
+
if (typeof cached.token !== "string" || typeof cached.expiresAt !== "number") {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
if (cached.expiresAt - BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS <= now) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
return cached.token;
|
|
539
|
+
} catch (e) {
|
|
540
|
+
return (_a = fromMemory == null ? void 0 : fromMemory.token) != null ? _a : null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
saveBootstrapToken(token, expiresAt) {
|
|
544
|
+
const cached = {
|
|
545
|
+
token,
|
|
546
|
+
expiresAt,
|
|
547
|
+
tokenVersion: "b1"
|
|
548
|
+
};
|
|
549
|
+
this.bootstrapMemoryCache = cached;
|
|
550
|
+
try {
|
|
551
|
+
localStorage.setItem(this.bootstrapCacheKey, JSON.stringify(cached));
|
|
552
|
+
} catch (e) {
|
|
553
|
+
}
|
|
554
|
+
}
|
|
476
555
|
saveToCache(locationId, data, params) {
|
|
477
556
|
const cached = {
|
|
478
557
|
locId: locationId != null ? locationId : null,
|
|
@@ -538,7 +617,7 @@ var CallForge = class _CallForge {
|
|
|
538
617
|
const data = await this.fetchFromApi(locationId, sessionToken, params);
|
|
539
618
|
this.saveToCache(locationId, data, params);
|
|
540
619
|
}
|
|
541
|
-
buildUrl(locationId, sessionToken, params) {
|
|
620
|
+
buildUrl(locationId, sessionToken, params, bootstrapToken) {
|
|
542
621
|
const { categoryId, endpoint } = this.config;
|
|
543
622
|
const queryParams = {
|
|
544
623
|
categoryId
|
|
@@ -549,6 +628,9 @@ var CallForge = class _CallForge {
|
|
|
549
628
|
if (sessionToken) {
|
|
550
629
|
queryParams.sessionToken = sessionToken;
|
|
551
630
|
}
|
|
631
|
+
if (bootstrapToken) {
|
|
632
|
+
queryParams.bootstrapToken = bootstrapToken;
|
|
633
|
+
}
|
|
552
634
|
for (const [key, value] of Object.entries(params)) {
|
|
553
635
|
if (value !== void 0) {
|
|
554
636
|
queryParams[key] = value;
|
|
@@ -558,6 +640,10 @@ var CallForge = class _CallForge {
|
|
|
558
640
|
const qs = sorted.map((k) => `${k}=${encodeURIComponent(queryParams[k])}`).join("&");
|
|
559
641
|
return `${endpoint}/v1/tracking/session?${qs}`;
|
|
560
642
|
}
|
|
643
|
+
buildBootstrapUrl() {
|
|
644
|
+
const { endpoint, categoryId } = this.config;
|
|
645
|
+
return `${endpoint}/v1/tracking/bootstrap?categoryId=${encodeURIComponent(categoryId)}`;
|
|
646
|
+
}
|
|
561
647
|
buildLocationUrl(locationId) {
|
|
562
648
|
const { endpoint } = this.config;
|
|
563
649
|
if (!locationId) {
|
|
@@ -595,6 +681,7 @@ for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
|
|
|
595
681
|
var site='${config.siteKey || ""}'||location.hostname||'unknown-site';
|
|
596
682
|
var key='cf_tracking_v1_'+site+'_${categoryId}';
|
|
597
683
|
var lkey='cf_location_v1_'+site;
|
|
684
|
+
var bkey='cf_bootstrap_v1_'+site+'_${categoryId}';
|
|
598
685
|
try{
|
|
599
686
|
var cl=JSON.parse(localStorage.getItem(lkey));
|
|
600
687
|
if(cl&&cl.expiresAt>Date.now()+30000){
|
|
@@ -614,12 +701,18 @@ token=(!loc||c.locId===loc)?c.sessionToken:null;
|
|
|
614
701
|
var cp=c.params||{};
|
|
615
702
|
p=Object.assign({},cp,p);
|
|
616
703
|
}}catch(e){}
|
|
704
|
+
var bt=null;
|
|
705
|
+
try{
|
|
706
|
+
var cb=JSON.parse(localStorage.getItem(bkey));
|
|
707
|
+
if(cb&&typeof cb.token==='string'&&cb.expiresAt>Date.now()+10000)bt=cb.token;
|
|
708
|
+
}catch(e){}
|
|
709
|
+
var bp=bt?Promise.resolve({bootstrapToken:bt}):fetch('${endpoint}/v1/tracking/bootstrap?categoryId=${categoryId}',{credentials:'omit'}).then(function(r){if(!r.ok)return null;return r.json()}).then(function(b){if(!b||typeof b.bootstrapToken!=='string'||typeof b.expiresAt!=='number')return null;try{localStorage.setItem(bkey,JSON.stringify({token:b.bootstrapToken,expiresAt:b.expiresAt,tokenVersion:'b1'}))}catch(e){}return b}).catch(function(){return null});
|
|
617
710
|
var url='${endpoint}/v1/tracking/session?categoryId=${categoryId}';
|
|
618
711
|
if(loc)url+='&loc_physical_ms='+loc;
|
|
619
712
|
if(token)url+='&sessionToken='+encodeURIComponent(token);
|
|
620
713
|
var ks=Object.keys(p).sort();
|
|
621
714
|
for(var j=0;j<ks.length;j++)url+='&'+ks[j]+'='+encodeURIComponent(p[ks[j]]);
|
|
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});
|
|
715
|
+
window.__cfTracking=bp.then(function(b){if(b&&b.bootstrapToken)url+='&bootstrapToken='+encodeURIComponent(b.bootstrapToken);return 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});
|
|
623
716
|
})();`.replace(/\n/g, "");
|
|
624
717
|
return `<link rel="preconnect" href="${endpoint}">
|
|
625
718
|
<script>${script}</script>`;
|