@callforge/tracking-client 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,17 +19,18 @@ Client integration requirements:
19
19
  - Keep handling `phoneNumber` and `leaseId` separately. A request can return a phone number with `leaseId: null` when lease assignment is intentionally suppressed.
20
20
  - For attribution/scale metrics, treat `leaseId` as the source of truth for lease-backed traffic.
21
21
 
22
- ## Realtime Data Layer Upgrade (v0.10+)
22
+ ## Realtime Data Layer Upgrade (v0.9+)
23
23
 
24
24
  This release adds explicit browser helpers for the new realtime layer:
25
- - `client.linkPhoneCall({ phoneNumber, realtime })` for strict web-click to phone-call linkage plus optional realtime fields.
25
+ - `client.linkPhoneCall({ phoneNumber })` for strict web-click to phone-call linkage.
26
+ - `client.setWebZip(zipCode, { source })` for immediate web ZIP availability to call processing.
26
27
 
27
28
  Integration checklist:
28
29
  - Call `linkPhoneCall` immediately before opening a `tel:` link.
29
30
  - Pass the exact dialed number string (`+1...`) used by the link.
30
31
  - Continue dialing even if `linkPhoneCall` fails (best-effort attribution assist, not UX-blocking).
31
- - When ZIP is known, include it in `linkPhoneCall(...realtime)` as `webZip` and `webZipSource`.
32
- - Use `realtime.params` for additional key/value fields you want persisted with the linked session.
32
+ - Call `setWebZip` when the visitor selects or types a ZIP.
33
+ - Keep `setParams` for broader attribution params; use `setWebZip` for fast ZIP propagation.
33
34
 
34
35
  ## Quick Start
35
36
 
@@ -96,40 +97,32 @@ Notes:
96
97
  - `callIntentToken` is short-lived and single-use.
97
98
  - Treat it as an opaque secret (do not log it).
98
99
 
99
- ### 4. Realtime call-link with bundled realtime fields (recommended)
100
+ ### 4. Realtime call-link + web ZIP helpers (recommended)
100
101
 
101
- Use `linkPhoneCall` to create the strict call-link and optionally attach realtime fields in the same request.
102
+ Use explicit helpers to write realtime data from the browser.
102
103
 
103
104
  ```typescript
104
105
  const client = CallForge.init({ categoryId: 'your-category-id' });
105
106
 
106
- async function dialTrackedNumber(phoneNumber: string, webZip?: string) {
107
+ async function dialTrackedNumber(phoneNumber: string) {
107
108
  try {
108
109
  // Default TTL is 30s (clamped server-side to 5-120s).
109
- await client.linkPhoneCall({
110
- phoneNumber,
111
- realtime: webZip
112
- ? {
113
- webZip,
114
- webZipSource: 'manual', // or 'suggested'
115
- params: {
116
- zipChoiceMethod: 'picker',
117
- },
118
- }
119
- : undefined,
120
- });
110
+ await client.linkPhoneCall({ phoneNumber });
121
111
  } finally {
122
112
  // Do not block dialing on telemetry failure.
123
113
  window.location.href = `tel:${phoneNumber}`;
124
114
  }
125
115
  }
116
+
117
+ // Example ZIP picker handler
118
+ await client.setWebZip('30309', { source: 'manual' });
126
119
  ```
127
120
 
128
121
  Notes:
129
122
  - `linkPhoneCall` is optimized for mobile tap-to-call timing.
130
- - `realtime.webZip` accepts `manual` (typed) and `suggested` (picked from options) via `webZipSource`.
131
- - `realtime.webZip` is validated client-side (`12345` format).
132
- - `realtime.params` allows additional key/value payloads for future realtime enrichment without API changes.
123
+ - `setWebZip` accepts `manual` (typed) and `suggested` (picked from options).
124
+ - `setWebZip` validates ZIP format (`12345`) client-side before sending.
125
+ - `setWebZip` also sends `webZip` as a custom session param in a best-effort sync backup path.
133
126
 
134
127
  ### 5. GA4 Integration
135
128
 
@@ -263,19 +256,12 @@ console.log(intent.callIntentToken);
263
256
 
264
257
  ### `client.linkPhoneCall(input)`
265
258
 
266
- Create a short-lived realtime call-link intent keyed by dialed number, with optional realtime payload fields persisted against the linked session.
259
+ Create a short-lived realtime call-link intent keyed by dialed number.
267
260
 
