@aranova/tracking-react 1.18.0 → 1.19.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.mjs CHANGED
@@ -233,7 +233,8 @@ var TRACKING_CAPABILITIES = [
233
233
  "form_capture",
234
234
  "phone_click_capture",
235
235
  "phone_fields",
236
- "typed_form_submit"
236
+ "typed_form_submit",
237
+ "video_tracking"
237
238
  ];
238
239
  var CAPABILITY_DOM_ATTRIBUTE = "data-aranova-capability";
239
240
  var registered = /* @__PURE__ */ new Set();
@@ -774,8 +775,48 @@ function parseTrigger(value) {
774
775
  if (typeof t.threshold_seconds === "number") spec.threshold_seconds = t.threshold_seconds;
775
776
  if (typeof t.page_threshold === "number") spec.page_threshold = t.page_threshold;
776
777
  if (typeof t.page_name === "string") spec.page_name = t.page_name;
778
+ if (t.metric === "percent" || t.metric === "seconds") spec.metric = t.metric;
779
+ if (typeof t.threshold === "number") spec.threshold = t.threshold;
780
+ if (typeof t.video_id === "string" || t.video_id === null) spec.video_id = t.video_id;
777
781
  return spec;
778
782
  }
783
+ function parseVideoEntry(fallbackKey2, entry) {
784
+ const key = typeof entry.key === "string" && entry.key ? entry.key : fallbackKey2;
785
+ if (!key) return null;
786
+ const host = typeof entry.host === "string" ? entry.host : "";
787
+ const title = typeof entry.title === "string" && entry.title ? entry.title : key;
788
+ const sourcesRaw = isRecord(entry.sources) ? entry.sources : null;
789
+ const desktop = sourcesRaw && typeof sourcesRaw.desktop === "string" ? sourcesRaw.desktop : typeof entry.desktop_uid === "string" ? entry.desktop_uid : "";
790
+ if (!host || !desktop) return null;
791
+ const mobile = sourcesRaw && typeof sourcesRaw.mobile === "string" ? sourcesRaw.mobile : typeof entry.mobile_uid === "string" ? entry.mobile_uid : null;
792
+ const posterTime = typeof entry.poster_time === "number" ? entry.poster_time : typeof entry.poster_time_s === "number" ? entry.poster_time_s : 4;
793
+ return {
794
+ key,
795
+ title,
796
+ host,
797
+ sources: { desktop, mobile },
798
+ poster_time: posterTime,
799
+ duration_s: typeof entry.duration_s === "number" ? entry.duration_s : null
800
+ };
801
+ }
802
+ function parseVideos(value) {
803
+ const out = {};
804
+ if (Array.isArray(value)) {
805
+ for (const item of value) {
806
+ if (!isRecord(item)) continue;
807
+ const parsed = parseVideoEntry("", item);
808
+ if (parsed) out[parsed.key] = parsed;
809
+ }
810
+ return out;
811
+ }
812
+ if (!isRecord(value)) return out;
813
+ for (const [key, entry] of Object.entries(value)) {
814
+ if (!isRecord(entry)) continue;
815
+ const parsed = parseVideoEntry(key, entry);
816
+ if (parsed) out[parsed.key] = parsed;
817
+ }
818
+ return out;
819
+ }
779
820
  function parseConversionConfig(raw) {
780
821
  if (!raw || typeof raw !== "object") return null;
781
822
  const obj = raw;
@@ -826,7 +867,8 @@ function parseConversionConfig(raw) {
826
867
  goals,
827
868
  linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
828
869
  integrations: parseIntegrations(obj.integrations),
829
- lead_forms: parseLeadForms(obj.lead_forms)
870
+ lead_forms: parseLeadForms(obj.lead_forms),
871
+ videos: parseVideos(obj.videos)
830
872
  };
831
873
  }
