@datalyr/web 1.7.3 → 1.7.5

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @datalyr/web v1.7.3
2
+ * @datalyr/web v1.7.5
3
3
  * Datalyr Web SDK - Modern attribution tracking for web applications
4
4
  * (c) 2026 Datalyr Inc.
5
5
  * Released under the MIT License
@@ -2298,16 +2298,22 @@ class EventQueue {
2298
2298
  // so only request it when the body is safely under the cap; oversized batches go via
2299
2299
  // a normal fetch (no cap) and succeed.
2300
2300
  const useKeepalive = new Blob([body]).size <= 60000;
2301
+ // First-party endpoint (the customer's own tracking host, never *.datalyr.com — set
2302
+ // by the script-origin auto-derive): send the cookie cross-subdomain so the edge can
2303
+ // read/refresh the server-set, ITP-durable visitor_id. Legacy datalyr.com endpoints
2304
+ // stay credential-less — unchanged behavior for every existing install.
2305
+ let firstPartyEndpoint = false;
2301
2306
  try {
2302
- const response = yield fetch(currentEndpoint, {
2303
- method: 'POST',
2304
- headers: {
2307
+ firstPartyEndpoint = !/(^|\.)datalyr\.com$/i.test(new URL(currentEndpoint).hostname);
2308
+ }
2309
+ catch (_a) {
2310
+ /* malformed endpoint — leave false */
2311
+ }
2312
+ try {
2313
+ const response = yield fetch(currentEndpoint, Object.assign({ method: 'POST', headers: {
2305
2314
  'Content-Type': 'application/json',
2306
2315
  'X-Batch-Size': events.length.toString()
2307
- },
2308
- body,
2309
- keepalive: useKeepalive
2310
- });
2316
+ }, body, keepalive: useKeepalive }, (firstPartyEndpoint ? { credentials: 'include' } : {})));
2311
2317
  if (!response.ok) {
2312
2318
  // Handle rate limiting
2313
2319
  if (response.status === 429) {
@@ -4336,7 +4342,7 @@ class Datalyr {
4336
4342
  return this.initializationPromise;
4337
4343
  }
4338
4344
  this.initializationPromise = (() => __awaiter(this, void 0, void 0, function* () {
4339
- var _a;
4345
+ var _a, _b;
4340
4346
  try {
4341
4347
  // SEC-03: encryption is for PII-AT-REST only — it must NOT gate event delivery,
4342
4348
  // pageviews, or pixels. On a non-secure context (http://) or an old browser,
@@ -4459,6 +4465,15 @@ class Datalyr {
4459
4465
  // opted-out / DNT / GPC visitor's id + click-ids aren't stamped on outbound links.
4460
4466
  this.syncOutboundLinkParams(this.config.checkoutChampDomains);
4461
4467
  }
4468
+ // Stripe Payment Link auto-decoration (D1, DEFAULT ON): stamp
4469
+ // client_reference_id=<visitor_id> (+ prefilled_email when identified) onto
4470
+ // buy.stripe.com / custom payment-link domain anchors and Stripe embeds, so a
4471
+ // snippet-only merchant's checkout.session.completed webhooks attribute
4472
+ // deterministically with zero server work. Gated on shouldTrack() so
4473
+ // opted-out / DNT / GPC visitors never get an id stamped into outbound links.
4474
+ if (this.config.stripePaymentLinks !== false && this.shouldTrack()) {
4475
+ this.syncStripePaymentLinks((_b = this.config.stripeLinkDomains) !== null && _b !== void 0 ? _b : []);
4476
+ }
4462
4477
  // Track initial page view if enabled (AFTER encryption ready)
4463
4478
  if (this.config.trackPageViews) {
4464
4479
  this.page();
@@ -5379,6 +5394,159 @@ class Datalyr {
5379
5394
  }
5380
5395
  this.log("Checkout Champ outbound-link sync active for:", lowerDomains);
5381
5396
  }
5397
+ /**
5398
+ * Stripe Payment Link auto-decoration (D1). Structurally mirrors
5399
+ * syncOutboundLinkParams: stampAll on init, debounced MutationObserver for
5400
+ * SPA-rendered anchors, capture-phase click re-stamp + queue flush, disposer
5401
+ * wired into destroy()/pagehide.
5402
+ *
5403
+ * Match: exact-host equality against buy.stripe.com + config.stripeLinkDomains
5404
+ * via the URL API — NEVER substring (a[href*=...] would match
5405
+ * evil.com/buy.stripe.com paths) and NEVER checkout.stripe.com (Checkout
5406
+ * Session URLs ignore these params — the session is already created).
5407
+ *
5408
+ * Stamp (URL API preserves existing query + hash):
5409
+ * - client_reference_id=<visitorId> only if the param is absent (merchant-set
5410
+ * wins) and the id passes the Stripe format guard (Stripe silently DROPS
5411
+ * invalid values — don't write garbage into merchant links).
5412
+ * - prefilled_email=<email> only if identified email exists, the param is
5413
+ * absent, locked_prefilled_email is absent, and privacyMode !== 'strict'.
5414
+ * - <stripe-pricing-table>/<stripe-buy-button>: client-reference-id attribute
5415
+ * if absent.
5416
+ *
5417
+ * The decorated link's checkout.session.completed arrives with
5418
+ * client_reference_id, which stripe-connect.js already reads — zero server
5419
+ * changes. window.open(paymentLink) is not covered (use getVisitorId()
5420
+ * manually); iframe/shadow-DOM links are unreachable from document.
5421
+ */
5422
+ syncStripePaymentLinks(extraDomains) {
5423
+ if (typeof window === "undefined" || typeof document === "undefined")
5424
+ return;
5425
+ const hosts = new Set(["buy.stripe.com"]);
5426
+ for (const d of extraDomains) {
5427
+ const h = String(d).trim().toLowerCase();
5428
+ if (h)
5429
+ hosts.add(h);
5430
+ }
5431
+ const matchesPaymentLinkHost = (href) => {
5432
+ try {
5433
+ const host = new URL(href, window.location.href).hostname.toLowerCase();
5434
+ return hosts.has(host); // exact-host equality only
5435
+ }
5436
+ catch (_a) {
5437
+ return false;
5438
+ }
5439
+ };
5440
+ // Stripe accepts alphanumeric + `-` + `_`, max 200 chars, and silently drops
5441
+ // invalid values. Legacy/foreign persisted cookie ids are unconstrained —
5442
+ // skip silently rather than stamp a value Stripe would discard.
5443
+ const CLIENT_REFERENCE_ID_RE = /^[A-Za-z0-9_-]{1,200}$/;
5444
+ const getClientReferenceId = () => {
5445
+ const vid = this.identity.getAnonymousId();
5446
+ return vid && CLIENT_REFERENCE_ID_RE.test(vid) ? vid : null;
5447
+ };
5448
+ const getPrefilledEmail = () => {
5449
+ var _a;
5450
+ if (this.config.privacyMode === "strict")
5451
+ return null;
5452
+ const email = (_a = this.userProperties) === null || _a === void 0 ? void 0 : _a.email;
5453
+ return typeof email === "string" && email ? email : null;
5454
+ };
5455
+ const stampLink = (anchor) => {
5456
+ if (!anchor.href || !matchesPaymentLinkHost(anchor.href))
5457
+ return;
5458
+ try {
5459
+ const u = new URL(anchor.href, window.location.href);
5460
+ let mutated = false;
5461
+ const vid = getClientReferenceId();
5462
+ if (vid && !u.searchParams.get("client_reference_id")) {
5463
+ u.searchParams.set("client_reference_id", vid);
5464
+ mutated = true;
5465
+ }
5466
+ const email = getPrefilledEmail();
5467
+ if (email &&
5468
+ !u.searchParams.get("prefilled_email") &&
5469
+ !u.searchParams.get("locked_prefilled_email")) {
5470
+ // URL API percent-encodes automatically.
5471
+ u.searchParams.set("prefilled_email", email);
5472
+ mutated = true;
5473
+ }
5474
+ if (mutated)
5475
+ anchor.href = u.toString();
5476
+ }
5477
+ catch (_a) {
5478
+ // Ignore malformed URLs — don't break the page.
5479
+ }
5480
+ };
5481
+ const stampEmbeds = () => {
5482
+ const vid = getClientReferenceId();
5483
+ if (!vid)
5484
+ return;
5485
+ document.querySelectorAll("stripe-pricing-table, stripe-buy-button").forEach((el) => {
5486
+ if (!el.getAttribute("client-reference-id")) {
5487
+ el.setAttribute("client-reference-id", vid);
5488
+ }
5489
+ });
5490
+ };
5491
+ const stampAll = () => {
5492
+ document.querySelectorAll("a[href]").forEach((el) => stampLink(el));
5493
+ stampEmbeds();
5494
+ };
5495
+ const onClick = (e) => {
5496
+ var _a, _b;
5497
+ const target = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest("a[href]");
5498
+ if (!target)
5499
+ return;
5500
+ const anchor = target;
5501
+ if (!matchesPaymentLinkHost(anchor.href))
5502
+ return;
5503
+ stampLink(anchor); // re-stamp in case identify() happened since DOMReady
5504
+ try {
5505
+ (_b = this.queue) === null || _b === void 0 ? void 0 : _b.flush();
5506
+ }
5507
+ catch (_c) {
5508
+ // Best-effort — never block navigation.
5509
+ }
5510
+ };
5511
+ stampAll();
5512
+ document.addEventListener("click", onClick, true);
5513
+ try {
5514
+ // Debounce re-stamping (same rationale as the CC sync): a busy SPA can fire
5515
+ // thousands of mutations per second; 150ms is small enough to catch links
5516
+ // before the user can click them, large enough to coalesce bursts. The
5517
+ // click-time re-stamp is the final safety net — no pushState hook needed.
5518
+ let restampTimer = null;
5519
+ const scheduleRestamp = () => {
5520
+ if (restampTimer != null)
5521
+ return;
5522
+ restampTimer = setTimeout(() => {
5523
+ restampTimer = null;
5524
+ stampAll();
5525
+ }, 150);
5526
+ };
5527
+ const observer = new MutationObserver(scheduleRestamp);
5528
+ observer.observe(document.documentElement, { childList: true, subtree: true });
5529
+ // Observer + click listener live for the session; pagehide cleans up on
5530
+ // full-page unload, and destroy() tears them down early via this disposer
5531
+ // (otherwise destroy()+re-init() leaks the observer + capturing listener).
5532
+ this.stripeLinksDisposer = () => {
5533
+ try {
5534
+ observer.disconnect();
5535
+ }
5536
+ catch ( /* idempotent */_a) { /* idempotent */ }
5537
+ if (restampTimer != null) {
5538
+ clearTimeout(restampTimer);
5539
+ restampTimer = null;
5540
+ }
5541
+ document.removeEventListener("click", onClick, true);
5542
+ };
5543
+ window.addEventListener("pagehide", () => { var _a; return (_a = this.stripeLinksDisposer) === null || _a === void 0 ? void 0 : _a.call(this); }, { once: true });
5544
+ }
5545
+ catch (error) {
5546
+ this.log("MutationObserver setup failed (Stripe Payment Link sync):", error);
5547
+ }
5548
+ this.log("Stripe Payment Link auto-decoration active for:", Array.from(hosts));
5549
+ }
5382
5550
  /**
5383
5551
  * Create event payload
5384
5552
  */
@@ -5461,7 +5629,7 @@ class Datalyr {
5461
5629
  // SDK metadata. MUST stay in sync with package.json "version" — the build guard
5462
5630
  // (scripts/check-bundle.js, run by build:check) fails the build if the bundle's
5463
5631
  // sdk_version doesn't match package.json. (FSR-103)
5464
- sdk_version: '1.7.3',
5632
+ sdk_version: '1.7.4',
5465
5633
  sdk_name: 'datalyr-web-sdk'
5466
5634
  };
5467
5635
  return payload;
@@ -5682,6 +5850,10 @@ class Datalyr {
5682
5850
  this.outboundDisposer();
5683
5851
  this.outboundDisposer = undefined;
5684
5852
  }
5853
+ if (this.stripeLinksDisposer) {
5854
+ this.stripeLinksDisposer();
5855
+ this.stripeLinksDisposer = undefined;
5856
+ }
5685
5857
  // Clean up queue
5686
5858
  if (this.queue) {
5687
5859
  this.queue.destroy();