@crelora/mark 0.2.2 → 0.3.2

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,4 @@
1
- const _ = "https://ingest.onelence.com";
1
+ const m = "https://ingest.onelence.com";
2
2
  class f extends Error {
3
3
  status;
4
4
  retryAfterMs;
@@ -6,12 +6,12 @@ class f extends Error {
6
6
  super(t), this.name = "TransportError", this.status = e.status, this.retryAfterMs = e.retryAfterMs;
7
7
  }
8
8
  }
9
- function m(a) {
10
- return !(typeof a != "number" || a < 400 || a >= 500 || a === 408 || a === 429);
9
+ function y(o) {
10
+ return !(typeof o != "number" || o < 400 || o >= 500 || o === 408 || o === 429);
11
11
  }
12
- function y(a) {
13
- if (!a) return;
14
- const t = a.trim();
12
+ function I(o) {
13
+ if (!o) return;
14
+ const t = o.trim();
15
15
  if (!t) return;
16
16
  const e = Number(t);
17
17
  if (Number.isFinite(e) && e >= 0)
@@ -22,20 +22,20 @@ function y(a) {
22
22
  return i > 0 ? i : 0;
23
23
  }
24
24
  }
25
- const w = 5, I = 300, b = 15e3, k = 2880 * 60 * 1e3;
26
- class v {
25
+ const w = 5, v = 300, b = 15e3, k = 2880 * 60 * 1e3;
26
+ class M {
27
27
  constructor(t, e = {}) {
28
- this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? I, this.maxBackoffMs = e.maxBackoffMs ?? b, this.maxItemAgeMs = e.maxItemAgeMs ?? k, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
28
+ this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? v, this.maxBackoffMs = e.maxBackoffMs ?? b, this.maxItemAgeMs = e.maxItemAgeMs ?? k, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
29
29
  const s = this.loadPersisted?.() ?? [];
30
30
  if (s.length > 0) {
31
31
  const i = Date.now();
32
- for (const o of s) {
33
- const n = o.enqueuedAt ?? i;
34
- if (i - n > this.maxItemAgeMs) {
32
+ for (const n of s) {
33
+ const r = n.enqueuedAt ?? i;
34
+ if (i - r > this.maxItemAgeMs) {
35
35
  this.dropped += 1;
36
36
  continue;
37
37
  }
38
- this.queue.push({ ...o, enqueuedAt: n });
38
+ this.queue.push({ ...n, enqueuedAt: r });
39
39
  }
40
40
  this.persist();
41
41
  }
@@ -66,8 +66,8 @@ class v {
66
66
  await this.process(!0), await this.transport.flush?.();
67
67
  }
68
68
  /**
69
- * Best-effort synchronous drain using sendBeacon. Intended for page unload;
70
- * errors are swallowed because the tab is going away.
69
+ * Best-effort synchronous drain on page unload. Uses fetch keepalive (via
70
+ * preferBeacon) so auth headers are included.
71
71
  */
72
72
  drainViaBeacon() {
73
73
  if (this.queue.length === 0) return;
@@ -118,7 +118,7 @@ class v {
118
118
  } catch (s) {
119
119
  this.failed += 1, this.onError?.(s, e.data);
120
120
  const i = s instanceof f ? s.status : void 0;
121
- if (m(i)) {
121
+ if (y(i)) {
122
122
  this.queue.shift(), this.dropped += 1, this.persist(), this.debug && console.error("[Mark] Dropping event after non-retriable status", i, e.path);
123
123
  continue;
124
124
  }
@@ -126,18 +126,18 @@ class v {
126
126
  this.queue.shift(), this.dropped += 1, this.persist(), this.debug && console.error("[Mark] Dropping event after max retries", e.path, s);
127
127
  continue;
128
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));
129
+ const n = s instanceof f ? s.retryAfterMs : void 0;
130
+ let r;
131
+ if (typeof n == "number")
132
+ r = Math.min(this.maxBackoffMs, Math.max(0, n));
133
133
  else {
134
- const r = Math.random() * this.baseBackoffMs;
135
- n = Math.min(
134
+ const a = Math.random() * this.baseBackoffMs;
135
+ r = Math.min(
136
136
  this.maxBackoffMs,
137
- this.baseBackoffMs * 2 ** (e.attempts - 1) + r
137
+ this.baseBackoffMs * 2 ** (e.attempts - 1) + a
138
138
  );
139
139
  }
140
- e.nextAttemptAt = Date.now() + n, this.persist();
140
+ e.nextAttemptAt = Date.now() + r, this.persist();
141
141
  break;
142
142
  }
143
143
  }
@@ -147,7 +147,15 @@ class v {
147
147
  }
148
148
  }
149
149
  }
150
- const M = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), A = /* @__PURE__ */ new Set([
150
+ const A = 256;
151
+ function p(o) {
152
+ if (typeof o != "string")
153
+ return;
154
+ const t = o.trim();
155
+ if (!(!t || t.length > A) && !t.includes("@") && !/\s/.test(t))
156
+ return t;
157
+ }
158
+ const S = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), q = /* @__PURE__ */ new Set([
151
159
  "user_id",
152
160
  "visitor_id",
153
161
  "click_id",
@@ -156,13 +164,13 @@ const M = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "so
156
164
  "consent_state",
157
165
  "source"
158
166
  ]);
159
- class q {
167
+ class D {
160
168
  constructor(t, e) {
161
169
  this.deps = e, this.validateConfig(t), this.config = {
162
- endpoint: t.endpoint ?? _,
170
+ endpoint: t.endpoint ?? m,
163
171
  ...t,
164
172
  include_page_context: t.include_page_context ?? !0
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 v(this.deps.transport, {
173
+ }, 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
174
  debug: this.config.debug,
167
175
  loadPersisted: () => this.deps.storage.getOutbox?.() ?? [],
168
176
  savePersisted: (s) => this.deps.storage.setOutbox?.(s),
@@ -180,7 +188,7 @@ class q {
180
188
  tcfCachedAllowed = !1;
181
189
  /**
182
190
  * Best-effort synchronous drain that dispatches all queued events via
183
- * sendBeacon. Intended for use on page unload (visibilitychange=hidden,
191
+ * fetch keepalive. Intended for use on page unload (visibilitychange=hidden,
184
192
  * pagehide) where async fetch may be cancelled by the browser.
185
193
  */
186
194
  drainViaBeacon() {
@@ -204,19 +212,19 @@ class q {
204
212
  return this.config.debug && console.warn("[Mark] Tracking blocked due to consent requirement."), !1;
205
213
  if (!s && !this.shouldSampleTrack())
206
214
  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 = {
215
+ const n = this.sanitizeTrackData(e), r = { ...n };
216
+ "query" in r && delete r.query, "site_id" in r && delete r.site_id, "site_host" in r && delete r.site_host;
217
+ const a = {
210
218
  event_name: t,
211
219
  message_id: this.createMessageId(),
212
- ...this.getIdentityFields(o),
213
- ...n
220
+ ...this.getIdentityFields(n),
221
+ ...r
214
222
  };
215
- s && (r.is_conversion = !0);
216
- const h = o.site_id ?? this.siteId, u = o.site_host ?? this.siteHost;
217
- h && (r.site_id = h), u && (r.site_host = u), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(r), !u && r.site && (r.site_host = r.site)), this.applyInternalFlag(r, o.is_internal);
218
- const c = this.config.before_send ? this.config.before_send(r) : r;
219
- return c ? (this.ensureSession(), this.applySessionFields(c), this.config.batching?.enabled && !s && !i?.preferBeacon ? (this.enqueueBatch(c), !0) : (this.queue.enqueue("/event", { ...c, __prefer_beacon: i?.preferBeacon === !0 }), !0)) : !0;
223
+ s && (a.is_conversion = !0);
224
+ const h = n.site_id ?? this.siteId, u = n.site_host ?? this.siteHost;
225
+ h && (a.site_id = h), u && (a.site_host = u), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(a), !u && a.site && (a.site_host = a.site)), this.applyInternalFlag(a, n.is_internal);
226
+ const d = this.config.before_send ? this.config.before_send(a) : a;
227
+ return d ? (this.ensureSession(), this.applySessionFields(d), this.config.batching?.enabled && !s && !i?.preferBeacon ? (this.enqueueBatch(d), !0) : (this.queue.enqueue("/event", { ...d, __prefer_beacon: i?.preferBeacon === !0 }), !0)) : !0;
220
228
  }
221
229
  identify(t, e = {}) {
222
230
  if (!t) {
@@ -251,6 +259,15 @@ class q {
251
259
  getVisitorId() {
252
260
  return this.deps.storage.getVisitorId();
253
261
  }
262
+ /**
263
+ * Adopts or replaces the stored visitor id with a trusted first-party value.
264
+ * Use when an external anonymous id (for example a platform client id) arrives
265
+ * after init. Returns false when the id is invalid.
266
+ */
267
+ setVisitorId(t) {
268
+ const e = p(t);
269
+ return e ? this.deps.storage.setVisitorId ? this.deps.storage.setVisitorId(e) : (this.deps.storage.getVisitorId() === e || this.deps.storage.update({ visitor_id: e }), !0) : (this.config.debug && console.warn("[Mark] setVisitorId called with an invalid visitor id."), !1);
270
+ }
254
271
  setConsent(t) {
255
272
  const e = this.deps.storage.getConsentStatus();
256
273
  this.deps.storage.setConsentStatus(t), t === "denied" ? (this.deps.storage.clearAttribution?.(), this.deps.storage.clearCookieVisitorId?.(), this.deps.storage.setInternal?.(!1)) : t === "granted" && e === "denied" && this.config.rotate_visitor_on_consent_change && this.deps.storage.rotateVisitorId?.();
@@ -318,10 +335,10 @@ class q {
318
335
  e === !0 || s ? t.is_internal = !0 : delete t.is_internal;
319
336
  }
320
337
  getIdentityFields(t) {
321
- 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 ?? {}, u = { ...r, ...h }, c = {};
322
- e && (c.visitor_id = e), s && (c.user_id = s), i && (c.click_id = i), o && (c.campaign_id = o), n && (c.session_id = n);
338
+ 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(), n = t?.campaign_id ?? this.deps.storage.getCampaignId(), r = t?.session_id ?? this.deps.storage.getSessionId?.(), a = this.deps.storage.getQueryParams() ?? {}, h = t?.query ?? {}, u = { ...a, ...h }, d = {};
339
+ e && (d.visitor_id = e), s && (d.user_id = s), i && (d.click_id = i), n && (d.campaign_id = n), r && (d.session_id = r);
323
340
  const l = this.deps.storage.getSessionStartedAt?.();
324
- return n && l && (c.session_started_at = l, c.session_elapsed_ms = Date.now() - Date.parse(l)), Object.keys(u).length > 0 && (c.query = u), c;
341
+ return r && l && (d.session_started_at = l, d.session_elapsed_ms = Date.now() - Date.parse(l)), Object.keys(u).length > 0 && (d.query = u), d;
325
342
  }
326
343
  /**
327
344
  * Patches session fields after ensureSession when the first event in a session was built
@@ -346,13 +363,13 @@ class q {
346
363
  sanitizeTrackData(t) {
347
364
  const e = {};
348
365
  for (const [s, i] of Object.entries(t))
349
- M.has(s) || (e[s] = i);
366
+ S.has(s) || (e[s] = i);
350
367
  return e;
351
368
  }
352
369
  sanitizeIdentifyTraits(t) {
353
370
  const e = {};
354
371
  for (const [s, i] of Object.entries(t))
355
- A.has(s) || (e[s] = i);
372
+ q.has(s) || (e[s] = i);
356
373
  return e;
357
374
  }
358
375
  validateConfig(t) {
@@ -368,6 +385,8 @@ class q {
368
385
  throw new Error("[Mark] `site_id` cannot be an empty string.");
369
386
  if (typeof t.site_host == "string" && !t.site_host.trim())
370
387
  throw new Error("[Mark] `site_host` cannot be an empty string.");
388
+ if (t.visitor_id !== void 0 && !p(t.visitor_id))
389
+ throw new Error("[Mark] `visitor_id` must be a non-empty string (max 256 characters).");
371
390
  }
372
391
  applyPageContext(t) {
373
392
  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)));
@@ -397,11 +416,11 @@ class q {
397
416
  ensureSession() {
398
417
  const t = Date.now(), e = this.deps.storage.getSessionId?.(), s = this.deps.storage.getLastActivityAt?.(), i = s ? Date.parse(s) : 0;
399
418
  if (!e || !i || t - i >= this.sessionTimeoutMs || this.crossedUtcDay(i, t)) {
400
- const n = this.createMessageId(), r = new Date(t).toISOString();
419
+ const r = this.createMessageId(), a = new Date(t).toISOString();
401
420
  this.deps.storage.update({
402
- session_id: n,
403
- session_started_at: r,
404
- last_activity_at: r
421
+ session_id: r,
422
+ session_started_at: a,
423
+ last_activity_at: a
405
424
  });
406
425
  return;
407
426
  }
@@ -447,21 +466,21 @@ class q {
447
466
  if (t?.type !== "tcf" || typeof window > "u") return;
448
467
  const e = t.purposes, s = (i) => {
449
468
  try {
450
- const o = window;
451
- if (typeof o.__tcfapi != "function") {
469
+ const n = window;
470
+ if (typeof n.__tcfapi != "function") {
452
471
  i > 0 && setTimeout(() => s(i - 1), 200);
453
472
  return;
454
473
  }
455
- o.__tcfapi("addEventListener", 2, (n, r) => {
456
- if (!r || !n) {
474
+ n.__tcfapi("addEventListener", 2, (r, a) => {
475
+ if (!a || !r) {
457
476
  this.tcfCachedAllowed = !1;
458
477
  return;
459
478
  }
460
- if (n.gdprApplies === !1) {
479
+ if (r.gdprApplies === !1) {
461
480
  this.tcfCachedAllowed = !0;
462
481
  return;
463
482
  }
464
- const h = n.purpose?.consents ?? {};
483
+ const h = r.purpose?.consents ?? {};
465
484
  this.tcfCachedAllowed = e.every((u) => h[String(u)] === !0);
466
485
  });
467
486
  } catch {
@@ -471,12 +490,12 @@ class q {
471
490
  s(10);
472
491
  }
473
492
  }
474
- class S {
493
+ class T {
475
494
  config;
476
495
  endpoint;
477
496
  pending = /* @__PURE__ */ new Set();
478
497
  constructor(t) {
479
- this.config = t, this.endpoint = t.endpoint ?? _;
498
+ this.config = t, this.endpoint = t.endpoint ?? m;
480
499
  }
481
500
  async send(t, e, s) {
482
501
  const i = this.sendInternal(t, e, s);
@@ -491,51 +510,44 @@ class S {
491
510
  this.pending.size !== 0 && await Promise.allSettled(Array.from(this.pending));
492
511
  }
493
512
  async sendInternal(t, e, s) {
494
- const i = this.joinUrl(this.endpoint, t), o = this.config.key, n = {
513
+ const i = this.joinUrl(this.endpoint, t), n = this.config.key, r = {
495
514
  "Content-Type": "application/json",
496
- [o.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: o
515
+ [n.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: n
497
516
  };
498
- this.config.debug && console.log("[Mark] Sending", i, e);
499
- const r = JSON.stringify(e);
500
- if (s?.preferBeacon && typeof navigator < "u" && typeof navigator.sendBeacon == "function") {
501
- const d = new Blob([r], { type: "application/json" });
502
- if (navigator.sendBeacon(i, d))
503
- return;
504
- }
505
- if (typeof fetch != "function")
517
+ if (this.config.debug && console.log("[Mark] Sending", i, e), typeof fetch != "function")
506
518
  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.");
507
- const h = this.config.request_timeout_ms ?? 1e4, u = new AbortController();
508
- let c = !1;
519
+ const a = JSON.stringify(e), h = this.config.request_timeout_ms ?? 1e4, u = new AbortController();
520
+ let d = !1;
509
521
  const l = setTimeout(() => {
510
- c = !0, u.abort();
522
+ d = !0, u.abort();
511
523
  }, h);
512
524
  try {
513
- const d = await fetch(i, {
525
+ const c = await fetch(i, {
514
526
  method: "POST",
515
- headers: n,
516
- body: r,
517
- keepalive: !0,
527
+ headers: r,
528
+ body: a,
529
+ keepalive: s?.preferBeacon === !0,
518
530
  signal: u.signal
519
531
  });
520
- if (!d.ok) {
521
- const g = await this.readErrorSnippet(d), p = y(d.headers.get("Retry-After"));
532
+ if (!c.ok) {
533
+ const g = await this.readErrorSnippet(c), _ = I(c.headers.get("Retry-After"));
522
534
  throw this.config.debug && console.error("[Mark] Request rejected", {
523
535
  url: i,
524
- status: d.status,
525
- statusText: d.statusText,
536
+ status: c.status,
537
+ statusText: c.statusText,
526
538
  body: g,
527
- retryAfterMs: p
539
+ retryAfterMs: _
528
540
  }), new f(
529
- `[Mark] Request rejected with status ${d.status}: ${g}`,
530
- { status: d.status, retryAfterMs: p }
541
+ `[Mark] Request rejected with status ${c.status}: ${g}`,
542
+ { status: c.status, retryAfterMs: _ }
531
543
  );
532
544
  }
533
- } catch (d) {
534
- if (this.config.debug && console.error("[Mark] Failed to send", i, d), d instanceof f)
535
- throw d;
536
- if (c)
545
+ } catch (c) {
546
+ if (this.config.debug && console.error("[Mark] Failed to send", i, c), c instanceof f)
547
+ throw c;
548
+ if (d)
537
549
  throw new f(`[Mark] Request timed out after ${h}ms`, { status: 408 });
538
- const g = d instanceof Error ? d.message : String(d);
550
+ const g = c instanceof Error ? c.message : String(c);
539
551
  throw new f(`[Mark] Network error: ${g}`);
540
552
  } finally {
541
553
  clearTimeout(l);
@@ -553,7 +565,7 @@ class S {
553
565
  }
554
566
  }
555
567
  }
556
- class D {
568
+ class E {
557
569
  constructor(t = {}) {
558
570
  this.defaults = t;
559
571
  }
@@ -594,6 +606,10 @@ class D {
594
606
  }
595
607
  rotateVisitorId() {
596
608
  }
609
+ setVisitorId(t) {
610
+ const e = p(t);
611
+ return e ? (this.defaults.visitor_id = e, !0) : !1;
612
+ }
597
613
  getInternal() {
598
614
  return this.defaults.is_internal;
599
615
  }
@@ -601,7 +617,7 @@ class D {
601
617
  this.defaults.is_internal = t ? !0 : void 0;
602
618
  }
603
619
  }
604
- class T {
620
+ class x {
605
621
  constructor(t) {
606
622
  this.client = t;
607
623
  }
@@ -634,6 +650,14 @@ class T {
634
650
  getVisitorId() {
635
651
  return this.client.getVisitorId();
636
652
  }
653
+ /**
654
+ * Adopts or replaces the visitor id for this NodeMark instance.
655
+ * With default StatelessStorage, updates the in-memory defaults used by
656
+ * subsequent track/identify calls.
657
+ */
658
+ setVisitorId(t) {
659
+ return this.client.setVisitorId(t);
660
+ }
637
661
  /**
638
662
  * Marks the current request/visitor as internal traffic. When using the
639
663
  * default StatelessStorage, the flag is scoped to this NodeMark instance.
@@ -645,9 +669,9 @@ class T {
645
669
  return this.client.getInternal();
646
670
  }
647
671
  }
648
- const E = (a, t = {}) => {
649
- const e = t.storage ?? new D({
650
- visitor_id: t.storageDefaults?.visitor_id,
672
+ const C = (o, t = {}) => {
673
+ const e = p(o.visitor_id), s = t.storage ?? new E({
674
+ visitor_id: t.storageDefaults?.visitor_id ?? e,
651
675
  user_id: t.storageDefaults?.user_id,
652
676
  session_id: t.storageDefaults?.session_id,
653
677
  session_started_at: t.storageDefaults?.session_started_at,
@@ -657,12 +681,12 @@ const E = (a, t = {}) => {
657
681
  query_params: t.storageDefaults?.query_params,
658
682
  consent_status: t.storageDefaults?.consent_status,
659
683
  is_internal: t.storageDefaults?.is_internal
660
- }), s = t.transport ?? new S(a), i = new q(a, { storage: e, transport: s });
661
- return new T(i);
684
+ }), i = t.transport ?? new T(o), n = new D(o, { storage: s, transport: i });
685
+ return new x(n);
662
686
  };
663
687
  export {
664
- T as NodeMark,
665
- D as StatelessStorage,
666
- E as createNodeMark
688
+ x as NodeMark,
689
+ E as StatelessStorage,
690
+ C as createNodeMark
667
691
  };
668
692
  //# sourceMappingURL=node.es.js.map