@crelora/mark 0.2.1 → 0.3.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,4 @@
1
- const p = "https://ingest.onelence.com";
1
+ const m = "https://ingest.onelence.com";
2
2
  class f extends Error {
3
3
  status;
4
4
  retryAfterMs;
@@ -6,10 +6,10 @@ 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) {
9
+ function y(a) {
10
10
  return !(typeof a != "number" || a < 400 || a >= 500 || a === 408 || a === 429);
11
11
  }
12
- function y(a) {
12
+ function w(a) {
13
13
  if (!a) return;
14
14
  const t = a.trim();
15
15
  if (!t) return;
@@ -22,20 +22,20 @@ function y(a) {
22
22
  return i > 0 ? i : 0;
23
23
  }
24
24
  }
25
- const w = 5, b = 300, I = 15e3, k = 2880 * 60 * 1e3;
26
- class v {
25
+ const I = 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 ?? b, this.maxBackoffMs = e.maxBackoffMs ?? I, 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 ?? I, 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
  }
@@ -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 o = 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) + o
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"]), q = /* @__PURE__ */ new Set([
150
+ const A = 256;
151
+ function p(a) {
152
+ if (typeof a != "string")
153
+ return;
154
+ const t = a.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 A {
167
+ class D {
160
168
  constructor(t, e) {
161
169
  this.deps = e, this.validateConfig(t), this.config = {
162
- endpoint: t.endpoint ?? p,
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),
@@ -204,19 +212,19 @@ class A {
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 o = {
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, 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)), this.applyInternalFlag(r, o.is_internal);
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;
223
+ s && (o.is_conversion = !0);
224
+ const h = n.site_id ?? this.siteId, u = n.site_host ?? this.siteHost;
225
+ h && (o.site_id = h), u && (o.site_host = u), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(o), !u && o.site && (o.site_host = o.site)), this.applyInternalFlag(o, n.is_internal);
226
+ const d = this.config.before_send ? this.config.before_send(o) : o;
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) {
@@ -236,7 +244,7 @@ class A {
236
244
  };
237
245
  this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost), this.applyInternalFlag(s);
238
246
  const i = this.config.before_send ? this.config.before_send(s) : s;
239
- i && this.queue.enqueue("/identify", i);
247
+ i && (this.ensureSession(), this.applySessionFields(i), this.queue.enqueue("/identify", i));
240
248
  }
241
249
  conversion(t, e = {}) {
242
250
  return this.trackInternal(t, e, !0);
@@ -251,6 +259,15 @@ class A {
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,8 +335,22 @@ class A {
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 ?? {}, d = { ...r, ...h }, u = {};
322
- 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;
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?.(), o = this.deps.storage.getQueryParams() ?? {}, h = t?.query ?? {}, u = { ...o, ...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);
340
+ const l = this.deps.storage.getSessionStartedAt?.();
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;
342
+ }
343
+ /**
344
+ * Patches session fields after ensureSession when the first event in a session was built
345
+ * before rotation created an id. Rotation events keep the previous session_id from identity.
346
+ */
347
+ applySessionFields(t) {
348
+ if (!t.session_id) {
349
+ const s = this.deps.storage.getSessionId?.(), i = this.deps.storage.getSessionStartedAt?.();
350
+ s && (t.session_id = s), i && (t.session_started_at = i);
351
+ }
352
+ const e = t.session_started_at;
353
+ t.session_id && typeof e == "string" && (t.session_elapsed_ms = Date.now() - Date.parse(e));
323
354
  }
324
355
  hasConsent() {
325
356
  if (this.config.consent_source?.type === "tcf" && typeof window < "u" && !this.tcfCachedAllowed)
@@ -332,7 +363,7 @@ class A {
332
363
  sanitizeTrackData(t) {
333
364
  const e = {};
334
365
  for (const [s, i] of Object.entries(t))
335
- M.has(s) || (e[s] = i);
366
+ S.has(s) || (e[s] = i);
336
367
  return e;
337
368
  }
338
369
  sanitizeIdentifyTraits(t) {
@@ -354,6 +385,8 @@ class A {
354
385
  throw new Error("[Mark] `site_id` cannot be an empty string.");
355
386
  if (typeof t.site_host == "string" && !t.site_host.trim())
356
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).");
357
390
  }
358
391
  applyPageContext(t) {
359
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)));
@@ -383,11 +416,11 @@ class A {
383
416
  ensureSession() {
384
417
  const t = Date.now(), e = this.deps.storage.getSessionId?.(), s = this.deps.storage.getLastActivityAt?.(), i = s ? Date.parse(s) : 0;
385
418
  if (!e || !i || t - i >= this.sessionTimeoutMs || this.crossedUtcDay(i, t)) {
386
- const n = this.createMessageId(), r = new Date(t).toISOString();
419
+ const r = this.createMessageId(), o = new Date(t).toISOString();
387
420
  this.deps.storage.update({
388
- session_id: n,
389
- session_started_at: r,
390
- last_activity_at: r
421
+ session_id: r,
422
+ session_started_at: o,
423
+ last_activity_at: o
391
424
  });
392
425
  return;
393
426
  }
@@ -433,22 +466,22 @@ class A {
433
466
  if (t?.type !== "tcf" || typeof window > "u") return;
434
467
  const e = t.purposes, s = (i) => {
435
468
  try {
436
- const o = window;
437
- if (typeof o.__tcfapi != "function") {
469
+ const n = window;
470
+ if (typeof n.__tcfapi != "function") {
438
471
  i > 0 && setTimeout(() => s(i - 1), 200);
439
472
  return;
440
473
  }
441
- o.__tcfapi("addEventListener", 2, (n, r) => {
442
- if (!r || !n) {
474
+ n.__tcfapi("addEventListener", 2, (r, o) => {
475
+ if (!o || !r) {
443
476
  this.tcfCachedAllowed = !1;
444
477
  return;
445
478
  }
446
- if (n.gdprApplies === !1) {
479
+ if (r.gdprApplies === !1) {
447
480
  this.tcfCachedAllowed = !0;
448
481
  return;
449
482
  }
450
- const h = n.purpose?.consents ?? {};
451
- this.tcfCachedAllowed = e.every((d) => h[String(d)] === !0);
483
+ const h = r.purpose?.consents ?? {};
484
+ this.tcfCachedAllowed = e.every((u) => h[String(u)] === !0);
452
485
  });
453
486
  } catch {
454
487
  this.tcfCachedAllowed = !1;
@@ -457,12 +490,12 @@ class A {
457
490
  s(10);
458
491
  }
459
492
  }
460
- class D {
493
+ class T {
461
494
  config;
462
495
  endpoint;
463
496
  pending = /* @__PURE__ */ new Set();
464
497
  constructor(t) {
465
- this.config = t, this.endpoint = t.endpoint ?? p;
498
+ this.config = t, this.endpoint = t.endpoint ?? m;
466
499
  }
467
500
  async send(t, e, s) {
468
501
  const i = this.sendInternal(t, e, s);
@@ -477,54 +510,54 @@ class D {
477
510
  this.pending.size !== 0 && await Promise.allSettled(Array.from(this.pending));
478
511
  }
479
512
  async sendInternal(t, e, s) {
480
- 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 = {
481
514
  "Content-Type": "application/json",
482
- [o.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: o
515
+ [n.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: n
483
516
  };
484
517
  this.config.debug && console.log("[Mark] Sending", i, e);
485
- const r = JSON.stringify(e);
518
+ const o = JSON.stringify(e);
486
519
  if (s?.preferBeacon && typeof navigator < "u" && typeof navigator.sendBeacon == "function") {
487
- const c = new Blob([r], { type: "application/json" });
520
+ const c = new Blob([o], { type: "application/json" });
488
521
  if (navigator.sendBeacon(i, c))
489
522
  return;
490
523
  }
491
524
  if (typeof fetch != "function")
492
525
  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.");
493
- const h = this.config.request_timeout_ms ?? 1e4, d = new AbortController();
494
- let u = !1;
495
- const _ = setTimeout(() => {
496
- u = !0, d.abort();
526
+ const h = this.config.request_timeout_ms ?? 1e4, u = new AbortController();
527
+ let d = !1;
528
+ const l = setTimeout(() => {
529
+ d = !0, u.abort();
497
530
  }, h);
498
531
  try {
499
532
  const c = await fetch(i, {
500
533
  method: "POST",
501
- headers: n,
502
- body: r,
534
+ headers: r,
535
+ body: o,
503
536
  keepalive: !0,
504
- signal: d.signal
537
+ signal: u.signal
505
538
  });
506
539
  if (!c.ok) {
507
- const l = await this.readErrorSnippet(c), g = y(c.headers.get("Retry-After"));
540
+ const g = await this.readErrorSnippet(c), _ = w(c.headers.get("Retry-After"));
508
541
  throw this.config.debug && console.error("[Mark] Request rejected", {
509
542
  url: i,
510
543
  status: c.status,
511
544
  statusText: c.statusText,
512
- body: l,
513
- retryAfterMs: g
545
+ body: g,
546
+ retryAfterMs: _
514
547
  }), new f(
515
- `[Mark] Request rejected with status ${c.status}: ${l}`,
516
- { status: c.status, retryAfterMs: g }
548
+ `[Mark] Request rejected with status ${c.status}: ${g}`,
549
+ { status: c.status, retryAfterMs: _ }
517
550
  );
518
551
  }
519
552
  } catch (c) {
520
553
  if (this.config.debug && console.error("[Mark] Failed to send", i, c), c instanceof f)
521
554
  throw c;
522
- if (u)
555
+ if (d)
523
556
  throw new f(`[Mark] Request timed out after ${h}ms`, { status: 408 });
524
- const l = c instanceof Error ? c.message : String(c);
525
- throw new f(`[Mark] Network error: ${l}`);
557
+ const g = c instanceof Error ? c.message : String(c);
558
+ throw new f(`[Mark] Network error: ${g}`);
526
559
  } finally {
527
- clearTimeout(_);
560
+ clearTimeout(l);
528
561
  }
529
562
  }
530
563
  joinUrl(t, e) {
@@ -539,7 +572,7 @@ class D {
539
572
  }
540
573
  }
541
574
  }
542
- class S {
575
+ class E {
543
576
  constructor(t = {}) {
544
577
  this.defaults = t;
545
578
  }
@@ -580,6 +613,10 @@ class S {
580
613
  }
581
614
  rotateVisitorId() {
582
615
  }
616
+ setVisitorId(t) {
617
+ const e = p(t);
618
+ return e ? (this.defaults.visitor_id = e, !0) : !1;
619
+ }
583
620
  getInternal() {
584
621
  return this.defaults.is_internal;
585
622
  }
@@ -587,7 +624,7 @@ class S {
587
624
  this.defaults.is_internal = t ? !0 : void 0;
588
625
  }
589
626
  }
590
- class T {
627
+ class x {
591
628
  constructor(t) {
592
629
  this.client = t;
593
630
  }
@@ -620,6 +657,14 @@ class T {
620
657
  getVisitorId() {
621
658
  return this.client.getVisitorId();
622
659
  }
660
+ /**
661
+ * Adopts or replaces the visitor id for this NodeMark instance.
662
+ * With default StatelessStorage, updates the in-memory defaults used by
663
+ * subsequent track/identify calls.
664
+ */
665
+ setVisitorId(t) {
666
+ return this.client.setVisitorId(t);
667
+ }
623
668
  /**
624
669
  * Marks the current request/visitor as internal traffic. When using the
625
670
  * default StatelessStorage, the flag is scoped to this NodeMark instance.
@@ -631,9 +676,9 @@ class T {
631
676
  return this.client.getInternal();
632
677
  }
633
678
  }
634
- const E = (a, t = {}) => {
635
- const e = t.storage ?? new S({
636
- visitor_id: t.storageDefaults?.visitor_id,
679
+ const C = (a, t = {}) => {
680
+ const e = p(a.visitor_id), s = t.storage ?? new E({
681
+ visitor_id: t.storageDefaults?.visitor_id ?? e,
637
682
  user_id: t.storageDefaults?.user_id,
638
683
  session_id: t.storageDefaults?.session_id,
639
684
  session_started_at: t.storageDefaults?.session_started_at,
@@ -643,12 +688,12 @@ const E = (a, t = {}) => {
643
688
  query_params: t.storageDefaults?.query_params,
644
689
  consent_status: t.storageDefaults?.consent_status,
645
690
  is_internal: t.storageDefaults?.is_internal
646
- }), s = t.transport ?? new D(a), i = new A(a, { storage: e, transport: s });
647
- return new T(i);
691
+ }), i = t.transport ?? new T(a), n = new D(a, { storage: s, transport: i });
692
+ return new x(n);
648
693
  };
649
694
  export {
650
- T as NodeMark,
651
- S as StatelessStorage,
652
- E as createNodeMark
695
+ x as NodeMark,
696
+ E as StatelessStorage,
697
+ C as createNodeMark
653
698
  };
654
699
  //# sourceMappingURL=node.es.js.map