@aranova/tracking-react 0.13.0 → 0.14.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/sales.js CHANGED
@@ -38,6 +38,90 @@ __export(sales_exports, {
38
38
  });
39
39
  module.exports = __toCommonJS(sales_exports);
40
40
 
41
+ // ../tracking-core/src/consent.ts
42
+ var CONSENT_STATE_KEY = "consent_state";
43
+ var grantedListeners = /* @__PURE__ */ new Set();
44
+ function onConsentGranted(listener) {
45
+ grantedListeners.add(listener);
46
+ return () => {
47
+ grantedListeners.delete(listener);
48
+ };
49
+ }
50
+ function getConsentState() {
51
+ if (typeof window === "undefined") return "pending";
52
+ try {
53
+ const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
54
+ if (storedState === "granted" || storedState === "denied") return storedState;
55
+ } catch {
56
+ }
57
+ return "pending";
58
+ }
59
+
60
+ // ../tracking-core/src/gtag.ts
61
+ var SEND_TO_RE = /^AW-[A-Za-z0-9]+\/[A-Za-z0-9_-]+$/;
62
+ function isValidSendTo(sendTo) {
63
+ return SEND_TO_RE.test(sendTo);
64
+ }
65
+ function fireGtagConversion(input) {
66
+ if (typeof window === "undefined" || typeof window.gtag !== "function") return false;
67
+ if (!isValidSendTo(input.sendTo)) return false;
68
+ const params = { send_to: input.sendTo };
69
+ if (input.value != null) params.value = input.value;
70
+ if (input.currency) params.currency = input.currency;
71
+ if (input.transactionId) params.transaction_id = input.transactionId;
72
+ try {
73
+ window.gtag("event", "conversion", params);
74
+ return true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ // ../tracking-core/src/resources/conversion-firing.ts
81
+ var DEDUP_PREFIX = "_aranova_conv_";
82
+ var MAX_PENDING = 100;
83
+ var pendingQueue = [];
84
+ function dedupKey(input) {
85
+ return `${DEDUP_PREFIX}${input.transactionId ?? ""}:${input.sendTo}`;
86
+ }
87
+ function alreadyFired(input) {
88
+ if (!input.transactionId || typeof window === "undefined") return false;
89
+ try {
90
+ return window.sessionStorage.getItem(dedupKey(input)) !== null;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function markFired(input) {
96
+ if (!input.transactionId || typeof window === "undefined") return;
97
+ try {
98
+ window.sessionStorage.setItem(dedupKey(input), "1");
99
+ } catch {
100
+ }
101
+ }
102
+ function fireOnce(input) {
103
+ if (alreadyFired(input)) return;
104
+ if (fireGtagConversion(input)) markFired(input);
105
+ }
106
+ function fireConversionWithConsent(input) {
107
+ const state = getConsentState();
108
+ if (state === "denied") return;
109
+ if (state === "pending") {
110
+ if (pendingQueue.length >= MAX_PENDING) pendingQueue.shift();
111
+ pendingQueue.push(input);
112
+ return;
113
+ }
114
+ fireOnce(input);
115
+ }
116
+ function flushPendingConversions() {
117
+ if (getConsentState() !== "granted") return;
118
+ while (pendingQueue.length > 0) {
119
+ const input = pendingQueue.shift();
120
+ if (input) fireOnce(input);
121
+ }
122
+ }
123
+ if (typeof window !== "undefined") onConsentGranted(flushPendingConversions);
124
+
41
125
  // ../tracking-core/src/events/page-view.ts
42
126
  var import_zod = require("zod");
43
127
  var pageViewMetadataSchema = import_zod.z.object({
@@ -121,22 +205,98 @@ async function salesRequest(config, method, path, body) {
121
205
  return await response.json();
122
206
  }
123
207
 
208
+ // ../tracking-core/src/resources/sales/money.ts
209
+ var MINOR_UNIT_EXPONENT = {
210
+ USD: 2,
211
+ CAD: 2
212
+ };
213
+ function exponentFor(currency) {
214
+ return MINOR_UNIT_EXPONENT[currency] ?? 2;
215
+ }
216
+ function toMinor(amount, currency) {
217
+ return Math.round(amount * 10 ** exponentFor(currency));
218
+ }
219
+ function fromMinor(cents, currency) {
220
+ return cents / 10 ** exponentFor(currency);
221
+ }
222
+ function formatMoney(cents, currency, locale) {
223
+ return new Intl.NumberFormat(locale, { style: "currency", currency }).format(
224
+ fromMinor(cents, currency)
225
+ );
226
+ }
227
+ function formatDateInTz(iso, timeZone, opts, locale) {
228
+ const date = new Date(iso);
229
+ if (Number.isNaN(date.getTime())) return iso;
230
+ return new Intl.DateTimeFormat(locale, {
231
+ year: "numeric",
232
+ month: "short",
233
+ day: "2-digit",
234
+ hour: "2-digit",
235
+ minute: "2-digit",
236
+ ...opts,
237
+ timeZone
238
+ }).format(date);
239
+ }
240
+
124
241
  // ../tracking-core/src/resources/sales/client.ts
242
+ function fireRecordedConversions(firing, input, recorded, sale, currency) {
243
+ if (!firing) return;
244
+ const txnBase = input.external_id ?? sale.id;
245
+ for (const item of recorded) {
246
+ if (!item.service) continue;
247
+ const config = firing.getFiring(item.service);
248
+ if (!config) continue;
249
+ const cents = item.amount_cents ?? config.value_cents ?? null;
250
+ fireConversionWithConsent({
251
+ sendTo: config.send_to,
252
+ value: cents != null ? fromMinor(cents, currency) : null,
253
+ currency: config.currency ?? currency,
254
+ transactionId: `${txnBase}:${item.service}`
255
+ });
256
+ }
257
+ }
125
258
  function createSalesClient(config) {
126
- return {
127
- async record(input) {
128
- const currency = input.currency ?? config.defaultCurrency;
129
- if (!currency) {
130
- throw new Error(
131
- "record: `currency` is required (pass it on the sale or set config.defaultCurrency)"
132
- );
259
+ async function record(input) {
260
+ const currency = input.currency ?? config.defaultCurrency;
261
+ if (!currency) {
262
+ throw new Error(
263
+ "record: `currency` is required (pass it on the sale or set config.defaultCurrency)"
264
+ );
265
+ }
266
+ const body = {
267
+ ...input,
268
+ currency,
269
+ occurred_at: input.occurred_at ?? (/* @__PURE__ */ new Date()).toISOString()
270
+ };
271
+ const sale = await salesRequest(config, "POST", "/sales", body);
272
+ const recorded = input.services?.length ? input.services.map((s) => ({
273
+ service: s.service,
274
+ amount_cents: s.amount_cents
275
+ })) : [
276
+ {
277
+ service: input.service,
278
+ amount_cents: input.amount_total_cents ?? null
133
279
  }
134
- const body = {
135
- ...input,
280
+ ];
281
+ fireRecordedConversions(config.firing, input, recorded, sale, currency);
282
+ return sale;
283
+ }
284
+ return {
285
+ record,
286
+ // recordSale is the intent-revealing alias — same behavior, clearer call site.
287
+ recordSale: record,
288
+ trackConversion(key, options) {
289
+ const firing = config.firing?.getFiring(key);
290
+ if (!firing) return;
291
+ const currency = firing.currency ?? options?.currency ?? config.defaultCurrency ?? null;
292
+ const cents = firing.value_cents ?? null;
293
+ const value = options?.value ?? (cents != null && currency ? fromMinor(cents, currency) : null);
294
+ fireConversionWithConsent({
295
+ sendTo: firing.send_to,
296
+ value,
136
297
  currency,
137
- occurred_at: input.occurred_at ?? (/* @__PURE__ */ new Date()).toISOString()
138
- };
139
- return salesRequest(config, "POST", "/sales", body);
298
+ transactionId: options?.transactionId ?? null
299
+ });
140
300
  },
141
301
  async list(query) {
142
302
  const { cursor, limit, sort, order, want_total, ...filters } = query ?? {};
@@ -230,39 +390,6 @@ function createSalesClient(config) {
230
390
  };
231
391
  }
232
392
 
233
- // ../tracking-core/src/resources/sales/money.ts
234
- var MINOR_UNIT_EXPONENT = {
235
- USD: 2,
236
- CAD: 2
237
- };
238
- function exponentFor(currency) {
239
- return MINOR_UNIT_EXPONENT[currency] ?? 2;
240
- }
241
- function toMinor(amount, currency) {
242
- return Math.round(amount * 10 ** exponentFor(currency));
243
- }
244
- function fromMinor(cents, currency) {
245
- return cents / 10 ** exponentFor(currency);
246
- }
247
- function formatMoney(cents, currency, locale) {
248
- return new Intl.NumberFormat(locale, { style: "currency", currency }).format(
249
- fromMinor(cents, currency)
250
- );
251
- }
252
- function formatDateInTz(iso, timeZone, opts, locale) {
253
- const date = new Date(iso);
254
- if (Number.isNaN(date.getTime())) return iso;
255
- return new Intl.DateTimeFormat(locale, {
256
- year: "numeric",
257
- month: "short",
258
- day: "2-digit",
259
- hour: "2-digit",
260
- minute: "2-digit",
261
- ...opts,
262
- timeZone
263
- }).format(date);
264
- }
265
-
266
393
  // ../tracking-core/src/resources/sales/schema.ts
267
394
  var import_zod2 = require("zod");
268
395
  var SUPPORTED_CURRENCIES = ["USD", "CAD"];