832
874
  function parseLeadForms(value) {
@@ -1072,6 +1114,21 @@ function automaticThresholdMet(goal, eventType, metadata) {
1072
1114
  case "form_start":
1073
1115
  case "phone_click":
1074
1116
  return true;
1117
+ case "video_play":
1118
+ return trigger.video_id == null || hasStringField(metadata, "video_id") && metadata.video_id === trigger.video_id;
1119
+ case "video_progress": {
1120
+ if (trigger.video_id != null && (!hasStringField(metadata, "video_id") || metadata.video_id !== trigger.video_id)) {
1121
+ return false;
1122
+ }
1123
+ if (typeof trigger.threshold !== "number") return false;
1124
+ if (trigger.metric === "seconds") {
1125
+ return hasNumberField(metadata, "watched_s") && metadata.watched_s >= trigger.threshold;
1126
+ }
1127
+ if (trigger.metric === "percent") {
1128
+ return hasNumberField(metadata, "max_percent") && metadata.max_percent >= trigger.threshold || hasNumberField(metadata, "watched_percent") && metadata.watched_percent >= trigger.threshold;
1129
+ }
1130
+ return false;
1131
+ }
1075
1132
  default:
1076
1133
  return false;
1077
1134
  }
@@ -1131,6 +1188,9 @@ var RETRY_BASE_MS = 5e3;
1131
1188
  var RETRY_MAX_MS = 5 * 6e4;
1132
1189
  var runtimes = /* @__PURE__ */ new Map();
1133
1190
  var configuredIds = /* @__PURE__ */ new Set();
1191
+ var EMPTY_GOALS = [];
1192
+ var EMPTY_STRINGS = [];
1193
+ var EMPTY_VIDEOS = {};
1134
1194
  var scriptLoad = null;
1135
1195
  var jsInitialized = false;
1136
1196
  function cacheKey2(url) {
@@ -1321,11 +1381,11 @@ var TrackingConfigRuntime = class {
1321
1381
  void this.flush();
1322
1382
  }
1323
1383
  listGoals() {
1324
- return this.current?.goals ?? [];
1384
+ return this.current?.goals ?? EMPTY_GOALS;
1325
1385
  }
1326
1386
  /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
1327
1387
  linkedHostnames() {
1328
- return this.current?.linked_hostnames ?? [];
1388
+ return this.current?.linked_hostnames ?? EMPTY_STRINGS;
1329
1389
  }
1330
1390
  /**
1331
1391
  * Integration blocks, from the cached object even while unconfirmed: the booking fallback
@@ -1338,6 +1398,14 @@ var TrackingConfigRuntime = class {
1338
1398
  leadForms() {
1339
1399
  return this.current?.lead_forms ?? null;
1340
1400
  }
1401
+ /** Configured Cloudflare Stream tracked videos for this site. */
1402
+ videos() {
1403
+ return this.current?.videos ?? EMPTY_VIDEOS;
1404
+ }
1405
+ /** Look up one configured Cloudflare Stream tracked video by key. */
1406
+ getVideo(key) {
1407
+ return this.current?.videos?.[key] ?? null;
1408
+ }
1341
1409
  async revalidateNow() {
1342
1410
  try {
1343
1411
  const headers = {};
@@ -1494,6 +1562,16 @@ var TrackingConfigRuntime = class {
1494
1562
  this.scheduleRetry();
1495
1563
  return;
1496
1564
  }
1565
+ if (outcome === "fired" && typeof window !== "undefined" && typeof window.fbq === "function") {
1566
+ try {
1567
+ window.fbq("trackCustom", item.key, {
1568
+ ...value != null ? { value } : {},
1569
+ ...currency ? { currency } : {},
1570
+ ...item.transactionId ? { transaction_id: item.transactionId } : {}
1571
+ });
1572
+ } catch {
1573
+ }
1574
+ }
1497
1575
  this.conversionQueue.shift();
1498
1576
  }
1499
1577
  }
@@ -1716,6 +1794,18 @@ var EVENT_SEMANTICS = {
1716
1794
  category: "page",
1717
1795
  outcomeRole: "navigation",
1718
1796
  clientVisibility: "simple"
1797
+ },
1798
+ video_play: {
1799
+ label: "Video played",
1800
+ category: "engagement",
1801
+ outcomeRole: "engagement",
1802
+ clientVisibility: "simple"
1803
+ },
1804
+ video_progress: {
1805
+ label: "Video watched",
1806
+ category: "engagement",
1807
+ outcomeRole: "engagement",
1808
+ clientVisibility: "detailed"
1719
1809
  }
1720
1810
  };
1721
1811
  function getEventSemantics(eventName) {
@@ -1798,6 +1888,36 @@ var formSubmitMetadataSchema = z2.strictObject({
1798
1888
  });
1799
1889
  var formSubmitConfigSchema = z2.strictObject({});
1800
1890
 
1891
+ // ../tracking-core/src/video-ranges.ts
1892
+ function roundVideoSeconds(value) {
1893
+ if (!Number.isFinite(value) || value < 0) return 0;
1894
+ return Math.round(value * 100) / 100;
1895
+ }
1896
+ function mergeVideoRanges(ranges, epsilonSeconds = 0.25) {
1897
+ const valid = ranges.filter((r) => Number.isFinite(r.start_s) && Number.isFinite(r.end_s) && r.end_s > r.start_s).map((r) => ({
1898
+ start_s: roundVideoSeconds(r.start_s),
1899
+ end_s: roundVideoSeconds(r.end_s)
1900
+ })).filter((r) => r.end_s - r.start_s >= 0.05).sort((a, b) => a.start_s - b.start_s || a.end_s - b.end_s);
1901
+ if (valid.length === 0) return [];
1902
+ const merged = [{ ...valid[0] }];
1903
+ for (let i = 1; i < valid.length; i += 1) {
1904
+ const cur = valid[i];
1905
+ const last = merged[merged.length - 1];
1906
+ if (cur.start_s <= last.end_s + epsilonSeconds) {
1907
+ last.end_s = roundVideoSeconds(Math.max(last.end_s, cur.end_s));
1908
+ } else {
1909
+ merged.push({ ...cur });
1910
+ }
1911
+ }
1912
+ return merged;
1913
+ }
1914
+ function sumVideoRangesSeconds(ranges, durationSeconds = 0) {
1915
+ const merged = mergeVideoRanges(ranges);
1916
+ const raw = merged.reduce((acc, r) => acc + Math.max(0, r.end_s - r.start_s), 0);
1917
+ const clamped = durationSeconds > 0 ? Math.min(raw, durationSeconds) : raw;
1918
+ return roundVideoSeconds(clamped);
1919
+ }
1920
+
1801
1921
  // ../tracking-core/src/ingest-typed.ts
1802
1922
  var clientSettings = /* @__PURE__ */ new WeakMap();
1803
1923
  function typedClientSettings(client) {
@@ -2580,7 +2700,7 @@ var zSaleItemEntitySchema = z4.object({
2580
2700
  external_item_id: z4.nullable(z4.string()),
2581
2701
  id: z4.uuid(),
2582
2702
  name: z4.nullable(z4.string()),
2583
- quantity: z4.string().check(z4.regex(/^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$/)),
2703
+ quantity: z4.string(),
2584
2704
  unit_cost_cents: z4.nullable(z4.int()),
2585
2705
  unit_price_cents: z4.int()
2586
2706
  });
@@ -2702,6 +2822,17 @@ var zApiErrorSchema = z4.object({
2702
2822
  errors: z4._default(z4.nullish(z4.array(zValidationErrorItem)), null),
2703
2823
  request_id: z4._default(z4.nullish(z4.string()), null)
2704
2824
  });
2825
+ var zWritebackCorrectionAction = z4.enum(["retry", "restate_now"]);
2826
+ var zWritebackCorrectionState = z4.enum([
2827
+ "queued",
2828
+ "sent",
2829
+ "restated",
2830
+ "failed",
2831
+ "limit_reached",
2832
+ "voided",
2833
+ "date_changed",
2834
+ "dry_run_ok"
2835
+ ]);
2705
2836
  var zWritebackEmailSource = z4.enum(["NONE", "SALE", "CUSTOMER", "HARVESTED"]);
2706
2837
  var zWritebackEnrichmentStatus = z4.enum(["NONE", "MATCHED", "AMBIGUOUS"]);
2707
2838
  var zWritebackSaleUploadCounts = z4.object({
@@ -2712,9 +2843,28 @@ var zWritebackSaleUploadCounts = z4.object({
2712
2843
  total: z4._default(z4.int(), 0),
2713
2844
  uploaded: z4._default(z4.int(), 0)
2714
2845
  });
2846
+ var zWritebackValueDrift = z4.enum(["none", "open", "failed"]);
2847
+ var zWritebackCorrectionSchema = z4.object({
2848
+ action: z4.nullable(zWritebackCorrectionAction),
2849
+ can_restate: z4._default(z4.boolean(), false),
2850
+ drift: zWritebackValueDrift,
2851
+ error_message: z4.nullable(z4.string()),
2852
+ google_currency: z4.string(),
2853
+ google_value_cents: z4.int(),
2854
+ restated_at: z4.nullable(z4.iso.datetime({ offset: true })),
2855
+ restated_from_cents: z4.nullable(z4.int()),
2856
+ restatement_limit: z4.int(),
2857
+ restatements_used: z4.int(),
2858
+ sale_currency: z4.string(),
2859
+ sale_value_cents: z4.int(),
2860
+ sends_at: z4.nullable(z4.iso.datetime({ offset: true })),
2861
+ state: zWritebackCorrectionState,
2862
+ upload_id: z4.uuid()
2863
+ });
2715
2864
  var zSaleWritebackStatusSchema = z4.object({
2716
2865
  blocked_reason: z4.nullable(z4.string()),
2717
2866
  can_dispatch: z4._default(z4.boolean(), false),
2867
+ correction: z4.nullable(zWritebackCorrectionSchema),
2718
2868
  email_source: z4.nullable(zWritebackEmailSource),
2719
2869
  enrichment_status: z4.nullable(zWritebackEnrichmentStatus),
2720
2870
  gclid_attached: z4._default(z4.boolean(), false),
@@ -2732,7 +2882,6 @@ var zSaleWritebackStatusSchema = z4.object({
2732
2882
  "out_of_window",
2733
2883
  "blocked"
2734
2884
  ]),
2735
- stale: z4._default(z4.boolean(), false),
2736
2885
  uncovered_service_ids: z4.array(z4.uuid()),
2737
2886
  uploads: zWritebackSaleUploadCounts
2738
2887
  });
@@ -2869,6 +3018,14 @@ import {
2869
3018
  useBookingSubmit
2870
3019
  } from "@aranova/tracking-react/client";
2871
3020
  import { createLeadFormSubmit } from "@aranova/tracking-react/client";
3021
+ import {
3022
+ DEFAULT_VIDEO_CHECKPOINTS,
3023
+ streamManifestUrl,
3024
+ streamPosterUrl,
3025
+ TrackedVideo,
3026
+ TrackedVideoStage,
3027
+ useTrackedVideoConfig
3028
+ } from "@aranova/tracking-react/client";
2872
3029
  export {
2873
3030
  AdPlatformTracking,
2874
3031
  AranovaApiError,
@@ -2876,6 +3033,7 @@ export {
2876
3033
  ConsentBanner,
2877
3034
  DEFAULT_DECLINE_TTL_DAYS,
2878
3035
  DEFAULT_PHONE_COUNTRY,
3036
+ DEFAULT_VIDEO_CHECKPOINTS,
2879
3037
  EVENT_SEMANTICS,
2880
3038
  GoogleAdsTracking,
2881
3039
  LEAD_FORM_KINDS,
@@ -2889,6 +3047,8 @@ export {
2889
3047
  SUPPORTED_CURRENCIES,
2890
3048
  TRACKING_PARAM_KEYS,
2891
3049
  TRACKING_RANGES,
3050
+ TrackedVideo,
3051
+ TrackedVideoStage,
2892
3052
  bookableSlots,
2893
3053
  buildBookingHandoffUrl,
2894
3054
  captureTrackingParamsFromLocation,
@@ -2913,6 +3073,7 @@ export {
2913
3073
  getTrackingConfigRuntime,
2914
3074
  groupBookingOptions,
2915
3075
  leadFormPrimitiveSchemas,
3076
+ mergeVideoRanges,
2916
3077
  newBookingAttemptKey,
2917
3078
  onConsentChange,
2918
3079
  optIn,
@@ -2922,6 +3083,7 @@ export {
2922
3083
  resetConsent,
2923
3084
  resolveConversionConfig,
2924
3085
  resolveTrackingConfigUrl,
3086
+ roundVideoSeconds,
2925
3087
  saleCreateSchema,
2926
3088
  saleItemSchema,
2927
3089
  saleServiceSchema,
@@ -2931,6 +3093,9 @@ export {
2931
3093
  shopToday,
2932
3094
  slotFields,
2933
3095
  stashUserData,
3096
+ streamManifestUrl,
3097
+ streamPosterUrl,
3098
+ sumVideoRangesSeconds,
2934
3099
  timeWindow,
2935
3100
  toE164,
2936
3101
  toMinor,
@@ -2944,6 +3109,7 @@ export {
2944
3109
  useGclid,
2945
3110
  usePhoneConfig,
2946
3111
  usePhoneField,
3112
+ useTrackedVideoConfig,
2947
3113
  useTrackingParams
2948
3114
  };
2949
3115
  //# sourceMappingURL=index.mjs.map