268
261
  ```typescript
269
262
  const result = await client.linkPhoneCall({
270
263
  phoneNumber: '+13105551234',
271
264
  ttlSeconds: 30, // Optional, default 30s
272
- realtime: {
273
- webZip: '30309', // Optional
274
- webZipSource: 'manual', // Optional, defaults to 'manual' when webZip is provided
275
- params: { // Optional
276
- zipChoiceMethod: 'picker',
277
- },
278
- },
279
265
  });
280
266
 
281
267
  console.log(result.status); // 'ready'
@@ -283,10 +269,25 @@ console.log(result.status); // 'ready'
283
269
 
284
270
  Use this right before `tel:` navigation so inbound call handling can perform strict 1:1 consume.
285
271
 
272
+ ### `client.setWebZip(zipCode, options?)`
273
+
274
+ Store visitor-selected ZIP in the realtime profile layer for immediate call-side enrichment.
275
+
276
+ ```typescript
277
+ const result = await client.setWebZip('30309', {
278
+ source: 'manual', // or 'suggested'
279
+ ttlSeconds: 3600, // Optional
280
+ });
281
+
282
+ if (result.status === 'ready') {
283
+ console.log(result.profile.webZipCode);
284
+ }
285
+ ```
286
+
286
287
  Behavior:
287
- - Rejects invalid `realtime.webZip` values unless they are 5 digits.
288
- - Persists bundled realtime values (`webZip` + `params`) during call-link intent creation.
289
- - Mirrors bundled realtime values into custom params and performs a best-effort session sync backup.
288
+ - Rejects invalid ZIP values unless they are 5 digits.
289
+ - Writes profile data keyed by `sessionId`.
290
+ - Also mirrors `webZip` and `webZipSource` into custom params and triggers a best-effort session sync backup.
290
291
 
291
292
  ### `client.onReady(callback)`
292
293
 
@@ -400,8 +401,7 @@ import type {
400
401
  LocationReadyCallback,
401
402
  CallIntentResponse,
402
403
  CallLinkIntentResponse,
403
- LinkPhoneCallInput,
404
- RealtimeLinkPayload,
404
+ RealtimeProfileResponse,
405
405
  RealtimeProfileSource,
406
406
  } from '@callforge/tracking-client';
407
407
  ```
package/dist/index.d.mts CHANGED
@@ -92,26 +92,18 @@ interface CallLinkIntentResponse {
92
92
  expiresAt: string;
93
93
  }
94
94
  type RealtimeProfileSource = 'manual' | 'suggested';
95
- interface RealtimeLinkPayload {
96
- /**
97
- * Optional web ZIP to persist alongside call-link intent.
98
- * Must be 5 digits.
99
- */
100
- webZip?: string;
101
- /**
102
- * Source for webZip. Defaults to 'manual' when webZip is provided.
103
- */
104
- webZipSource?: RealtimeProfileSource;
105
- /**
106
- * Additional realtime params to persist with the linked session.
107
- */
108
- params?: Record<string, string>;
109
- }
110
- interface LinkPhoneCallInput {
111
- phoneNumber: string;
112
- ttlSeconds?: number;
113
- realtime?: RealtimeLinkPayload;
114
- }
95
+ type RealtimeProfileResponse = {
96
+ status: 'ready';
97
+ profile: {
98
+ sessionId: string;
99
+ webZipCode: string;
100
+ webZipSource: RealtimeProfileSource;
101
+ webZipUpdatedAt: string;
102
+ expiresAt: string;
103
+ };
104
+ } | {
105
+ status: 'unavailable';
106
+ };
115
107
  /**
116
108
  * Callback function for onReady subscription.
117
109
  */
