@callforge/tracking-client 0.1.0 → 0.3.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/dist/index.d.mts CHANGED
@@ -77,6 +77,10 @@ declare class CallForge {
77
77
  private readonly cache;
78
78
  private sessionPromise;
79
79
  private customParams;
80
+ private ga4PollInterval;
81
+ private ga4PollTimeout;
82
+ private sessionId;
83
+ private sessionCreated;
80
84
  private constructor();
81
85
  /**
82
86
  * Initialize the CallForge tracking client.
@@ -94,9 +98,22 @@ declare class CallForge {
94
98
  onReady(callback: ReadyCallback): void;
95
99
  /**
96
100
  * Set custom tracking parameters.
97
- * Merges with existing params (new values override).
101
+ * If session already exists, sends PATCH to update.
102
+ * Otherwise queues params for next session request.
98
103
  */
99
- setParams(params: Record<string, string>): void;
104
+ setParams(params: Record<string, string>): Promise<void>;
105
+ /**
106
+ * Get the currently queued custom params.
107
+ */
108
+ getQueuedParams(): TrackingParams;
109
+ private patchSession;
110
+ /**
111
+ * Start polling for the GA4 _ga cookie.
112
+ * If found immediately, calls setParams right away.
113
+ * Otherwise polls every 500ms for up to 10 seconds.
114
+ */
115
+ startGA4CookiePolling(): void;
116
+ private stopGA4CookiePolling;
100
117
  private fetchSession;
101
118
  private getLocationId;
102
119
  private getAutoParams;
package/dist/index.d.ts CHANGED
@@ -77,6 +77,10 @@ declare class CallForge {
77
77
  private readonly cache;
78
78
  private sessionPromise;
79
79
  private customParams;
80
+ private ga4PollInterval;
81
+ private ga4PollTimeout;
82
+ private sessionId;
83
+ private sessionCreated;
80
84
  private constructor();
81
85
  /**
82
86
  * Initialize the CallForge tracking client.
@@ -94,9 +98,22 @@ declare class CallForge {
94
98
  onReady(callback: ReadyCallback): void;
95
99
  /**
96
100
  * Set custom tracking parameters.
97
- * Merges with existing params (new values override).
101
+ * If session already exists, sends PATCH to update.
102
+ * Otherwise queues params for next session request.
98
103
  */
99
- setParams(params: Record<string, string>): void;
104
+ setParams(params: Record<string, string>): Promise<void>;
105
+ /**
106
+ * Get the currently queued custom params.
107
+ */
108
+ getQueuedParams(): TrackingParams;
109
+ private patchSession;
110
+ /**
111
+ * Start polling for the GA4 _ga cookie.
112
+ * If found immediately, calls setParams right away.
113
+ * Otherwise polls every 500ms for up to 10 seconds.
114
+ */
115
+ startGA4CookiePolling(): void;
116
+ private stopGA4CookiePolling;
100
117
  private fetchSession;
101
118
  private getLocationId;
102
119
  private getAutoParams;
package/dist/index.js CHANGED
@@ -129,17 +129,27 @@ var TrackingCache = class {
129
129
 
130
130
  // src/client.ts
131
131
  var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
132
+ function getGA4ClientId() {
133
+ if (typeof document === "undefined") return null;
134
+ const match = document.cookie.match(/_ga=GA\d+\.\d+\.(.+?)(?:;|$)/);
135
+ return match ? match[1] : null;
136
+ }
132
137
  var FETCH_TIMEOUT_MS = 1e4;
133
138
  var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid"];
134
139
  var CallForge = class _CallForge {
135
140
  constructor(config) {
136
141
  this.sessionPromise = null;
137
142
  this.customParams = {};
143
+ this.ga4PollInterval = null;
144
+ this.ga4PollTimeout = null;
145
+ this.sessionId = null;
146
+ this.sessionCreated = false;
138
147
  this.config = {
139
148
  categoryId: config.categoryId,
140
149
  endpoint: config.endpoint || DEFAULT_ENDPOINT
141
150
  };
142
151
  this.cache = new TrackingCache(config.categoryId);
152
+ this.startGA4CookiePolling();
143
153
  }
144
154
  /**
145
155
  * Initialize the CallForge tracking client.
@@ -168,34 +178,103 @@ var CallForge = class _CallForge {
168
178
  }
169
179
  /**
170
180
  * Set custom tracking parameters.
171
- * Merges with existing params (new values override).
181
+ * If session already exists, sends PATCH to update.
182
+ * Otherwise queues params for next session request.
172
183
  */
173
- setParams(params) {
184
+ async setParams(params) {
174
185
  this.customParams = __spreadValues(__spreadValues({}, this.customParams), params);
186
+ if (this.sessionCreated && this.sessionId) {
187
+ await this.patchSession(params);
188
+ }
189
+ }
190
+ /**
191
+ * Get the currently queued custom params.
192
+ */
193
+ getQueuedParams() {
194
+ return __spreadValues({}, this.customParams);
195
+ }
196
+ async patchSession(params) {
197
+ try {
198
+ const response = await fetch(
199
+ `${this.config.endpoint}/v1/tracking/session/${this.sessionId}`,
200
+ {
201
+ method: "PATCH",
202
+ headers: { "Content-Type": "application/json" },
203
+ body: JSON.stringify(params)
204
+ }
205
+ );
206
+ if (!response.ok) {
207
+ console.warn("[CallForge] Failed to patch session:", response.status);
208
+ }
209
+ } catch (error) {
210
+ console.warn("[CallForge] Failed to patch session:", error);
211
+ }
212
+ }
213
+ /**
214
+ * Start polling for the GA4 _ga cookie.
215
+ * If found immediately, calls setParams right away.
216
+ * Otherwise polls every 500ms for up to 10 seconds.
217
+ */
218
+ startGA4CookiePolling() {
219
+ if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
220
+ return;
221
+ }
222
+ const clientId = getGA4ClientId();
223
+ if (clientId) {
224
+ this.setParams({ ga4ClientId: clientId });
225
+ return;
226
+ }
227
+ this.ga4PollInterval = setInterval(() => {
228
+ const clientId2 = getGA4ClientId();
229
+ if (clientId2) {
230
+ this.setParams({ ga4ClientId: clientId2 });
231
+ this.stopGA4CookiePolling();
232
+ }
233
+ }, 500);
234
+ this.ga4PollTimeout = setTimeout(() => {
235
+ this.stopGA4CookiePolling();
236
+ }, 1e4);
237
+ }
238
+ stopGA4CookiePolling() {
239
+ if (this.ga4PollInterval) {
240
+ clearInterval(this.ga4PollInterval);
241
+ this.ga4PollInterval = null;
242
+ }
243
+ if (this.ga4PollTimeout) {
244
+ clearTimeout(this.ga4PollTimeout);
245
+ this.ga4PollTimeout = null;
246
+ }
175
247
  }
176
248
  async fetchSession() {
177
249
  const locationId = this.getLocationId();
178
- if (!locationId) {
179
- return { sessionId: null, phoneNumber: null, location: null };
180
- }
181
- const cached = this.cache.get(locationId);
182
- if (cached) {
183
- return this.formatSession(cached);
250
+ if (locationId) {
251
+ const cached = this.cache.get(locationId);
252
+ if (cached) {
253
+ this.sessionId = cached.sessionId;
254
+ this.sessionCreated = true;
255
+ return this.formatSession(cached);
256
+ }
184
257
  }
185
258
  const autoParams = this.getAutoParams();
186
259
  const cachedParams = this.cache.getParams();
187
260
  const params = __spreadValues(__spreadValues(__spreadValues({}, autoParams), cachedParams), this.customParams);
188
- if (typeof window !== "undefined" && window.__cfTracking) {
261
+ if (locationId && typeof window !== "undefined" && window.__cfTracking) {
189
262
  try {
190
263
  const data2 = await window.__cfTracking;
191
264
  this.saveToCache(locationId, data2, params);
265
+ this.sessionId = data2.sessionId;
266
+ this.sessionCreated = true;
192
267
  return this.formatApiResponse(data2);
193
268
  } catch (e) {
194
269
  }
195
270
  }
196
- const sessionId = this.cache.getSessionId(locationId);
197
- const data = await this.fetchFromApi(locationId, sessionId, params);
198
- this.saveToCache(locationId, data, params);
271
+ const cachedSessionId = locationId ? this.cache.getSessionId(locationId) : null;
272
+ const data = await this.fetchFromApi(locationId, cachedSessionId, params);
273
+ if (locationId) {
274
+ this.saveToCache(locationId, data, params);
275
+ }
276
+ this.sessionId = data.sessionId;
277
+ this.sessionCreated = true;
199
278
  return this.formatApiResponse(data);
200
279
  }
201
280
  getLocationId() {
@@ -260,9 +339,11 @@ var CallForge = class _CallForge {
260
339
  buildUrl(locationId, sessionId, params) {
261
340
  const { categoryId, endpoint } = this.config;
262
341
  const queryParams = {
263
- categoryId,
264
- loc_physical_ms: locationId
342
+ categoryId
265
343
  };
344
+ if (locationId) {
345
+ queryParams.loc_physical_ms = locationId;
346
+ }
266
347
  if (sessionId) {
267
348
  queryParams.sessionId = sessionId;
268
349
  }
@@ -298,23 +379,24 @@ function getPreloadSnippet(config) {
298
379
  const script = `(function(){
299
380
  var u=new URLSearchParams(location.search);
300
381
  var loc=u.get('loc_physical_ms');
301
- if(!loc)return;
302
382
  var ap=['gclid','gbraid','wbraid','msclkid','fbclid'];
303
383
  var p={};
304
384
  for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
305
385
  var key='${cacheKey}';
306
- try{
386
+ var sid=null;
387
+ if(loc){try{
307
388
  var c=JSON.parse(localStorage.getItem(key));
308
389
  if(c&&c.locId===loc&&c.expiresAt>Date.now()+30000){c.params=Object.assign({},c.params,p);window.__cfTracking=Promise.resolve(c);return}
309
- var sid=(c&&c.locId===loc)?c.sessionId:null;
390
+ sid=(c&&c.locId===loc)?c.sessionId:null;
310
391
  var cp=c&&c.params||{};
311
392
  p=Object.assign({},p,cp);
312
- }catch(e){}
313
- var url='${endpoint}/v1/tracking/session?categoryId=${categoryId}&loc_physical_ms='+loc;
393
+ }catch(e){}}
394
+ var url='${endpoint}/v1/tracking/session?categoryId=${categoryId}';
395
+ if(loc)url+='&loc_physical_ms='+loc;
314
396
  if(sid)url+='&sessionId='+sid;
315
397
  var ks=Object.keys(p).sort();
316
398
  for(var j=0;j<ks.length;j++)url+='&'+ks[j]+'='+encodeURIComponent(p[ks[j]]);
317
- window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.json()}).then(function(d){d.params=p;try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionId:d.sessionId,phoneNumber:d.phoneNumber,location:d.location,expiresAt:d.expiresAt,params:p}))}catch(e){}return d});
399
+ window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.json()}).then(function(d){d.params=p;if(loc){try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionId:d.sessionId,phoneNumber:d.phoneNumber,location:d.location,expiresAt:d.expiresAt,params:p}))}catch(e){}}return d});
318
400
  })();`.replace(/\n/g, "");
319
401
  return `<link rel="preconnect" href="${endpoint}">
320
402
  <script>${script}</script>`;
package/dist/index.mjs CHANGED
@@ -105,17 +105,27 @@ var TrackingCache = class {
105
105
 
106
106
  // src/client.ts
107
107
  var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
108
+ function getGA4ClientId() {
109
+ if (typeof document === "undefined") return null;
110
+ const match = document.cookie.match(/_ga=GA\d+\.\d+\.(.+?)(?:;|$)/);
111
+ return match ? match[1] : null;
112
+ }
108
113
  var FETCH_TIMEOUT_MS = 1e4;
109
114
  var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid"];
110
115
  var CallForge = class _CallForge {
111
116
  constructor(config) {
112
117
  this.sessionPromise = null;
113
118
  this.customParams = {};
119
+ this.ga4PollInterval = null;
120
+ this.ga4PollTimeout = null;
121
+ this.sessionId = null;
122
+ this.sessionCreated = false;
114
123
  this.config = {
115
124
  categoryId: config.categoryId,
116
125
  endpoint: config.endpoint || DEFAULT_ENDPOINT
117
126
  };
118
127
  this.cache = new TrackingCache(config.categoryId);
128
+ this.startGA4CookiePolling();
119
129
  }
120
130
  /**
121
131
  * Initialize the CallForge tracking client.
@@ -144,34 +154,103 @@ var CallForge = class _CallForge {
144
154
  }
145
155
  /**
146
156
  * Set custom tracking parameters.
147
- * Merges with existing params (new values override).
157
+ * If session already exists, sends PATCH to update.
158
+ * Otherwise queues params for next session request.
148
159
  */
149
- setParams(params) {
160
+ async setParams(params) {
150
161
  this.customParams = __spreadValues(__spreadValues({}, this.customParams), params);
162
+ if (this.sessionCreated && this.sessionId) {
163
+ await this.patchSession(params);
164
+ }
165
+ }
166
+ /**
167
+ * Get the currently queued custom params.
168
+ */
169
+ getQueuedParams() {
170
+ return __spreadValues({}, this.customParams);
171
+ }
172
+ async patchSession(params) {
173
+ try {
174
+ const response = await fetch(
175
+ `${this.config.endpoint}/v1/tracking/session/${this.sessionId}`,
176
+ {
177
+ method: "PATCH",
178
+ headers: { "Content-Type": "application/json" },
179
+ body: JSON.stringify(params)
180
+ }
181
+ );
182
+ if (!response.ok) {
183
+ console.warn("[CallForge] Failed to patch session:", response.status);
184
+ }
185
+ } catch (error) {
186
+ console.warn("[CallForge] Failed to patch session:", error);
187
+ }
188
+ }
189
+ /**
190
+ * Start polling for the GA4 _ga cookie.
191
+ * If found immediately, calls setParams right away.
192
+ * Otherwise polls every 500ms for up to 10 seconds.
193
+ */
194
+ startGA4CookiePolling() {
195
+ if (this.ga4PollInterval !== null || this.ga4PollTimeout !== null) {
196
+ return;
197
+ }
198
+ const clientId = getGA4ClientId();
199
+ if (clientId) {
200
+ this.setParams({ ga4ClientId: clientId });
201
+ return;
202
+ }
203
+ this.ga4PollInterval = setInterval(() => {
204
+ const clientId2 = getGA4ClientId();
205
+ if (clientId2) {
206
+ this.setParams({ ga4ClientId: clientId2 });
207
+ this.stopGA4CookiePolling();
208
+ }
209
+ }, 500);
210
+ this.ga4PollTimeout = setTimeout(() => {
211
+ this.stopGA4CookiePolling();
212
+ }, 1e4);
213
+ }
214
+ stopGA4CookiePolling() {
215
+ if (this.ga4PollInterval) {
216
+ clearInterval(this.ga4PollInterval);
217
+ this.ga4PollInterval = null;
218
+ }
219
+ if (this.ga4PollTimeout) {
220
+ clearTimeout(this.ga4PollTimeout);
221
+ this.ga4PollTimeout = null;
222
+ }
151
223
  }
152
224
  async fetchSession() {
153
225
  const locationId = this.getLocationId();
154
- if (!locationId) {
155
- return { sessionId: null, phoneNumber: null, location: null };
156
- }
157
- const cached = this.cache.get(locationId);
158
- if (cached) {
159
- return this.formatSession(cached);
226
+ if (locationId) {
227
+ const cached = this.cache.get(locationId);
228
+ if (cached) {
229
+ this.sessionId = cached.sessionId;
230
+ this.sessionCreated = true;
231
+ return this.formatSession(cached);
232
+ }
160
233
  }
161
234
  const autoParams = this.getAutoParams();
162
235
  const cachedParams = this.cache.getParams();
163
236
  const params = __spreadValues(__spreadValues(__spreadValues({}, autoParams), cachedParams), this.customParams);
164
- if (typeof window !== "undefined" && window.__cfTracking) {
237
+ if (locationId && typeof window !== "undefined" && window.__cfTracking) {
165
238
  try {
166
239
  const data2 = await window.__cfTracking;
167
240
  this.saveToCache(locationId, data2, params);
241
+ this.sessionId = data2.sessionId;
242
+ this.sessionCreated = true;
168
243
  return this.formatApiResponse(data2);
169
244
  } catch (e) {
170
245
  }
171
246
  }
172
- const sessionId = this.cache.getSessionId(locationId);
173
- const data = await this.fetchFromApi(locationId, sessionId, params);
174
- this.saveToCache(locationId, data, params);
247
+ const cachedSessionId = locationId ? this.cache.getSessionId(locationId) : null;
248
+ const data = await this.fetchFromApi(locationId, cachedSessionId, params);
249
+ if (locationId) {
250
+ this.saveToCache(locationId, data, params);
251
+ }
252
+ this.sessionId = data.sessionId;
253
+ this.sessionCreated = true;
175
254
  return this.formatApiResponse(data);
176
255
  }
177
256
  getLocationId() {
@@ -236,9 +315,11 @@ var CallForge = class _CallForge {
236
315
  buildUrl(locationId, sessionId, params) {
237
316
  const { categoryId, endpoint } = this.config;
238
317
  const queryParams = {
239
- categoryId,
240
- loc_physical_ms: locationId
318
+ categoryId
241
319
  };
320
+ if (locationId) {
321
+ queryParams.loc_physical_ms = locationId;
322
+ }
242
323
  if (sessionId) {
243
324
  queryParams.sessionId = sessionId;
244
325
  }
@@ -274,23 +355,24 @@ function getPreloadSnippet(config) {
274
355
  const script = `(function(){
275
356
  var u=new URLSearchParams(location.search);
276
357
  var loc=u.get('loc_physical_ms');
277
- if(!loc)return;
278
358
  var ap=['gclid','gbraid','wbraid','msclkid','fbclid'];
279
359
  var p={};
280
360
  for(var i=0;i<ap.length;i++){var v=u.get(ap[i]);if(v)p[ap[i]]=v}
281
361
  var key='${cacheKey}';
282
- try{
362
+ var sid=null;
363
+ if(loc){try{
283
364
  var c=JSON.parse(localStorage.getItem(key));
284
365
  if(c&&c.locId===loc&&c.expiresAt>Date.now()+30000){c.params=Object.assign({},c.params,p);window.__cfTracking=Promise.resolve(c);return}
285
- var sid=(c&&c.locId===loc)?c.sessionId:null;
366
+ sid=(c&&c.locId===loc)?c.sessionId:null;
286
367
  var cp=c&&c.params||{};
287
368
  p=Object.assign({},p,cp);
288
- }catch(e){}
289
- var url='${endpoint}/v1/tracking/session?categoryId=${categoryId}&loc_physical_ms='+loc;
369
+ }catch(e){}}
370
+ var url='${endpoint}/v1/tracking/session?categoryId=${categoryId}';
371
+ if(loc)url+='&loc_physical_ms='+loc;
290
372
  if(sid)url+='&sessionId='+sid;
291
373
  var ks=Object.keys(p).sort();
292
374
  for(var j=0;j<ks.length;j++)url+='&'+ks[j]+'='+encodeURIComponent(p[ks[j]]);
293
- window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.json()}).then(function(d){d.params=p;try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionId:d.sessionId,phoneNumber:d.phoneNumber,location:d.location,expiresAt:d.expiresAt,params:p}))}catch(e){}return d});
375
+ window.__cfTracking=fetch(url,{credentials:'omit'}).then(function(r){return r.json()}).then(function(d){d.params=p;if(loc){try{localStorage.setItem(key,JSON.stringify({locId:loc,sessionId:d.sessionId,phoneNumber:d.phoneNumber,location:d.location,expiresAt:d.expiresAt,params:p}))}catch(e){}}return d});
294
376
  })();`.replace(/\n/g, "");
295
377
  return `<link rel="preconnect" href="${endpoint}">
296
378
  <script>${script}</script>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callforge/tracking-client",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",