@crelora/mark 0.0.16 → 0.1.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/node.es.js CHANGED
@@ -1,4 +1,153 @@
1
- const d = "https://ingest.onelence.com", u = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), h = /* @__PURE__ */ new Set([
1
+ const p = "https://ingest.onelence.com";
2
+ class f extends Error {
3
+ status;
4
+ retryAfterMs;
5
+ constructor(t, e = {}) {
6
+ super(t), this.name = "TransportError", this.status = e.status, this.retryAfterMs = e.retryAfterMs;
7
+ }
8
+ }
9
+ function m(a) {
10
+ return !(typeof a != "number" || a < 400 || a >= 500 || a === 408 || a === 429);
11
+ }
12
+ function y(a) {
13
+ if (!a) return;
14
+ const t = a.trim();
15
+ if (!t) return;
16
+ const e = Number(t);
17
+ if (Number.isFinite(e) && e >= 0)
18
+ return Math.floor(e * 1e3);
19
+ const s = Date.parse(t);
20
+ if (Number.isFinite(s)) {
21
+ const i = s - Date.now();
22
+ return i > 0 ? i : 0;
23
+ }
24
+ }
25
+ const w = 5, b = 300, k = 15e3, v = 2880 * 60 * 1e3;
26
+ class M {
27
+ constructor(t, e = {}) {
28
+ this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? b, this.maxBackoffMs = e.maxBackoffMs ?? k, this.maxItemAgeMs = e.maxItemAgeMs ?? v, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
29
+ const s = this.loadPersisted?.() ?? [];
30
+ if (s.length > 0) {
31
+ const i = Date.now();
32
+ for (const o of s) {
33
+ const n = o.enqueuedAt ?? i;
34
+ if (i - n > this.maxItemAgeMs) {
35
+ this.dropped += 1;
36
+ continue;
37
+ }
38
+ this.queue.push({ ...o, enqueuedAt: n });
39
+ }
40
+ this.persist();
41
+ }
42
+ }
43
+ queue = [];
44
+ flushing = !1;
45
+ sent = 0;
46
+ failed = 0;
47
+ dropped = 0;
48
+ maxAttempts;
49
+ baseBackoffMs;
50
+ maxBackoffMs;
51
+ maxItemAgeMs;
52
+ debug;
53
+ loadPersisted;
54
+ savePersisted;
55
+ onError;
56
+ enqueue(t, e) {
57
+ this.queue.push({
58
+ path: t,
59
+ data: e,
60
+ attempts: 0,
61
+ nextAttemptAt: Date.now(),
62
+ enqueuedAt: Date.now()
63
+ }), this.persist(), this.process();
64
+ }
65
+ async flush() {
66
+ await this.process(!0), await this.transport.flush?.();
67
+ }
68
+ /**
69
+ * Best-effort synchronous drain using sendBeacon. Intended for page unload;
70
+ * errors are swallowed because the tab is going away.
71
+ */
72
+ drainViaBeacon() {
73
+ if (this.queue.length === 0) return;
74
+ const t = this.queue.splice(0, this.queue.length);
75
+ this.persist();
76
+ for (const e of t) {
77
+ const s = { ...e.data };
78
+ delete s.__prefer_beacon;
79
+ try {
80
+ this.transport.send(e.path, s, { preferBeacon: !0 });
81
+ } catch {
82
+ }
83
+ }
84
+ }
85
+ getStats() {
86
+ return {
87
+ queued: this.queue.length,
88
+ sent: this.sent,
89
+ failed: this.failed,
90
+ dropped: this.dropped
91
+ };
92
+ }
93
+ clear() {
94
+ this.queue.splice(0, this.queue.length), this.persist();
95
+ }
96
+ persist() {
97
+ this.savePersisted?.(this.queue);
98
+ }
99
+ evictExpired() {
100
+ if (this.queue.length === 0) return;
101
+ const e = Date.now() - this.maxItemAgeMs;
102
+ let s = 0;
103
+ for (let i = this.queue.length - 1; i >= 0; i -= 1)
104
+ this.queue[i].enqueuedAt <= e && (this.queue.splice(i, 1), this.dropped += 1, s += 1);
105
+ s > 0 && this.persist();
106
+ }
107
+ async process(t = !1) {
108
+ if (!this.flushing) {
109
+ this.flushing = !0;
110
+ try {
111
+ for (this.evictExpired(); this.queue.length > 0; ) {
112
+ const e = this.queue[0];
113
+ if (!t && e.nextAttemptAt > Date.now())
114
+ break;
115
+ try {
116
+ const s = { ...e.data }, i = s.__prefer_beacon === !0;
117
+ delete s.__prefer_beacon, await this.transport.send(e.path, s, { preferBeacon: i }), this.queue.shift(), this.sent += 1, this.persist();
118
+ } catch (s) {
119
+ this.failed += 1, this.onError?.(s, e.data);
120
+ const i = s instanceof f ? s.status : void 0;
121
+ if (m(i)) {
122
+ this.queue.shift(), this.dropped += 1, this.persist(), this.debug && console.error("[Mark] Dropping event after non-retriable status", i, e.path);
123
+ continue;
124
+ }
125
+ if (e.attempts += 1, e.attempts >= this.maxAttempts) {
126
+ this.queue.shift(), this.dropped += 1, this.persist(), this.debug && console.error("[Mark] Dropping event after max retries", e.path, s);
127
+ continue;
128
+ }
129
+ const o = s instanceof f ? s.retryAfterMs : void 0;
130
+ let n;
131
+ if (typeof o == "number")
132
+ n = Math.min(this.maxBackoffMs, Math.max(0, o));
133
+ else {
134
+ const r = Math.random() * this.baseBackoffMs;
135
+ n = Math.min(
136
+ this.maxBackoffMs,
137
+ this.baseBackoffMs * 2 ** (e.attempts - 1) + r
138
+ );
139
+ }
140
+ e.nextAttemptAt = Date.now() + n, this.persist();
141
+ break;
142
+ }
143
+ }
144
+ } finally {
145
+ this.flushing = !1;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ const I = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), q = /* @__PURE__ */ new Set([
2
151
  "user_id",
3
152
  "visitor_id",
4
153
  "click_id",
@@ -7,56 +156,94 @@ const d = "https://ingest.onelence.com", u = /* @__PURE__ */ new Set(["event_nam
7
156
  "consent_state",
8
157
  "source"
9
158
  ]);
10
- class l {
159
+ class A {
11
160
  constructor(t, e) {
12
161
  this.deps = e, this.validateConfig(t), this.config = {
13
- endpoint: t.endpoint ?? d,
162
+ endpoint: t.endpoint ?? p,
14
163
  ...t,
15
164
  include_page_context: t.include_page_context ?? !0
16
- }, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host;
165
+ }, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host, this.sessionTimeoutMs = t.session_timeout_ms ?? 1800 * 1e3, this.queue = new M(this.deps.transport, {
166
+ debug: this.config.debug,
167
+ loadPersisted: () => this.deps.storage.getOutbox?.() ?? [],
168
+ savePersisted: (s) => this.deps.storage.setOutbox?.(s),
169
+ onError: (s, i) => this.config.on_error?.(s, i)
170
+ }), this.warnMisconfiguredSiteHost(), this.subscribeTcf();
17
171
  }
18
172
  config;
19
173
  consentRequirement;
20
174
  siteId;
21
175
  siteHost;
176
+ queue;
177
+ sessionTimeoutMs;
178
+ batchTimer = null;
179
+ batchedEvents = [];
180
+ tcfCachedAllowed = !1;
181
+ /**
182
+ * Best-effort synchronous drain that dispatches all queued events via
183
+ * sendBeacon. Intended for use on page unload (visibilitychange=hidden,
184
+ * pagehide) where async fetch may be cancelled by the browser.
185
+ */
186
+ drainViaBeacon() {
187
+ this.flushBatch(), this.queue.drainViaBeacon();
188
+ }
189
+ /**
190
+ * Kicks the queue to retry any pending items now. Safe to call repeatedly;
191
+ * used by the browser wrapper in response to `online` events or periodic
192
+ * timers.
193
+ */
194
+ kickQueue() {
195
+ this.queue.flush();
196
+ }
22
197
  track(t, e = {}) {
23
198
  return this.trackInternal(t, e, !1);
24
199
  }
25
- trackInternal(t, e = {}, s = !1) {
200
+ trackInternal(t, e = {}, s = !1, i) {
26
201
  if (!t)
27
202
  return this.config.debug && console.warn("[Mark] track called without event name"), !1;
28
- if (!this.hasConsent())
203
+ if (!this.hasConsent() || this.isDntBlocked())
29
204
  return this.config.debug && console.warn("[Mark] Tracking blocked due to consent requirement."), !1;
30
- const i = this.sanitizeTrackData(e), r = { ...i };
31
- "query" in r && delete r.query, "site_id" in r && delete r.site_id, "site_host" in r && delete r.site_host;
32
- const n = {
205
+ if (!s && !this.shouldSampleTrack())
206
+ return !0;
207
+ const o = this.sanitizeTrackData(e), n = { ...o };
208
+ "query" in n && delete n.query, "site_id" in n && delete n.site_id, "site_host" in n && delete n.site_host;
209
+ const r = {
33
210
  event_name: t,
34
- ...this.getIdentityFields(i),
35
- ...r
211
+ message_id: this.createMessageId(),
212
+ ...this.getIdentityFields(o),
213
+ ...n
36
214
  };
37
- s && (n.is_conversion = !0);
38
- const a = i.site_id ?? this.siteId, o = i.site_host ?? this.siteHost;
39
- return a && (n.site_id = a), o && (n.site_host = o), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(n), !o && n.site && (n.site_host = n.site)), this.deps.transport.send("/event", n), !0;
215
+ s && (r.is_conversion = !0);
216
+ const h = o.site_id ?? this.siteId, d = o.site_host ?? this.siteHost;
217
+ h && (r.site_id = h), d && (r.site_host = d), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(r), !d && r.site && (r.site_host = r.site));
218
+ const u = this.config.before_send ? this.config.before_send(r) : r;
219
+ return u ? (this.ensureSession(), this.config.batching?.enabled && !s && !i?.preferBeacon ? (this.enqueueBatch(u), !0) : (this.queue.enqueue("/event", { ...u, __prefer_beacon: i?.preferBeacon === !0 }), !0)) : !0;
40
220
  }
41
221
  identify(t, e = {}) {
42
222
  if (!t) {
43
223
  this.config.debug && console.warn("[Mark] identify called without userId");
44
224
  return;
45
225
  }
46
- if (!this.hasConsent()) {
226
+ if (!this.hasConsent() || this.isDntBlocked()) {
47
227
  this.config.debug && console.warn("[Mark] Identify blocked due to consent requirement.");
48
228
  return;
49
229
  }
230
+ this.deps.storage.update({ user_id: t });
50
231
  const s = {
51
232
  user_id: t,
233
+ message_id: this.createMessageId(),
52
234
  ...this.sanitizeIdentifyTraits(e),
53
235
  ...this.getIdentityFields()
54
236
  };
55
- this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost), this.deps.transport.send("/identify", s);
237
+ this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost);
238
+ const i = this.config.before_send ? this.config.before_send(s) : s;
239
+ i && this.queue.enqueue("/identify", i);
56
240
  }
57
241
  conversion(t, e = {}) {
58
242
  return this.trackInternal(t, e, !0);
59
243
  }
244
+ trackWithOptions(t, e = {}, s) {
245
+ return this.trackInternal(t, e, !1, s);
246
+ }
60
247
  /**
61
248
  * Returns the current visitor ID from storage, if any.
62
249
  * Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.
@@ -65,19 +252,42 @@ class l {
65
252
  return this.deps.storage.getVisitorId();
66
253
  }
67
254
  setConsent(t) {
68
- this.deps.storage.setConsentStatus(t);
69
- const e = {
255
+ const e = this.deps.storage.getConsentStatus();
256
+ this.deps.storage.setConsentStatus(t), t === "denied" ? (this.deps.storage.clearAttribution?.(), this.deps.storage.clearCookieVisitorId?.()) : t === "granted" && e === "denied" && this.config.rotate_visitor_on_consent_change && this.deps.storage.rotateVisitorId?.();
257
+ const s = {
70
258
  visitor_id: this.deps.storage.getVisitorId(),
71
259
  consent_state: t,
72
- source: "sdk"
260
+ source: "sdk",
261
+ message_id: this.createMessageId()
73
262
  };
74
- this.siteId && (e.site_id = this.siteId), this.siteHost && (e.site_host = this.siteHost), this.deps.transport.send("/consent", e);
263
+ this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost);
264
+ const i = this.config.before_send ? this.config.before_send(s) : s;
265
+ i && this.queue.enqueue("/consent", i);
266
+ }
267
+ reset() {
268
+ this.deps.storage.update({
269
+ user_id: void 0,
270
+ last_click_id: void 0,
271
+ campaign_id: void 0,
272
+ query_params: void 0,
273
+ session_id: void 0,
274
+ session_started_at: void 0,
275
+ last_activity_at: void 0
276
+ }), this.deps.storage.rotateVisitorId?.();
277
+ }
278
+ flush() {
279
+ return this.flushBatch(), this.queue.flush();
280
+ }
281
+ getStats() {
282
+ return this.queue.getStats();
75
283
  }
76
284
  getIdentityFields(t) {
77
- const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), s = t?.click_id ?? this.deps.storage.getLastClickId(), i = t?.campaign_id ?? this.deps.storage.getCampaignId(), r = this.deps.storage.getQueryParams() ?? {}, n = t?.query ?? {}, a = { ...r, ...n }, o = {};
78
- return e && (o.visitor_id = e), s && (o.click_id = s), i && (o.campaign_id = i), Object.keys(a).length > 0 && (o.query = a), o;
285
+ const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), s = t?.user_id ?? this.deps.storage.getUserId?.(), i = t?.click_id ?? this.deps.storage.getLastClickId(), o = t?.campaign_id ?? this.deps.storage.getCampaignId(), n = t?.session_id ?? this.deps.storage.getSessionId?.(), r = this.deps.storage.getQueryParams() ?? {}, h = t?.query ?? {}, d = { ...r, ...h }, u = {};
286
+ return e && (u.visitor_id = e), s && (u.user_id = s), i && (u.click_id = i), o && (u.campaign_id = o), n && (u.session_id = n), Object.keys(d).length > 0 && (u.query = d), u;
79
287
  }
80
288
  hasConsent() {
289
+ if (this.config.consent_source?.type === "tcf" && typeof window < "u" && !this.tcfCachedAllowed)
290
+ return !1;
81
291
  if (!this.consentRequirement)
82
292
  return !0;
83
293
  const e = this.deps.storage.getConsentStatus();
@@ -86,13 +296,13 @@ class l {
86
296
  sanitizeTrackData(t) {
87
297
  const e = {};
88
298
  for (const [s, i] of Object.entries(t))
89
- u.has(s) || (e[s] = i);
299
+ I.has(s) || (e[s] = i);
90
300
  return e;
91
301
  }
92
302
  sanitizeIdentifyTraits(t) {
93
303
  const e = {};
94
304
  for (const [s, i] of Object.entries(t))
95
- h.has(s) || (e[s] = i);
305
+ q.has(s) || (e[s] = i);
96
306
  return e;
97
307
  }
98
308
  validateConfig(t) {
@@ -110,42 +320,175 @@ class l {
110
320
  throw new Error("[Mark] `site_host` cannot be an empty string.");
111
321
  }
112
322
  applyPageContext(t) {
113
- t.page || t.title || t.referrer || t.site || (t.site = window.location.host, t.page = window.location.pathname, t.title = document.title, document.referrer && (t.referrer = document.referrer));
323
+ typeof document > "u" || (t.site || (t.site = window.location.host), t.page || (t.page = window.location.pathname), t.title || (t.title = document.title), !t.referrer && document.referrer && (t.referrer = this.scrubReferrer(document.referrer)));
324
+ }
325
+ enqueueBatch(t) {
326
+ this.batchedEvents.push(t);
327
+ const e = this.config.batching?.max_size ?? 20;
328
+ if (this.batchedEvents.length >= e) {
329
+ this.flushBatch();
330
+ return;
331
+ }
332
+ if (!this.batchTimer) {
333
+ const s = this.config.batching?.flush_interval_ms ?? 2e3;
334
+ this.batchTimer = setTimeout(() => {
335
+ this.batchTimer = null, this.flushBatch();
336
+ }, s);
337
+ }
338
+ }
339
+ flushBatch() {
340
+ if (this.batchedEvents.length === 0) return;
341
+ const t = this.config.batching?.endpoint_path ?? "/events", e = this.batchedEvents.splice(0, this.batchedEvents.length);
342
+ this.queue.enqueue(t, { events: e, message_id: this.createMessageId() });
343
+ }
344
+ createMessageId() {
345
+ return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`;
346
+ }
347
+ ensureSession() {
348
+ const t = Date.now(), e = this.deps.storage.getSessionId?.(), s = this.deps.storage.getLastActivityAt?.(), i = s ? Date.parse(s) : 0;
349
+ if (!e || !i || t - i >= this.sessionTimeoutMs || this.crossedUtcDay(i, t)) {
350
+ const n = this.createMessageId(), r = new Date(t).toISOString();
351
+ this.deps.storage.update({
352
+ session_id: n,
353
+ session_started_at: r,
354
+ last_activity_at: r
355
+ });
356
+ return;
357
+ }
358
+ this.deps.storage.update({ last_activity_at: new Date(t).toISOString() });
359
+ }
360
+ crossedUtcDay(t, e) {
361
+ const s = new Date(t), i = new Date(e);
362
+ return s.getUTCFullYear() !== i.getUTCFullYear() || s.getUTCMonth() !== i.getUTCMonth() || s.getUTCDate() !== i.getUTCDate();
363
+ }
364
+ shouldSampleTrack() {
365
+ return typeof this.config.sample_rate != "number" ? !0 : this.config.sample_rate <= 0 ? !1 : this.config.sample_rate >= 1 ? !0 : Math.random() <= this.config.sample_rate;
366
+ }
367
+ isDntBlocked() {
368
+ if (!this.config.honor_dnt || typeof navigator > "u")
369
+ return !1;
370
+ const t = navigator.doNotTrack, e = navigator.globalPrivacyControl;
371
+ return t === "1" || e === !0;
372
+ }
373
+ scrubReferrer(t) {
374
+ try {
375
+ const e = new URL(t);
376
+ if (typeof window > "u") return t;
377
+ const s = new URL(window.location.href);
378
+ return e.origin !== s.origin ? (e.search = "", e.toString()) : t;
379
+ } catch {
380
+ return t;
381
+ }
382
+ }
383
+ warnMisconfiguredSiteHost() {
384
+ !this.config.debug || !this.siteHost || typeof window > "u" || (window.location.host !== this.siteHost && console.warn("[Mark] config.site_host does not match current host", {
385
+ expected: this.siteHost,
386
+ actual: window.location.host
387
+ }), this.siteId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(this.siteId) && console.warn("[Mark] config.site_id does not look like UUID v4", this.siteId));
388
+ }
389
+ /**
390
+ * Subscribes to the IAB TCF v2 CMP via `__tcfapi('addEventListener', ...)`.
391
+ * Result is cached in `tcfCachedAllowed` so the synchronous `hasConsent()`
392
+ * path does not need to await the CMP. If the CMP is not yet present, we
393
+ * poll briefly (CMPs commonly load asynchronously) and give up after ~2s.
394
+ */
395
+ subscribeTcf() {
396
+ const t = this.config.consent_source;
397
+ if (t?.type !== "tcf" || typeof window > "u") return;
398
+ const e = t.purposes, s = (i) => {
399
+ try {
400
+ const o = window;
401
+ if (typeof o.__tcfapi != "function") {
402
+ i > 0 && setTimeout(() => s(i - 1), 200);
403
+ return;
404
+ }
405
+ o.__tcfapi("addEventListener", 2, (n, r) => {
406
+ if (!r || !n) {
407
+ this.tcfCachedAllowed = !1;
408
+ return;
409
+ }
410
+ if (n.gdprApplies === !1) {
411
+ this.tcfCachedAllowed = !0;
412
+ return;
413
+ }
414
+ const h = n.purpose?.consents ?? {};
415
+ this.tcfCachedAllowed = e.every((d) => h[String(d)] === !0);
416
+ });
417
+ } catch {
418
+ this.tcfCachedAllowed = !1;
419
+ }
420
+ };
421
+ s(10);
114
422
  }
115
423
  }
116
- class f {
424
+ class D {
117
425
  config;
118
426
  endpoint;
427
+ pending = /* @__PURE__ */ new Set();
119
428
  constructor(t) {
120
- this.validateConfig(t), this.config = t, this.endpoint = t.endpoint ?? d;
429
+ this.config = t, this.endpoint = t.endpoint ?? p;
121
430
  }
122
- async send(t, e) {
123
- const s = this.joinUrl(this.endpoint, t), i = this.config.key, r = {
431
+ async send(t, e, s) {
432
+ const i = this.sendInternal(t, e, s);
433
+ this.pending.add(i);
434
+ try {
435
+ await i;
436
+ } finally {
437
+ this.pending.delete(i);
438
+ }
439
+ }
440
+ async flush() {
441
+ this.pending.size !== 0 && await Promise.allSettled(Array.from(this.pending));
442
+ }
443
+ async sendInternal(t, e, s) {
444
+ const i = this.joinUrl(this.endpoint, t), o = this.config.key, n = {
124
445
  "Content-Type": "application/json",
125
- [i.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: i
446
+ [o.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: o
126
447
  };
127
- if (this.config.debug && console.log("[Mark] Sending", s, e), typeof fetch != "function") {
128
- this.config.debug && console.error("[Mark] Global fetch is not available in this runtime.");
129
- return;
448
+ this.config.debug && console.log("[Mark] Sending", i, e);
449
+ const r = JSON.stringify(e);
450
+ if (s?.preferBeacon && typeof navigator < "u" && typeof navigator.sendBeacon == "function") {
451
+ const c = new Blob([r], { type: "application/json" });
452
+ if (navigator.sendBeacon(i, c))
453
+ return;
130
454
  }
455
+ if (typeof fetch != "function")
456
+ throw this.config.debug && console.error("[Mark] Global fetch is not available in this runtime."), new f("[Mark] Global fetch is not available in this runtime.");
457
+ const h = this.config.request_timeout_ms ?? 1e4, d = new AbortController();
458
+ let u = !1;
459
+ const _ = setTimeout(() => {
460
+ u = !0, d.abort();
461
+ }, h);
131
462
  try {
132
- const n = await fetch(s, {
463
+ const c = await fetch(i, {
133
464
  method: "POST",
134
- headers: r,
135
- body: JSON.stringify(e),
136
- keepalive: !0
465
+ headers: n,
466
+ body: r,
467
+ keepalive: !0,
468
+ signal: d.signal
137
469
  });
138
- if (!n.ok && this.config.debug) {
139
- const a = await this.readErrorSnippet(n);
140
- console.error("[Mark] Request rejected", {
141
- url: s,
142
- status: n.status,
143
- statusText: n.statusText,
144
- body: a
145
- });
470
+ if (!c.ok) {
471
+ const l = await this.readErrorSnippet(c), g = y(c.headers.get("Retry-After"));
472
+ throw this.config.debug && console.error("[Mark] Request rejected", {
473
+ url: i,
474
+ status: c.status,
475
+ statusText: c.statusText,
476
+ body: l,
477
+ retryAfterMs: g
478
+ }), new f(
479
+ `[Mark] Request rejected with status ${c.status}: ${l}`,
480
+ { status: c.status, retryAfterMs: g }
481
+ );
146
482
  }
147
- } catch (n) {
148
- this.config.debug && console.error("[Mark] Failed to send", s, n);
483
+ } catch (c) {
484
+ if (this.config.debug && console.error("[Mark] Failed to send", i, c), c instanceof f)
485
+ throw c;
486
+ if (u)
487
+ throw new f(`[Mark] Request timed out after ${h}ms`, { status: 408 });
488
+ const l = c instanceof Error ? c.message : String(c);
489
+ throw new f(`[Mark] Network error: ${l}`);
490
+ } finally {
491
+ clearTimeout(_);
149
492
  }
150
493
  }
151
494
  joinUrl(t, e) {
@@ -159,18 +502,8 @@ class f {
159
502
  return "";
160
503
  }
161
504
  }
162
- validateConfig(t) {
163
- if (!t.key || !t.key.trim())
164
- throw new Error("[Mark] `key` must be a non-empty string.");
165
- if (t.endpoint)
166
- try {
167
- new URL(t.endpoint);
168
- } catch {
169
- throw new Error("[Mark] `endpoint` must be a valid absolute URL.");
170
- }
171
- }
172
505
  }
173
- class _ {
506
+ class S {
174
507
  constructor(t = {}) {
175
508
  this.defaults = t;
176
509
  }
@@ -186,6 +519,18 @@ class _ {
186
519
  getQueryParams() {
187
520
  return this.defaults.query_params;
188
521
  }
522
+ getUserId() {
523
+ return this.defaults.user_id;
524
+ }
525
+ getSessionId() {
526
+ return this.defaults.session_id;
527
+ }
528
+ getSessionStartedAt() {
529
+ return this.defaults.session_started_at;
530
+ }
531
+ getLastActivityAt() {
532
+ return this.defaults.last_activity_at;
533
+ }
189
534
  getConsentStatus() {
190
535
  return this.defaults.consent_status;
191
536
  }
@@ -193,8 +538,14 @@ class _ {
193
538
  }
194
539
  setConsentStatus() {
195
540
  }
541
+ clearAttribution() {
542
+ }
543
+ clearCookieVisitorId() {
544
+ }
545
+ rotateVisitorId() {
546
+ }
196
547
  }
197
- class g {
548
+ class T {
198
549
  constructor(t) {
199
550
  this.client = t;
200
551
  }
@@ -210,6 +561,15 @@ class g {
210
561
  setConsent(t) {
211
562
  this.client.setConsent(t);
212
563
  }
564
+ flush() {
565
+ return this.client.flush();
566
+ }
567
+ reset() {
568
+ this.client.reset();
569
+ }
570
+ getStats() {
571
+ return this.client.getStats();
572
+ }
213
573
  /**
214
574
  * Returns the visitor ID from the configured storage, if any.
215
575
  * With default StatelessStorage, this is the value passed via `storageDefaults.visitor_id` when creating the client.
@@ -219,18 +579,23 @@ class g {
219
579
  return this.client.getVisitorId();
220
580
  }
221
581
  }
222
- const p = (c, t = {}) => {
223
- const e = t.storage ?? new _({
582
+ const E = (a, t = {}) => {
583
+ const e = t.storage ?? new S({
224
584
  visitor_id: t.storageDefaults?.visitor_id,
585
+ user_id: t.storageDefaults?.user_id,
586
+ session_id: t.storageDefaults?.session_id,
587
+ session_started_at: t.storageDefaults?.session_started_at,
588
+ last_activity_at: t.storageDefaults?.last_activity_at,
225
589
  last_click_id: t.storageDefaults?.last_click_id,
226
590
  campaign_id: t.storageDefaults?.campaign_id,
227
591
  query_params: t.storageDefaults?.query_params,
228
592
  consent_status: t.storageDefaults?.consent_status
229
- }), s = t.transport ?? new f(c), i = new l(c, { storage: e, transport: s });
230
- return new g(i);
593
+ }), s = t.transport ?? new D(a), i = new A(a, { storage: e, transport: s });
594
+ return new T(i);
231
595
  };
232
596
  export {
233
- g as NodeMark,
234
- _ as StatelessStorage,
235
- p as createNodeMark
597
+ T as NodeMark,
598
+ S as StatelessStorage,
599
+ E as createNodeMark
236
600
  };
601
+ //# sourceMappingURL=node.es.js.map