@datalyr/web 1.7.3 → 1.7.4

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