@@ -168,9 +160,17 @@ declare class CallForge {
168
160
  * Create a short-lived realtime call-link intent for a specific dialed number.
169
161
  * Use this immediately before opening a `tel:` link.
170
162
  */
171
- linkPhoneCall(input: LinkPhoneCallInput): Promise<CallLinkIntentResponse>;
172
- private normalizeRealtimeLinkPayload;
173
- private buildRealtimeFallbackParams;
163
+ linkPhoneCall(input: {
164
+ phoneNumber: string;
165
+ ttlSeconds?: number;
166
+ }): Promise<CallLinkIntentResponse>;
167
+ /**
168
+ * Write visitor-selected web ZIP to the realtime data layer.
169
+ */
170
+ setWebZip(zipCode: string, options?: {
171
+ source?: RealtimeProfileSource;
172
+ ttlSeconds?: number;
173
+ }): Promise<RealtimeProfileResponse>;
174
174
  /**
175
175
  * Subscribe to session ready event.
176
176
  * Callback is called once session data is available.
@@ -234,4 +234,4 @@ declare class CallForge {
234
234
  */
235
235
  declare function getPreloadSnippet(config: CallForgeConfig): string;
236
236
 
237
- export { CallForge, type CallForgeConfig, type CallIntentResponse, type CallLinkIntentResponse, type LinkPhoneCallInput, type LocationReadyCallback, type ReadyCallback, type RealtimeLinkPayload, type RealtimeProfileSource, type TrackingLocation, type TrackingLocationSource, type TrackingParams, type TrackingSession, getPreloadSnippet };
237
+ export { CallForge, type CallForgeConfig, type CallIntentResponse, type CallLinkIntentResponse, type LocationReadyCallback, type ReadyCallback, type RealtimeProfileResponse, type RealtimeProfileSource, type TrackingLocation, type TrackingLocationSource, type TrackingParams, type TrackingSession, getPreloadSnippet };
package/dist/index.d.ts CHANGED
@@ -92,26 +92,18 @@ interface CallLinkIntentResponse {
92
92
  expiresAt: string;
93
93
  }
94
94
  type RealtimeProfileSource = 'manual' | 'suggested';
95
- interface RealtimeLinkPayload {
96
- /**
97
- * Optional web ZIP to persist alongside call-link intent.
98
- * Must be 5 digits.
99
- */
100
- webZip?: string;
101
- /**
102
- * Source for webZip. Defaults to 'manual' when webZip is provided.
103
- */
104
- webZipSource?: RealtimeProfileSource;
105
- /**
106
- * Additional realtime params to persist with the linked session.
107
- */
108
- params?: Record<string, string>;
109
- }
110
- interface LinkPhoneCallInput {
111
- phoneNumber: string;
112
- ttlSeconds?: number;
113
- realtime?: RealtimeLinkPayload;
114
- }
95
+ type RealtimeProfileResponse = {
96
+ status: 'ready';
97
+ profile: {
98
+ sessionId: string;
99
+ webZipCode: string;
100
+ webZipSource: RealtimeProfileSource;
101
+ webZipUpdatedAt: string;
102
+ expiresAt: string;
103
+ };
104
+ } | {
105
+ status: 'unavailable';
106
+ };
115
107
  /**
116
108
  * Callback function for onReady subscription.
117
109
  */
@@ -168,9 +160,17 @@ declare class CallForge {
168
160
  * Create a short-lived realtime call-link intent for a specific dialed number.
169
161
  * Use this immediately before opening a `tel:` link.
170
162
  */
171
- linkPhoneCall(input: LinkPhoneCallInput): Promise<CallLinkIntentResponse>;
172
- private normalizeRealtimeLinkPayload;
173
- private buildRealtimeFallbackParams;
163
+ linkPhoneCall(input: {
164
+ phoneNumber: string;
165
+ ttlSeconds?: number;
166
+ }): Promise<CallLinkIntentResponse>;
167
+ /**
168
+ * Write visitor-selected web ZIP to the realtime data layer.
169
+ */
170
+ setWebZip(zipCode: string, options?: {
171
+ source?: RealtimeProfileSource;
172
+ ttlSeconds?: number;
173
+ }): Promise<RealtimeProfileResponse>;
174
174
  /**
175
175
  * Subscribe to session ready event.
176
176
  * Callback is called once session data is available.
@@ -234,4 +234,4 @@ declare class CallForge {
234
234
  */
235
235
  declare function getPreloadSnippet(config: CallForgeConfig): string;
236
236
 
237
- export { CallForge, type CallForgeConfig, type CallIntentResponse, type CallLinkIntentResponse, type LinkPhoneCallInput, type LocationReadyCallback, type ReadyCallback, type RealtimeLinkPayload, type RealtimeProfileSource, type TrackingLocation, type TrackingLocationSource, type TrackingParams, type TrackingSession, getPreloadSnippet };
237
+ export { CallForge, type CallForgeConfig, type CallIntentResponse, type CallLinkIntentResponse, type LocationReadyCallback, type ReadyCallback, type RealtimeProfileResponse, type RealtimeProfileSource, type TrackingLocation, type TrackingLocationSource, type TrackingParams, type TrackingSession, getPreloadSnippet };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
7
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
@@ -17,6 +19,7 @@ var __spreadValues = (a, b) => {
17
19
  }
18
20
  return a;
19
21
  };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
23
  var __export = (target, all) => {
21
24
  for (var name in all)
22
25
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -204,6 +207,7 @@ var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
204
207
  var FETCH_TIMEOUT_MS = 1e4;
205
208
  var CALL_INTENT_TIMEOUT_MS = 8e3;
206
209
  var REALTIME_CALL_LINK_TIMEOUT_MS = 5e3;
210
+ var REALTIME_PROFILE_TIMEOUT_MS = 5e3;
207
211
  var BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS = 1e4;
208
212
  var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid", "gad_campaignid", "gad_source"];
209
213
  var ZIP_CODE_PATTERN = /^\d{5}$/;
@@ -297,11 +301,6 @@ var CallForge = class _CallForge {
297
301
  */
298
302
  async linkPhoneCall(input) {
299
303
  const session = await this.getSession();
300
- const realtime = this.normalizeRealtimeLinkPayload(input.realtime);
301
- const fallbackParams = this.buildRealtimeFallbackParams(realtime);
302
- if (Object.keys(fallbackParams).length > 0) {
303
- void this.setParams(fallbackParams);
304
- }
305
304
  const controller = new AbortController();
306
305
  const timeoutId = setTimeout(() => controller.abort(), REALTIME_CALL_LINK_TIMEOUT_MS);
307
306
  try {
@@ -312,11 +311,11 @@ var CallForge = class _CallForge {
312
311
  },
313
312
  credentials: "omit",
314
313
  signal: controller.signal,
315
- body: JSON.stringify(__spreadValues({
314
+ body: JSON.stringify({
316
315
  sessionToken: session.sessionToken,
317
316
  phoneNumber: input.phoneNumber,
318
317
  ttlSeconds: input.ttlSeconds
319
- }, realtime ? { realtime } : {}))
318
+ })
320
319
  });
321
320
  if (!response.ok) {
322
321
  throw new Error(`Realtime call-link API error: ${response.status} ${response.statusText}`);
@@ -326,59 +325,52 @@ var CallForge = class _CallForge {
326
325
  clearTimeout(timeoutId);
327
326
  }
328
327
  }
329
- normalizeRealtimeLinkPayload(realtime) {
330
- if (!realtime) {
331
- return void 0;
332
- }
333
- const normalized = {};
334
- if (typeof realtime.webZip !== "undefined") {
335
- const normalizedZip = realtime.webZip.trim();
336
- if (!ZIP_CODE_PATTERN.test(normalizedZip)) {
337
- throw new Error("Invalid realtime.webZip. Expected a 5-digit ZIP.");
338
- }
339
- normalized.webZip = normalizedZip;
340
- }
341
- if (typeof realtime.webZipSource !== "undefined") {
342
- if (realtime.webZipSource !== "manual" && realtime.webZipSource !== "suggested") {
343
- throw new Error('Invalid realtime.webZipSource. Expected "manual" or "suggested".');
344
- }
345
- normalized.webZipSource = realtime.webZipSource;
346
- }
347
- if (normalized.webZipSource && !normalized.webZip) {
348
- throw new Error("realtime.webZipSource requires realtime.webZip.");
328
+ /**
329
+ * Write visitor-selected web ZIP to the realtime data layer.
330
+ */
331
+ async setWebZip(zipCode, options) {
332
+ var _a;
333
+ const normalizedZip = zipCode.trim();
334
+ if (!ZIP_CODE_PATTERN.test(normalizedZip)) {
335
+ throw new Error("Invalid ZIP code. Expected a 5-digit ZIP.");
349
336
  }
350
- if (realtime.params) {
351
- const normalizedParams = {};
352
- for (const [key, value] of Object.entries(realtime.params)) {
353
- if (key.trim() === "") {
354
- throw new Error("Invalid realtime.params key. Keys must be non-empty strings.");
355
- }
356
- if (typeof value !== "string") {
357
- throw new Error(`Invalid realtime.params value for key "${key}". Expected string.`);
358
- }
359
- normalizedParams[key] = value;
337
+ const session = await this.getSession();
338
+ const fallbackSource = (_a = options == null ? void 0 : options.source) != null ? _a : "manual";
339
+ void this.setParams({
340
+ webZip: normalizedZip,
341
+ webZipSource: fallbackSource
342
+ });
343
+ const controller = new AbortController();
344
+ const timeoutId = setTimeout(() => controller.abort(), REALTIME_PROFILE_TIMEOUT_MS);
345
+ try {
346
+ const response = await fetch(`${this.config.endpoint}/v1/tracking/realtime-profile`, {
347
+ method: "POST",
348
+ headers: {
349
+ "Content-Type": "application/json"
350
+ },
351
+ credentials: "omit",
352
+ signal: controller.signal,
353
+ body: JSON.stringify({
354
+ sessionToken: session.sessionToken,
355
+ webZip: normalizedZip,
356
+ source: options == null ? void 0 : options.source,
357
+ ttlSeconds: options == null ? void 0 : options.ttlSeconds
358
+ })
359
+ });
360
+ if (!response.ok) {
361
+ throw new Error(`Realtime profile API error: ${response.status} ${response.statusText}`);
360
362
  }
361
- if (Object.keys(normalizedParams).length > 0) {
362
- normalized.params = normalizedParams;
363
+ const result = await response.json();
364
+ if (result.status === "ready") {
365
+ this.customParams = __spreadProps(__spreadValues({}, this.customParams), {
366
+ webZip: result.profile.webZipCode,
367
+ webZipSource: result.profile.webZipSource
368
+ });
363
369
  }
370
+ return result;
371
+ } finally {
372
+ clearTimeout(timeoutId);
364
373
  }
365
- if (normalized.webZip && !normalized.webZipSource) {
366
- normalized.webZipSource = "manual";
367
- }
368
- if (typeof normalized.webZip === "undefined" && typeof normalized.webZipSource === "undefined" && typeof normalized.params === "undefined") {
369
- return void 0;
370
- }
371
- return normalized;
372
- }
373
- buildRealtimeFallbackParams(realtime) {
374
- var _a, _b;
375
- if (!realtime) {
376
- return {};
377
- }
378
- return __spreadValues(__spreadValues({}, (_a = realtime.params) != null ? _a : {}), realtime.webZip ? {
379
- webZip: realtime.webZip,
380
- webZipSource: (_b = realtime.webZipSource) != null ? _b : "manual"
381
- } : {});
382
374
  }
383
375
  /**
384
376
  * Subscribe to session ready event.
@@ -519,14 +511,15 @@ var CallForge = class _CallForge {
519
511
  return this.formatApiResponse(data);
520
512
  }
521
513
  async fetchLocation() {
522
- var _a, _b;
514
+ var _a, _b, _c, _d;
523
515
  const locationId = this.getLocationId();
524
516
  if (typeof window !== "undefined" && window.__cfTrackingLocation) {
525
517
  try {
526
518
  const data2 = await window.__cfTrackingLocation;
527
519
  const dataWithExtras = data2;
528
520
  const effectiveLocId = (_a = dataWithExtras.locId) != null ? _a : locationId;
529
- const location2 = this.toTrackingLocation(data2.location, dataWithExtras.zipOptions);
521
+ const zipOptions = (_c = dataWithExtras.zipOptions) != null ? _c : (_b = data2.location) == null ? void 0 : _b.zipOptions;
522
+ const location2 = this.toTrackingLocation(data2.location, zipOptions);
530
523
  this.saveLocationToCache(effectiveLocId, location2, data2.expiresAt);
531
524
  return location2;
532
525
  } catch (e) {
@@ -534,7 +527,7 @@ var CallForge = class _CallForge {
534
527
  }
535
528
  const cached = this.locationCache.get(locationId);
536
529
  if (cached) {
537
- return this.toTrackingLocation(cached.location, (_b = cached.location) == null ? void 0 : _b.zipOptions);
530
+ return this.toTrackingLocation(cached.location, (_d = cached.location) == null ? void 0 : _d.zipOptions);
538
531
  }
539
532
  const data = await this.fetchLocationFromApi(locationId);
540
533
  const location = this.toTrackingLocation(data.location, data.zipOptions);
package/dist/index.mjs CHANGED
@@ -1,4 +1,6 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
2
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
4
6
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -14,6 +16,7 @@ var __spreadValues = (a, b) => {
14
16
  }
15
17
  return a;
16
18
  };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
17
20
 
18
21
  // src/cache.ts
19
22
  var EXPIRY_BUFFER_MS = 3e4;
@@ -180,6 +183,7 @@ var DEFAULT_ENDPOINT = "https://tracking.callforge.io";
180
183
  var FETCH_TIMEOUT_MS = 1e4;
181
184
  var CALL_INTENT_TIMEOUT_MS = 8e3;
182
185
  var REALTIME_CALL_LINK_TIMEOUT_MS = 5e3;
186
+ var REALTIME_PROFILE_TIMEOUT_MS = 5e3;
183
187
  var BOOTSTRAP_TOKEN_EXPIRY_BUFFER_MS = 1e4;
184
188
  var AUTO_PARAMS = ["gclid", "gbraid", "wbraid", "msclkid", "fbclid", "gad_campaignid", "gad_source"];
185
189
  var ZIP_CODE_PATTERN = /^\d{5}$/;
@@ -273,11 +277,6 @@ var CallForge = class _CallForge {
273
277
  */
274
278
  async linkPhoneCall(input) {
275
279
  const session = await this.getSession();
276
- const realtime = this.normalizeRealtimeLinkPayload(input.realtime);
277
- const fallbackParams = this.buildRealtimeFallbackParams(realtime);
278
- if (Object.keys(fallbackParams).length > 0) {
279
- void this.setParams(fallbackParams);
280
- }
281
280
  const controller = new AbortController();
282
281
  const timeoutId = setTimeout(() => controller.abort(), REALTIME_CALL_LINK_TIMEOUT_MS);
283
282
  try {
@@ -288,11 +287,11 @@ var CallForge = class _CallForge {
288
287
  },
289
288
  credentials: "omit",
290
289
  signal: controller.signal,
291
- body: JSON.stringify(__spreadValues({
290
+ body: JSON.stringify({
292
291
  sessionToken: session.sessionToken,
293
292
  phoneNumber: input.phoneNumber,
294
293
  ttlSeconds: input.ttlSeconds
295
- }, realtime ? { realtime } : {}))
294
+ })
296
295
  });
297
296
  if (!response.ok) {
298
297
  throw new Error(`Realtime call-link API error: ${response.status} ${response.statusText}`);
@@ -302,59 +301,52 @@ var CallForge = class _CallForge {
302
301
  clearTimeout(timeoutId);
303
302
  }
304
303
  }
305
- normalizeRealtimeLinkPayload(realtime) {
306
- if (!realtime) {
307
- return void 0;
308
- }
309
- const normalized = {};
310
- if (typeof realtime.webZip !== "undefined") {
311
- const normalizedZip = realtime.webZip.trim();
312
- if (!ZIP_CODE_PATTERN.test(normalizedZip)) {
313
- throw new Error("Invalid realtime.webZip. Expected a 5-digit ZIP.");
314
- }
315
- normalized.webZip = normalizedZip;
316
- }
317
- if (typeof realtime.webZipSource !== "undefined") {
318
- if (realtime.webZipSource !== "manual" && realtime.webZipSource !== "suggested") {
319
- throw new Error('Invalid realtime.webZipSource. Expected "manual" or "suggested".');
320
- }
321
- normalized.webZipSource = realtime.webZipSource;
322
- }
323
- if (normalized.webZipSource && !normalized.webZip) {
324
- throw new Error("realtime.webZipSource requires realtime.webZip.");
304
+ /**
305
+ * Write visitor-selected web ZIP to the realtime data layer.
306
+ */
307
+ async setWebZip(zipCode, options) {
308
+ var _a;
309
+ const normalizedZip = zipCode.trim();
310
+ if (!ZIP_CODE_PATTERN.test(normalizedZip)) {
311
+ throw new Error("Invalid ZIP code. Expected a 5-digit ZIP.");
325
312
  }
326
- if (realtime.params) {
327
- const normalizedParams = {};
328
- for (const [key, value] of Object.entries(realtime.params)) {
329
- if (key.trim() === "") {
330
- throw new Error("Invalid realtime.params key. Keys must be non-empty strings.");
331
- }
332
- if (typeof value !== "string") {
333
- throw new Error(`Invalid realtime.params value for key "${key}". Expected string.`);
334
- }
335
- normalizedParams[key] = value;
313
+ const session = await this.getSession();
314
+ const fallbackSource = (_a = options == null ? void 0 : options.source) != null ? _a : "manual";
315
+ void this.setParams({
316
+ webZip: normalizedZip,
317
+ webZipSource: fallbackSource
318
+ });
319
+ const controller = new AbortController();
320
+ const timeoutId = setTimeout(() => controller.abort(), REALTIME_PROFILE_TIMEOUT_MS);
321
+ try {
322
+ const response = await fetch(`${this.config.endpoint}/v1/tracking/realtime-profile`, {
323
+ method: "POST",
324
+ headers: {
325
+ "Content-Type": "application/json"
326
+ },
327
+ credentials: "omit",
328
+ signal: controller.signal,
329
+ body: JSON.stringify({
330
+ sessionToken: session.sessionToken,
331
+ webZip: normalizedZip,
332
+ source: options == null ? void 0 : options.source,
333
+ ttlSeconds: options == null ? void 0 : options.ttlSeconds
334
+ })
335
+ });
336
+ if (!response.ok) {
337
+ throw new Error(`Realtime profile API error: ${response.status} ${response.statusText}`);
336
338
  }
337
- if (Object.keys(normalizedParams).length > 0) {
338
- normalized.params = normalizedParams;
339
+ const result = await response.json();
340
+ if (result.status === "ready") {
341
+ this.customParams = __spreadProps(__spreadValues({}, this.customParams), {
342
+ webZip: result.profile.webZipCode,
343
+ webZipSource: result.profile.webZipSource
344
+ });
339
345
  }
346
+ return result;
347
+ } finally {
348
+ clearTimeout(timeoutId);
340
349
  }
341
- if (normalized.webZip && !normalized.webZipSource) {
342
- normalized.webZipSource = "manual";
343
- }
344
- if (typeof normalized.webZip === "undefined" && typeof normalized.webZipSource === "undefined" && typeof normalized.params === "undefined") {
345
- return void 0;
346
- }
347
- return normalized;
348
- }
349
- buildRealtimeFallbackParams(realtime) {
350
- var _a, _b;
351
- if (!realtime) {
352
- return {};
353
- }
354
- return __spreadValues(__spreadValues({}, (_a = realtime.params) != null ? _a : {}), realtime.webZip ? {
355
- webZip: realtime.webZip,
356
- webZipSource: (_b = realtime.webZipSource) != null ? _b : "manual"
357
- } : {});
358
350
  }
359
351
  /**
360
352
  * Subscribe to session ready event.
@@ -495,14 +487,15 @@ var CallForge = class _CallForge {
495
487
  return this.formatApiResponse(data);
496
488
  }
497
489
  async fetchLocation() {
498
- var _a, _b;
490
+ var _a, _b, _c, _d;
499
491
  const locationId = this.getLocationId();
500
492
  if (typeof window !== "undefined" && window.__cfTrackingLocation) {
501
493
  try {
502
494
  const data2 = await window.__cfTrackingLocation;
503
495
  const dataWithExtras = data2;
504
496
  const effectiveLocId = (_a = dataWithExtras.locId) != null ? _a : locationId;
505
- const location2 = this.toTrackingLocation(data2.location, dataWithExtras.zipOptions);
497
+ const zipOptions = (_c = dataWithExtras.zipOptions) != null ? _c : (_b = data2.location) == null ? void 0 : _b.zipOptions;
498
+ const location2 = this.toTrackingLocation(data2.location, zipOptions);
506
499
  this.saveLocationToCache(effectiveLocId, location2, data2.expiresAt);
507
500
  return location2;
508
501
  } catch (e) {
@@ -510,7 +503,7 @@ var CallForge = class _CallForge {
510
503
  }
511
504
  const cached = this.locationCache.get(locationId);
512
505
  if (cached) {
513
- return this.toTrackingLocation(cached.location, (_b = cached.location) == null ? void 0 : _b.zipOptions);
506
+ return this.toTrackingLocation(cached.location, (_d = cached.location) == null ? void 0 : _d.zipOptions);
514
507
  }
515
508
  const data = await this.fetchLocationFromApi(locationId);
516
509
  const location = this.toTrackingLocation(data.location, data.zipOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callforge/tracking-client",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",