@ekomerc/storefront 0.1.0 → 0.1.3
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/README.md +237 -10
- package/dist/README.md +679 -0
- package/dist/errors.d.cts +74 -0
- package/dist/errors.d.ts +74 -80
- package/dist/index.cjs +2840 -1810
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1493 -0
- package/dist/index.d.ts +1493 -952
- package/dist/index.js +2841 -1811
- package/dist/index.js.map +1 -1
- package/dist/package.json +45 -0
- package/dist/types.d.cts +656 -0
- package/dist/types.d.ts +656 -1
- package/package.json +12 -5
package/dist/index.cjs
CHANGED
|
@@ -107,7 +107,7 @@ const ADDRESS_FRAGMENT = `
|
|
|
107
107
|
addressLine1
|
|
108
108
|
addressLine2
|
|
109
109
|
city
|
|
110
|
-
|
|
110
|
+
zip
|
|
111
111
|
phone
|
|
112
112
|
isDefault
|
|
113
113
|
createdAt
|
|
@@ -185,7 +185,7 @@ function mapAddressData(data) {
|
|
|
185
185
|
addressLine1: data.addressLine1,
|
|
186
186
|
addressLine2: data.addressLine2,
|
|
187
187
|
city: data.city,
|
|
188
|
-
|
|
188
|
+
zip: data.zip,
|
|
189
189
|
phone: data.phone,
|
|
190
190
|
isDefault: data.isDefault,
|
|
191
191
|
createdAt: data.createdAt
|
|
@@ -459,117 +459,6 @@ function createAuthOperations(client, storage) {
|
|
|
459
459
|
}
|
|
460
460
|
};
|
|
461
461
|
}
|
|
462
|
-
const TRACKING_SCHEMA_VERSION$1 = 1;
|
|
463
|
-
const TRACKING_ATTRIBUTION_STORAGE_KEY = "srb_tracking_attribution";
|
|
464
|
-
function hasBrowserContext$1() {
|
|
465
|
-
return typeof window !== "undefined";
|
|
466
|
-
}
|
|
467
|
-
function normalizeParam(value) {
|
|
468
|
-
if (value === null) return null;
|
|
469
|
-
const trimmed = value.trim();
|
|
470
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
471
|
-
}
|
|
472
|
-
function hasAttributionValue(snapshot) {
|
|
473
|
-
return [
|
|
474
|
-
snapshot.utm.source,
|
|
475
|
-
snapshot.utm.medium,
|
|
476
|
-
snapshot.utm.campaign,
|
|
477
|
-
snapshot.utm.term,
|
|
478
|
-
snapshot.utm.content,
|
|
479
|
-
snapshot.clickIds.gclid,
|
|
480
|
-
snapshot.clickIds.gbraid,
|
|
481
|
-
snapshot.clickIds.wbraid,
|
|
482
|
-
snapshot.clickIds.ttclid,
|
|
483
|
-
snapshot.clickIds.fbclid
|
|
484
|
-
].some((value) => value !== null);
|
|
485
|
-
}
|
|
486
|
-
function parseStoredSnapshot(raw) {
|
|
487
|
-
if (!raw) return null;
|
|
488
|
-
try {
|
|
489
|
-
const parsed = JSON.parse(raw);
|
|
490
|
-
if (parsed.schemaVersion !== TRACKING_SCHEMA_VERSION$1 || typeof parsed.capturedAt !== "string") {
|
|
491
|
-
return null;
|
|
492
|
-
}
|
|
493
|
-
const utm = parsed.utm;
|
|
494
|
-
const clickIds = parsed.clickIds;
|
|
495
|
-
if (!utm || !clickIds) return null;
|
|
496
|
-
return {
|
|
497
|
-
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
498
|
-
capturedAt: parsed.capturedAt,
|
|
499
|
-
utm: {
|
|
500
|
-
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
501
|
-
source: typeof utm.source === "string" ? utm.source : null,
|
|
502
|
-
medium: typeof utm.medium === "string" ? utm.medium : null,
|
|
503
|
-
campaign: typeof utm.campaign === "string" ? utm.campaign : null,
|
|
504
|
-
term: typeof utm.term === "string" ? utm.term : null,
|
|
505
|
-
content: typeof utm.content === "string" ? utm.content : null
|
|
506
|
-
},
|
|
507
|
-
clickIds: {
|
|
508
|
-
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
509
|
-
capturedAt: typeof clickIds.capturedAt === "string" ? clickIds.capturedAt : null,
|
|
510
|
-
gclid: typeof clickIds.gclid === "string" ? clickIds.gclid : null,
|
|
511
|
-
gbraid: typeof clickIds.gbraid === "string" ? clickIds.gbraid : null,
|
|
512
|
-
wbraid: typeof clickIds.wbraid === "string" ? clickIds.wbraid : null,
|
|
513
|
-
ttclid: typeof clickIds.ttclid === "string" ? clickIds.ttclid : null,
|
|
514
|
-
fbclid: typeof clickIds.fbclid === "string" ? clickIds.fbclid : null
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
} catch {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
function readStoredSnapshot() {
|
|
522
|
-
if (!hasBrowserContext$1()) return null;
|
|
523
|
-
try {
|
|
524
|
-
return parseStoredSnapshot(window.localStorage.getItem(TRACKING_ATTRIBUTION_STORAGE_KEY));
|
|
525
|
-
} catch {
|
|
526
|
-
return null;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
function writeStoredSnapshot(snapshot) {
|
|
530
|
-
if (!hasBrowserContext$1()) return;
|
|
531
|
-
try {
|
|
532
|
-
window.localStorage.setItem(TRACKING_ATTRIBUTION_STORAGE_KEY, JSON.stringify(snapshot));
|
|
533
|
-
} catch {
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
function buildSnapshotFromSearch(search) {
|
|
537
|
-
const params = new URLSearchParams(search);
|
|
538
|
-
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
539
|
-
return {
|
|
540
|
-
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
541
|
-
capturedAt,
|
|
542
|
-
utm: {
|
|
543
|
-
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
544
|
-
source: normalizeParam(params.get("utm_source")),
|
|
545
|
-
medium: normalizeParam(params.get("utm_medium")),
|
|
546
|
-
campaign: normalizeParam(params.get("utm_campaign")),
|
|
547
|
-
term: normalizeParam(params.get("utm_term")),
|
|
548
|
-
content: normalizeParam(params.get("utm_content"))
|
|
549
|
-
},
|
|
550
|
-
clickIds: {
|
|
551
|
-
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
552
|
-
capturedAt,
|
|
553
|
-
gclid: normalizeParam(params.get("gclid")),
|
|
554
|
-
gbraid: normalizeParam(params.get("gbraid")),
|
|
555
|
-
wbraid: normalizeParam(params.get("wbraid")),
|
|
556
|
-
ttclid: normalizeParam(params.get("ttclid")),
|
|
557
|
-
fbclid: normalizeParam(params.get("fbclid"))
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function captureLandingTrackingAttribution() {
|
|
562
|
-
if (!hasBrowserContext$1()) return null;
|
|
563
|
-
const snapshot = buildSnapshotFromSearch(window.location.search);
|
|
564
|
-
if (hasAttributionValue(snapshot)) {
|
|
565
|
-
writeStoredSnapshot(snapshot);
|
|
566
|
-
return snapshot;
|
|
567
|
-
}
|
|
568
|
-
return readStoredSnapshot();
|
|
569
|
-
}
|
|
570
|
-
function getTrackingAttributionSnapshot() {
|
|
571
|
-
return readStoredSnapshot();
|
|
572
|
-
}
|
|
573
462
|
function isAbsoluteUrl(value) {
|
|
574
463
|
return /^https?:\/\//i.test(value);
|
|
575
464
|
}
|
|
@@ -619,7 +508,7 @@ function normalizeCollectionAssetUrls(collection, endpoint) {
|
|
|
619
508
|
imageAsset: collection.imageAsset ? { ...collection.imageAsset, url: resolveAssetUrl(collection.imageAsset.url, endpoint) } : null
|
|
620
509
|
};
|
|
621
510
|
}
|
|
622
|
-
const CART_FRAGMENT
|
|
511
|
+
const CART_FRAGMENT = `
|
|
623
512
|
id
|
|
624
513
|
token
|
|
625
514
|
status
|
|
@@ -685,114 +574,7 @@ const CART_FRAGMENT$1 = `
|
|
|
685
574
|
createdAt
|
|
686
575
|
updatedAt
|
|
687
576
|
`;
|
|
688
|
-
|
|
689
|
-
query Cart {
|
|
690
|
-
cart {
|
|
691
|
-
${CART_FRAGMENT$1}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
`;
|
|
695
|
-
const CART_CREATE_MUTATION = `
|
|
696
|
-
mutation CartCreate {
|
|
697
|
-
cartCreate {
|
|
698
|
-
cart {
|
|
699
|
-
${CART_FRAGMENT$1}
|
|
700
|
-
}
|
|
701
|
-
token
|
|
702
|
-
userErrors {
|
|
703
|
-
field
|
|
704
|
-
message
|
|
705
|
-
code
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
`;
|
|
710
|
-
const CART_ITEM_ADD_MUTATION = `
|
|
711
|
-
mutation CartItemAdd($input: CartItemAddInput!) {
|
|
712
|
-
cartItemAdd(input: $input) {
|
|
713
|
-
cart {
|
|
714
|
-
${CART_FRAGMENT$1}
|
|
715
|
-
}
|
|
716
|
-
userErrors {
|
|
717
|
-
field
|
|
718
|
-
message
|
|
719
|
-
code
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
`;
|
|
724
|
-
const CART_ITEM_UPDATE_MUTATION = `
|
|
725
|
-
mutation CartItemUpdate($input: CartItemUpdateInput!) {
|
|
726
|
-
cartItemUpdate(input: $input) {
|
|
727
|
-
cart {
|
|
728
|
-
${CART_FRAGMENT$1}
|
|
729
|
-
}
|
|
730
|
-
userErrors {
|
|
731
|
-
field
|
|
732
|
-
message
|
|
733
|
-
code
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
`;
|
|
738
|
-
const CART_ITEM_REMOVE_MUTATION = `
|
|
739
|
-
mutation CartItemRemove($input: CartItemRemoveInput!) {
|
|
740
|
-
cartItemRemove(input: $input) {
|
|
741
|
-
cart {
|
|
742
|
-
${CART_FRAGMENT$1}
|
|
743
|
-
}
|
|
744
|
-
userErrors {
|
|
745
|
-
field
|
|
746
|
-
message
|
|
747
|
-
code
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
`;
|
|
752
|
-
const CART_CLEAR_MUTATION = `
|
|
753
|
-
mutation CartClear {
|
|
754
|
-
cartClear {
|
|
755
|
-
cart {
|
|
756
|
-
${CART_FRAGMENT$1}
|
|
757
|
-
}
|
|
758
|
-
userErrors {
|
|
759
|
-
field
|
|
760
|
-
message
|
|
761
|
-
code
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
`;
|
|
766
|
-
const CART_PROMO_CODE_APPLY_MUTATION = `
|
|
767
|
-
mutation CartPromoCodeApply($input: CartPromoCodeApplyInput!) {
|
|
768
|
-
cartPromoCodeApply(input: $input) {
|
|
769
|
-
cart {
|
|
770
|
-
${CART_FRAGMENT$1}
|
|
771
|
-
}
|
|
772
|
-
userErrors {
|
|
773
|
-
field
|
|
774
|
-
message
|
|
775
|
-
code
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
`;
|
|
780
|
-
const CART_PROMO_CODE_REMOVE_MUTATION = `
|
|
781
|
-
mutation CartPromoCodeRemove {
|
|
782
|
-
cartPromoCodeRemove {
|
|
783
|
-
cart {
|
|
784
|
-
${CART_FRAGMENT$1}
|
|
785
|
-
}
|
|
786
|
-
userErrors {
|
|
787
|
-
field
|
|
788
|
-
message
|
|
789
|
-
code
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
`;
|
|
794
|
-
const INVALID_CART_STATES = ["checkout", "converted", "abandoned", "expired"];
|
|
795
|
-
function mapCartData$1(data, endpoint) {
|
|
577
|
+
function mapCartData(data, endpoint) {
|
|
796
578
|
return {
|
|
797
579
|
id: data.id,
|
|
798
580
|
token: data.token,
|
|
@@ -831,45 +613,319 @@ function mapCartData$1(data, endpoint) {
|
|
|
831
613
|
updatedAt: data.updatedAt
|
|
832
614
|
};
|
|
833
615
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
616
|
+
const TRACKING_SCHEMA_VERSION$1 = 1;
|
|
617
|
+
const TRACKING_ATTRIBUTION_STORAGE_KEY = "ekomerc_tracking_attribution";
|
|
618
|
+
const DEFAULT_TRACKING_ATTRIBUTION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
619
|
+
const TRACKING_ATTRIBUTION_QUERY_PARAM_KEYS = [
|
|
620
|
+
"utm_source",
|
|
621
|
+
"utm_medium",
|
|
622
|
+
"utm_campaign",
|
|
623
|
+
"utm_term",
|
|
624
|
+
"utm_content",
|
|
625
|
+
"gclid",
|
|
626
|
+
"gbraid",
|
|
627
|
+
"wbraid",
|
|
628
|
+
"ttclid",
|
|
629
|
+
"fbclid"
|
|
630
|
+
];
|
|
631
|
+
function hasBrowserContext$1() {
|
|
632
|
+
return typeof window !== "undefined";
|
|
633
|
+
}
|
|
634
|
+
function hasTrackingAttributionParams(search) {
|
|
635
|
+
const params = new URLSearchParams(search);
|
|
636
|
+
return TRACKING_ATTRIBUTION_QUERY_PARAM_KEYS.some((key) => {
|
|
637
|
+
const value = params.get(key);
|
|
638
|
+
return value !== null && value.trim().length > 0;
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
function normalizeParam(value) {
|
|
642
|
+
if (value === null) return null;
|
|
643
|
+
const trimmed = value.trim();
|
|
644
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
645
|
+
}
|
|
646
|
+
function hasAttributionValue(snapshot) {
|
|
647
|
+
return [
|
|
648
|
+
snapshot.utm.source,
|
|
649
|
+
snapshot.utm.medium,
|
|
650
|
+
snapshot.utm.campaign,
|
|
651
|
+
snapshot.utm.term,
|
|
652
|
+
snapshot.utm.content,
|
|
653
|
+
snapshot.clickIds.gclid,
|
|
654
|
+
snapshot.clickIds.gbraid,
|
|
655
|
+
snapshot.clickIds.wbraid,
|
|
656
|
+
snapshot.clickIds.ttclid,
|
|
657
|
+
snapshot.clickIds.fbclid
|
|
658
|
+
].some((value) => value !== null);
|
|
659
|
+
}
|
|
660
|
+
function resolveTrackingAttributionTtlMs(options) {
|
|
661
|
+
const ttlMs = options?.ttlMs;
|
|
662
|
+
if (typeof ttlMs !== "number" || !Number.isFinite(ttlMs) || ttlMs < 0) {
|
|
663
|
+
return DEFAULT_TRACKING_ATTRIBUTION_TTL_MS;
|
|
842
664
|
}
|
|
843
|
-
return
|
|
665
|
+
return ttlMs;
|
|
844
666
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if (stateError) {
|
|
850
|
-
return new errors.StateError(messages, "unknown");
|
|
667
|
+
function isSnapshotExpired(snapshot, ttlMs) {
|
|
668
|
+
const capturedAtMs = Date.parse(snapshot.capturedAt);
|
|
669
|
+
if (Number.isNaN(capturedAtMs)) {
|
|
670
|
+
return true;
|
|
851
671
|
}
|
|
852
|
-
return
|
|
853
|
-
messages,
|
|
854
|
-
userErrors.map((e) => ({ field: e.field, message: e.message }))
|
|
855
|
-
);
|
|
672
|
+
return Date.now() - capturedAtMs > ttlMs;
|
|
856
673
|
}
|
|
857
|
-
function
|
|
858
|
-
return
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
674
|
+
function parseStoredSnapshot(raw) {
|
|
675
|
+
if (!raw) return null;
|
|
676
|
+
try {
|
|
677
|
+
const parsed = JSON.parse(raw);
|
|
678
|
+
if (parsed.schemaVersion !== TRACKING_SCHEMA_VERSION$1 || typeof parsed.capturedAt !== "string") {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
const utm = parsed.utm;
|
|
682
|
+
const clickIds = parsed.clickIds;
|
|
683
|
+
if (!utm || !clickIds) return null;
|
|
684
|
+
return {
|
|
685
|
+
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
686
|
+
capturedAt: parsed.capturedAt,
|
|
687
|
+
utm: {
|
|
688
|
+
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
689
|
+
source: typeof utm.source === "string" ? utm.source : null,
|
|
690
|
+
medium: typeof utm.medium === "string" ? utm.medium : null,
|
|
691
|
+
campaign: typeof utm.campaign === "string" ? utm.campaign : null,
|
|
692
|
+
term: typeof utm.term === "string" ? utm.term : null,
|
|
693
|
+
content: typeof utm.content === "string" ? utm.content : null
|
|
694
|
+
},
|
|
695
|
+
clickIds: {
|
|
696
|
+
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
697
|
+
capturedAt: typeof clickIds.capturedAt === "string" ? clickIds.capturedAt : null,
|
|
698
|
+
gclid: typeof clickIds.gclid === "string" ? clickIds.gclid : null,
|
|
699
|
+
gbraid: typeof clickIds.gbraid === "string" ? clickIds.gbraid : null,
|
|
700
|
+
wbraid: typeof clickIds.wbraid === "string" ? clickIds.wbraid : null,
|
|
701
|
+
ttclid: typeof clickIds.ttclid === "string" ? clickIds.ttclid : null,
|
|
702
|
+
fbclid: typeof clickIds.fbclid === "string" ? clickIds.fbclid : null
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
} catch {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function removeStoredSnapshot() {
|
|
710
|
+
if (!hasBrowserContext$1()) return;
|
|
711
|
+
try {
|
|
712
|
+
window.localStorage.removeItem(TRACKING_ATTRIBUTION_STORAGE_KEY);
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function readStoredSnapshot(options) {
|
|
717
|
+
if (!hasBrowserContext$1()) return null;
|
|
718
|
+
try {
|
|
719
|
+
const snapshot = parseStoredSnapshot(window.localStorage.getItem(TRACKING_ATTRIBUTION_STORAGE_KEY));
|
|
720
|
+
if (!snapshot) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
if (isSnapshotExpired(snapshot, resolveTrackingAttributionTtlMs(options))) {
|
|
724
|
+
removeStoredSnapshot();
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
return snapshot;
|
|
728
|
+
} catch {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function writeStoredSnapshot(snapshot) {
|
|
733
|
+
if (!hasBrowserContext$1()) return;
|
|
734
|
+
try {
|
|
735
|
+
window.localStorage.setItem(TRACKING_ATTRIBUTION_STORAGE_KEY, JSON.stringify(snapshot));
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function buildSnapshotFromSearch(search) {
|
|
740
|
+
const params = new URLSearchParams(search);
|
|
741
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
742
|
+
return {
|
|
743
|
+
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
744
|
+
capturedAt,
|
|
745
|
+
utm: {
|
|
746
|
+
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
747
|
+
source: normalizeParam(params.get("utm_source")),
|
|
748
|
+
medium: normalizeParam(params.get("utm_medium")),
|
|
749
|
+
campaign: normalizeParam(params.get("utm_campaign")),
|
|
750
|
+
term: normalizeParam(params.get("utm_term")),
|
|
751
|
+
content: normalizeParam(params.get("utm_content"))
|
|
752
|
+
},
|
|
753
|
+
clickIds: {
|
|
754
|
+
schemaVersion: TRACKING_SCHEMA_VERSION$1,
|
|
755
|
+
capturedAt,
|
|
756
|
+
gclid: normalizeParam(params.get("gclid")),
|
|
757
|
+
gbraid: normalizeParam(params.get("gbraid")),
|
|
758
|
+
wbraid: normalizeParam(params.get("wbraid")),
|
|
759
|
+
ttclid: normalizeParam(params.get("ttclid")),
|
|
760
|
+
fbclid: normalizeParam(params.get("fbclid"))
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function captureLandingTrackingAttribution(options) {
|
|
765
|
+
if (!hasBrowserContext$1()) return null;
|
|
766
|
+
const snapshot = buildSnapshotFromSearch(window.location.search);
|
|
767
|
+
if (hasAttributionValue(snapshot)) {
|
|
768
|
+
writeStoredSnapshot(snapshot);
|
|
769
|
+
return snapshot;
|
|
770
|
+
}
|
|
771
|
+
return readStoredSnapshot(options);
|
|
772
|
+
}
|
|
773
|
+
function getTrackingAttributionSnapshot(options) {
|
|
774
|
+
return readStoredSnapshot(options);
|
|
775
|
+
}
|
|
776
|
+
function stripLandingTrackingAttributionFromUrl() {
|
|
777
|
+
if (!hasBrowserContext$1()) return false;
|
|
778
|
+
if (!hasTrackingAttributionParams(window.location.search)) return false;
|
|
779
|
+
if (typeof window.history?.replaceState !== "function") return false;
|
|
780
|
+
const params = new URLSearchParams(window.location.search);
|
|
781
|
+
for (const key of TRACKING_ATTRIBUTION_QUERY_PARAM_KEYS) {
|
|
782
|
+
params.delete(key);
|
|
783
|
+
}
|
|
784
|
+
const search = params.toString();
|
|
785
|
+
const hash = window.location.hash ?? "";
|
|
786
|
+
const nextUrl = `${window.location.pathname}${search ? `?${search}` : ""}${hash}`;
|
|
787
|
+
window.history.replaceState(window.history.state, "", nextUrl);
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
const CART_QUERY = `
|
|
791
|
+
query Cart {
|
|
792
|
+
cart {
|
|
793
|
+
${CART_FRAGMENT}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
`;
|
|
797
|
+
const CART_CREATE_MUTATION = `
|
|
798
|
+
mutation CartCreate {
|
|
799
|
+
cartCreate {
|
|
800
|
+
cart {
|
|
801
|
+
${CART_FRAGMENT}
|
|
802
|
+
}
|
|
803
|
+
token
|
|
804
|
+
userErrors {
|
|
805
|
+
field
|
|
806
|
+
message
|
|
807
|
+
code
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
`;
|
|
812
|
+
const CART_ITEM_ADD_MUTATION = `
|
|
813
|
+
mutation CartItemAdd($input: CartItemAddInput!) {
|
|
814
|
+
cartItemAdd(input: $input) {
|
|
815
|
+
cart {
|
|
816
|
+
${CART_FRAGMENT}
|
|
817
|
+
}
|
|
818
|
+
userErrors {
|
|
819
|
+
field
|
|
820
|
+
message
|
|
821
|
+
code
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
`;
|
|
826
|
+
const CART_ITEM_UPDATE_MUTATION = `
|
|
827
|
+
mutation CartItemUpdate($input: CartItemUpdateInput!) {
|
|
828
|
+
cartItemUpdate(input: $input) {
|
|
829
|
+
cart {
|
|
830
|
+
${CART_FRAGMENT}
|
|
831
|
+
}
|
|
832
|
+
userErrors {
|
|
833
|
+
field
|
|
834
|
+
message
|
|
835
|
+
code
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
`;
|
|
840
|
+
const CART_ITEM_REMOVE_MUTATION = `
|
|
841
|
+
mutation CartItemRemove($input: CartItemRemoveInput!) {
|
|
842
|
+
cartItemRemove(input: $input) {
|
|
843
|
+
cart {
|
|
844
|
+
${CART_FRAGMENT}
|
|
845
|
+
}
|
|
846
|
+
userErrors {
|
|
847
|
+
field
|
|
848
|
+
message
|
|
849
|
+
code
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
`;
|
|
854
|
+
const CART_CLEAR_MUTATION = `
|
|
855
|
+
mutation CartClear {
|
|
856
|
+
cartClear {
|
|
857
|
+
cart {
|
|
858
|
+
${CART_FRAGMENT}
|
|
859
|
+
}
|
|
860
|
+
userErrors {
|
|
861
|
+
field
|
|
862
|
+
message
|
|
863
|
+
code
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
`;
|
|
868
|
+
const CART_PROMO_CODE_APPLY_MUTATION = `
|
|
869
|
+
mutation CartPromoCodeApply($input: CartPromoCodeApplyInput!) {
|
|
870
|
+
cartPromoCodeApply(input: $input) {
|
|
871
|
+
cart {
|
|
872
|
+
${CART_FRAGMENT}
|
|
873
|
+
}
|
|
874
|
+
userErrors {
|
|
875
|
+
field
|
|
876
|
+
message
|
|
877
|
+
code
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
`;
|
|
882
|
+
const CART_PROMO_CODE_REMOVE_MUTATION = `
|
|
883
|
+
mutation CartPromoCodeRemove {
|
|
884
|
+
cartPromoCodeRemove {
|
|
885
|
+
cart {
|
|
886
|
+
${CART_FRAGMENT}
|
|
887
|
+
}
|
|
888
|
+
userErrors {
|
|
889
|
+
field
|
|
890
|
+
message
|
|
891
|
+
code
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
`;
|
|
896
|
+
function handleUserErrors$1(userErrors) {
|
|
897
|
+
if (userErrors.length === 0) return null;
|
|
898
|
+
const messages = userErrors.map((e) => e.message).join("; ");
|
|
899
|
+
const stateError = userErrors.find((e) => e.code?.includes("STATE") || e.message.includes("state"));
|
|
900
|
+
if (stateError) {
|
|
901
|
+
return new errors.StateError(messages, "unknown");
|
|
902
|
+
}
|
|
903
|
+
return new errors.ValidationError(
|
|
904
|
+
messages,
|
|
905
|
+
userErrors.map((e) => ({ field: e.field, message: e.message }))
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
function createCartOperations(client, storage) {
|
|
909
|
+
function getTrackingAttribution() {
|
|
910
|
+
return captureLandingTrackingAttribution({
|
|
911
|
+
ttlMs: client.config.trackingAttributionTTL
|
|
912
|
+
}) ?? void 0;
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
async get() {
|
|
916
|
+
const token = storage.get(CART_TOKEN_KEY);
|
|
917
|
+
if (!token) {
|
|
918
|
+
return neverthrow.ok(null);
|
|
919
|
+
}
|
|
920
|
+
const result = await client.query({ query: CART_QUERY }, { cache: false });
|
|
921
|
+
if (result.isErr()) {
|
|
922
|
+
return neverthrow.err(result.error);
|
|
923
|
+
}
|
|
924
|
+
if (!result.value.cart) {
|
|
869
925
|
storage.remove(CART_TOKEN_KEY);
|
|
870
926
|
return neverthrow.ok(null);
|
|
871
927
|
}
|
|
872
|
-
return neverthrow.ok(mapCartData
|
|
928
|
+
return neverthrow.ok(mapCartData(result.value.cart, client.config.endpoint));
|
|
873
929
|
},
|
|
874
930
|
async create() {
|
|
875
931
|
const result = await client.mutate({
|
|
@@ -887,7 +943,7 @@ function createCartOperations(client, storage) {
|
|
|
887
943
|
return neverthrow.err(new errors.NotFoundError("Failed to create cart"));
|
|
888
944
|
}
|
|
889
945
|
storage.set(CART_TOKEN_KEY, payload.token);
|
|
890
|
-
return neverthrow.ok(mapCartData
|
|
946
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
891
947
|
},
|
|
892
948
|
async addItem(variantId, quantity) {
|
|
893
949
|
const token = storage.get(CART_TOKEN_KEY);
|
|
@@ -900,7 +956,7 @@ function createCartOperations(client, storage) {
|
|
|
900
956
|
input: {
|
|
901
957
|
variantId,
|
|
902
958
|
quantity,
|
|
903
|
-
trackingAttribution:
|
|
959
|
+
trackingAttribution: getTrackingAttribution()
|
|
904
960
|
}
|
|
905
961
|
}
|
|
906
962
|
});
|
|
@@ -915,11 +971,7 @@ function createCartOperations(client, storage) {
|
|
|
915
971
|
if (!payload.cart) {
|
|
916
972
|
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
917
973
|
}
|
|
918
|
-
|
|
919
|
-
if (stateCheck.isErr()) {
|
|
920
|
-
return neverthrow.err(stateCheck.error);
|
|
921
|
-
}
|
|
922
|
-
return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
|
|
974
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
923
975
|
},
|
|
924
976
|
async updateItem(variantId, quantity) {
|
|
925
977
|
const token = storage.get(CART_TOKEN_KEY);
|
|
@@ -932,7 +984,7 @@ function createCartOperations(client, storage) {
|
|
|
932
984
|
input: {
|
|
933
985
|
variantId,
|
|
934
986
|
quantity,
|
|
935
|
-
trackingAttribution:
|
|
987
|
+
trackingAttribution: getTrackingAttribution()
|
|
936
988
|
}
|
|
937
989
|
}
|
|
938
990
|
});
|
|
@@ -947,11 +999,7 @@ function createCartOperations(client, storage) {
|
|
|
947
999
|
if (!payload.cart) {
|
|
948
1000
|
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
949
1001
|
}
|
|
950
|
-
|
|
951
|
-
if (stateCheck.isErr()) {
|
|
952
|
-
return neverthrow.err(stateCheck.error);
|
|
953
|
-
}
|
|
954
|
-
return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
|
|
1002
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
955
1003
|
},
|
|
956
1004
|
async removeItem(variantId) {
|
|
957
1005
|
const token = storage.get(CART_TOKEN_KEY);
|
|
@@ -963,7 +1011,7 @@ function createCartOperations(client, storage) {
|
|
|
963
1011
|
variables: {
|
|
964
1012
|
input: {
|
|
965
1013
|
variantId,
|
|
966
|
-
trackingAttribution:
|
|
1014
|
+
trackingAttribution: getTrackingAttribution()
|
|
967
1015
|
}
|
|
968
1016
|
}
|
|
969
1017
|
});
|
|
@@ -978,11 +1026,7 @@ function createCartOperations(client, storage) {
|
|
|
978
1026
|
if (!payload.cart) {
|
|
979
1027
|
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
980
1028
|
}
|
|
981
|
-
|
|
982
|
-
if (stateCheck.isErr()) {
|
|
983
|
-
return neverthrow.err(stateCheck.error);
|
|
984
|
-
}
|
|
985
|
-
return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
|
|
1029
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
986
1030
|
},
|
|
987
1031
|
async clear() {
|
|
988
1032
|
const token = storage.get(CART_TOKEN_KEY);
|
|
@@ -1003,11 +1047,7 @@ function createCartOperations(client, storage) {
|
|
|
1003
1047
|
if (!payload.cart) {
|
|
1004
1048
|
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
1005
1049
|
}
|
|
1006
|
-
|
|
1007
|
-
if (stateCheck.isErr()) {
|
|
1008
|
-
return neverthrow.err(stateCheck.error);
|
|
1009
|
-
}
|
|
1010
|
-
return neverthrow.ok(mapCartData$1(payload.cart, client.config.endpoint));
|
|
1050
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
1011
1051
|
},
|
|
1012
1052
|
async applyPromoCode(code) {
|
|
1013
1053
|
const token = storage.get(CART_TOKEN_KEY);
|
|
@@ -1029,7 +1069,7 @@ function createCartOperations(client, storage) {
|
|
|
1029
1069
|
if (!payload.cart) {
|
|
1030
1070
|
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
1031
1071
|
}
|
|
1032
|
-
return neverthrow.ok(mapCartData
|
|
1072
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
1033
1073
|
},
|
|
1034
1074
|
async removePromoCode() {
|
|
1035
1075
|
const token = storage.get(CART_TOKEN_KEY);
|
|
@@ -1050,10 +1090,21 @@ function createCartOperations(client, storage) {
|
|
|
1050
1090
|
if (!payload.cart) {
|
|
1051
1091
|
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
1052
1092
|
}
|
|
1053
|
-
return neverthrow.ok(mapCartData
|
|
1093
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
1054
1094
|
}
|
|
1055
1095
|
};
|
|
1056
1096
|
}
|
|
1097
|
+
function isGlobalId(value) {
|
|
1098
|
+
if (!value.includes(":")) {
|
|
1099
|
+
try {
|
|
1100
|
+
const decoded = atob(value);
|
|
1101
|
+
return decoded.includes(":");
|
|
1102
|
+
} catch {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return false;
|
|
1107
|
+
}
|
|
1057
1108
|
const CATEGORY_FIELDS = `
|
|
1058
1109
|
id
|
|
1059
1110
|
name
|
|
@@ -1089,7 +1140,7 @@ query CategoriesTree {
|
|
|
1089
1140
|
}
|
|
1090
1141
|
`;
|
|
1091
1142
|
const CATEGORY_BY_ID_QUERY = `
|
|
1092
|
-
query CategoryById($id:
|
|
1143
|
+
query CategoryById($id: GID!) {
|
|
1093
1144
|
storefrontCategory(id: $id) {
|
|
1094
1145
|
${CATEGORY_FIELDS}
|
|
1095
1146
|
parent {
|
|
@@ -1121,7 +1172,7 @@ query CategoryByHandle($handle: String!) {
|
|
|
1121
1172
|
}
|
|
1122
1173
|
`;
|
|
1123
1174
|
const CATEGORY_PRODUCTS_BY_ID_QUERY = `
|
|
1124
|
-
query CategoryProductsById($id:
|
|
1175
|
+
query CategoryProductsById($id: GID!, $first: Int, $after: String, $includeDescendants: Boolean) {
|
|
1125
1176
|
storefrontCategory(id: $id) {
|
|
1126
1177
|
id
|
|
1127
1178
|
products(first: $first, after: $after, includeDescendants: $includeDescendants) {
|
|
@@ -1340,17 +1391,6 @@ function flattenTree(categories, parentId, depth) {
|
|
|
1340
1391
|
}
|
|
1341
1392
|
return result;
|
|
1342
1393
|
}
|
|
1343
|
-
function isGlobalId$2(value) {
|
|
1344
|
-
if (!value.includes(":")) {
|
|
1345
|
-
try {
|
|
1346
|
-
const decoded = atob(value);
|
|
1347
|
-
return decoded.includes(":");
|
|
1348
|
-
} catch {
|
|
1349
|
-
return false;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
return false;
|
|
1353
|
-
}
|
|
1354
1394
|
function createCategoriesOperations(client) {
|
|
1355
1395
|
return {
|
|
1356
1396
|
async tree() {
|
|
@@ -1375,7 +1415,7 @@ function createCategoriesOperations(client) {
|
|
|
1375
1415
|
return neverthrow.ok(flattenTree(tree, null, 0));
|
|
1376
1416
|
},
|
|
1377
1417
|
async get(idOrHandle) {
|
|
1378
|
-
const useId = isGlobalId
|
|
1418
|
+
const useId = isGlobalId(idOrHandle);
|
|
1379
1419
|
const result = await client.query({
|
|
1380
1420
|
query: useId ? CATEGORY_BY_ID_QUERY : CATEGORY_BY_HANDLE_QUERY,
|
|
1381
1421
|
variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
|
|
@@ -1389,7 +1429,7 @@ function createCategoriesOperations(client) {
|
|
|
1389
1429
|
return neverthrow.ok(mapRawCategory(result.value.storefrontCategory, client.config.endpoint));
|
|
1390
1430
|
},
|
|
1391
1431
|
async getProducts(idOrHandle, options) {
|
|
1392
|
-
const useId = isGlobalId
|
|
1432
|
+
const useId = isGlobalId(idOrHandle);
|
|
1393
1433
|
const result = await client.query({
|
|
1394
1434
|
query: useId ? CATEGORY_PRODUCTS_BY_ID_QUERY : CATEGORY_PRODUCTS_BY_HANDLE_QUERY,
|
|
1395
1435
|
variables: {
|
|
@@ -1418,1170 +1458,1711 @@ function createCategoriesOperations(client) {
|
|
|
1418
1458
|
}
|
|
1419
1459
|
};
|
|
1420
1460
|
}
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
image {
|
|
1446
|
-
id
|
|
1447
|
-
url
|
|
1448
|
-
altText
|
|
1449
|
-
position
|
|
1461
|
+
const ANALYTICS_QUEUE_KEY_PREFIX = "ekomerc_aq_";
|
|
1462
|
+
const ANALYTICS_QUEUE_KEY_API_PREFIX_LENGTH = 12;
|
|
1463
|
+
const ANALYTICS_QUEUE_MAX_EVENTS = 100;
|
|
1464
|
+
const ANALYTICS_QUEUE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1465
|
+
function isPlainObject$1(value) {
|
|
1466
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1467
|
+
}
|
|
1468
|
+
function isAnalyticsRetryQueueEntry(value) {
|
|
1469
|
+
if (!isPlainObject$1(value)) {
|
|
1470
|
+
return false;
|
|
1471
|
+
}
|
|
1472
|
+
const event = value.event;
|
|
1473
|
+
return isPlainObject$1(event) && typeof event.eventId === "string" && typeof value.queuedAt === "number" && (value.notBefore === void 0 || typeof value.notBefore === "number");
|
|
1474
|
+
}
|
|
1475
|
+
function getAnalyticsRetryQueueKey(apiKey) {
|
|
1476
|
+
return `${ANALYTICS_QUEUE_KEY_PREFIX}${apiKey.slice(0, ANALYTICS_QUEUE_KEY_API_PREFIX_LENGTH)}`;
|
|
1477
|
+
}
|
|
1478
|
+
function createAnalyticsRetryQueue(storage, apiKey, emitDiagnostic) {
|
|
1479
|
+
const queueKey = getAnalyticsRetryQueueKey(apiKey);
|
|
1480
|
+
function persist(entries) {
|
|
1481
|
+
try {
|
|
1482
|
+
if (entries.length === 0) {
|
|
1483
|
+
storage.remove(queueKey);
|
|
1484
|
+
return;
|
|
1450
1485
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1486
|
+
storage.set(queueKey, JSON.stringify(entries));
|
|
1487
|
+
} catch {
|
|
1453
1488
|
}
|
|
1454
1489
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
appliedPromoCode {
|
|
1462
|
-
code
|
|
1463
|
-
discountType
|
|
1464
|
-
discountAmount
|
|
1465
|
-
description
|
|
1466
|
-
}
|
|
1467
|
-
appliedDiscounts {
|
|
1468
|
-
promotionId
|
|
1469
|
-
discountClass
|
|
1470
|
-
discountType
|
|
1471
|
-
discountAmount
|
|
1472
|
-
description
|
|
1473
|
-
isAutomatic
|
|
1474
|
-
}
|
|
1475
|
-
customerEmail
|
|
1476
|
-
customerPhone
|
|
1477
|
-
shippingAddress
|
|
1478
|
-
billingAddress
|
|
1479
|
-
paymentMethod
|
|
1480
|
-
shippingRateId
|
|
1481
|
-
notes
|
|
1482
|
-
checkoutStartedAt
|
|
1483
|
-
checkoutExpiresAt
|
|
1484
|
-
createdAt
|
|
1485
|
-
updatedAt
|
|
1486
|
-
`;
|
|
1487
|
-
const CHECKOUT_START_MUTATION = `
|
|
1488
|
-
mutation CheckoutStart {
|
|
1489
|
-
checkoutStart {
|
|
1490
|
-
cart {
|
|
1491
|
-
${CART_FRAGMENT}
|
|
1490
|
+
function read(nowMs = Date.now()) {
|
|
1491
|
+
let raw;
|
|
1492
|
+
try {
|
|
1493
|
+
raw = storage.get(queueKey);
|
|
1494
|
+
} catch {
|
|
1495
|
+
return [];
|
|
1492
1496
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
message
|
|
1496
|
-
code
|
|
1497
|
+
if (!raw) {
|
|
1498
|
+
return [];
|
|
1497
1499
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
checkoutUpdate(input: $input) {
|
|
1504
|
-
cart {
|
|
1505
|
-
${CART_FRAGMENT}
|
|
1500
|
+
let parsed;
|
|
1501
|
+
try {
|
|
1502
|
+
parsed = JSON.parse(raw);
|
|
1503
|
+
} catch {
|
|
1504
|
+
return [];
|
|
1506
1505
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
message
|
|
1510
|
-
code
|
|
1506
|
+
if (!Array.isArray(parsed)) {
|
|
1507
|
+
return [];
|
|
1511
1508
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
status
|
|
1522
|
-
customerEmail
|
|
1523
|
-
customerPhone
|
|
1524
|
-
shippingAddress
|
|
1525
|
-
billingAddress
|
|
1526
|
-
subtotal
|
|
1527
|
-
total
|
|
1528
|
-
note
|
|
1529
|
-
items {
|
|
1530
|
-
id
|
|
1531
|
-
productTitle
|
|
1532
|
-
variantTitle
|
|
1533
|
-
sku
|
|
1534
|
-
quantity
|
|
1535
|
-
unitPrice
|
|
1536
|
-
totalPrice
|
|
1509
|
+
const entries = parsed.filter(isAnalyticsRetryQueueEntry);
|
|
1510
|
+
const activeEntries = entries.filter((entry) => {
|
|
1511
|
+
const isExpired = nowMs - entry.queuedAt >= ANALYTICS_QUEUE_TTL_MS;
|
|
1512
|
+
if (isExpired) {
|
|
1513
|
+
emitDiagnostic({
|
|
1514
|
+
target: "queue",
|
|
1515
|
+
status: "evicted_expired",
|
|
1516
|
+
eventId: entry.event.eventId
|
|
1517
|
+
});
|
|
1537
1518
|
}
|
|
1538
|
-
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
recipientName
|
|
1543
|
-
referenceNumber
|
|
1544
|
-
ipsQrCodeBase64
|
|
1545
|
-
expiresAt
|
|
1546
|
-
}
|
|
1547
|
-
userErrors {
|
|
1548
|
-
field
|
|
1549
|
-
message
|
|
1550
|
-
code
|
|
1519
|
+
return !isExpired;
|
|
1520
|
+
});
|
|
1521
|
+
if (activeEntries.length !== entries.length) {
|
|
1522
|
+
persist(activeEntries);
|
|
1551
1523
|
}
|
|
1524
|
+
return activeEntries;
|
|
1552
1525
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1526
|
+
function enqueue(entry) {
|
|
1527
|
+
const entries = read(entry.queuedAt);
|
|
1528
|
+
const nextEntries = [...entries];
|
|
1529
|
+
while (nextEntries.length >= ANALYTICS_QUEUE_MAX_EVENTS) {
|
|
1530
|
+
const evicted = nextEntries.shift();
|
|
1531
|
+
if (evicted) {
|
|
1532
|
+
emitDiagnostic({
|
|
1533
|
+
target: "queue",
|
|
1534
|
+
status: "evicted_full",
|
|
1535
|
+
event: evicted.event
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1565
1538
|
}
|
|
1539
|
+
nextEntries.push(entry);
|
|
1540
|
+
persist(nextEntries);
|
|
1541
|
+
emitDiagnostic({
|
|
1542
|
+
target: "queue",
|
|
1543
|
+
status: "enqueued",
|
|
1544
|
+
event: entry.event
|
|
1545
|
+
});
|
|
1546
|
+
return nextEntries;
|
|
1547
|
+
}
|
|
1548
|
+
function dequeue(count, nowMs = Date.now()) {
|
|
1549
|
+
const entries = read(nowMs);
|
|
1550
|
+
const dequeueCount = Math.max(0, Math.floor(count));
|
|
1551
|
+
const removedEntries = entries.slice(0, dequeueCount);
|
|
1552
|
+
persist(entries.slice(removedEntries.length));
|
|
1553
|
+
emitDiagnostic({
|
|
1554
|
+
target: "queue",
|
|
1555
|
+
status: "flushed",
|
|
1556
|
+
count: removedEntries.length
|
|
1557
|
+
});
|
|
1558
|
+
return removedEntries;
|
|
1559
|
+
}
|
|
1560
|
+
function replace(entries) {
|
|
1561
|
+
persist(entries);
|
|
1566
1562
|
}
|
|
1567
|
-
}
|
|
1568
|
-
`;
|
|
1569
|
-
function mapCartData(data, endpoint) {
|
|
1570
1563
|
return {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
id: item.id,
|
|
1576
|
-
variantId: item.variantId,
|
|
1577
|
-
quantity: item.quantity,
|
|
1578
|
-
priceAtAdd: item.priceAtAdd,
|
|
1579
|
-
effectiveUnitPrice: item.effectiveUnitPrice,
|
|
1580
|
-
lineTotal: item.lineTotal,
|
|
1581
|
-
taxAmount: item.taxAmount,
|
|
1582
|
-
variant: item.variant ? {
|
|
1583
|
-
...item.variant,
|
|
1584
|
-
image: item.variant.image ? { ...item.variant.image, url: resolveAssetUrl(item.variant.image.url, endpoint) } : null
|
|
1585
|
-
} : null
|
|
1586
|
-
})),
|
|
1587
|
-
totalItems: data.totalItems,
|
|
1588
|
-
totalPrice: data.totalPrice,
|
|
1589
|
-
taxTotal: data.taxTotal,
|
|
1590
|
-
shippingTotal: data.shippingTotal,
|
|
1591
|
-
total: data.total,
|
|
1592
|
-
discountTotal: data.discountTotal,
|
|
1593
|
-
appliedPromoCode: data.appliedPromoCode,
|
|
1594
|
-
appliedDiscounts: data.appliedDiscounts,
|
|
1595
|
-
customerEmail: data.customerEmail,
|
|
1596
|
-
customerPhone: data.customerPhone,
|
|
1597
|
-
shippingAddress: data.shippingAddress,
|
|
1598
|
-
billingAddress: data.billingAddress,
|
|
1599
|
-
paymentMethod: data.paymentMethod,
|
|
1600
|
-
shippingRateId: data.shippingRateId,
|
|
1601
|
-
notes: data.notes,
|
|
1602
|
-
checkoutStartedAt: data.checkoutStartedAt ?? null,
|
|
1603
|
-
checkoutExpiresAt: data.checkoutExpiresAt ?? null,
|
|
1604
|
-
createdAt: data.createdAt,
|
|
1605
|
-
updatedAt: data.updatedAt
|
|
1564
|
+
read,
|
|
1565
|
+
enqueue,
|
|
1566
|
+
dequeue,
|
|
1567
|
+
replace
|
|
1606
1568
|
};
|
|
1607
1569
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1570
|
+
const TRACKING_POLICY_EVENT_TYPES = [
|
|
1571
|
+
"analytics.page_view",
|
|
1572
|
+
"analytics.product_view",
|
|
1573
|
+
"analytics.collection_view",
|
|
1574
|
+
"analytics.search_performed",
|
|
1575
|
+
"analytics.add_to_cart",
|
|
1576
|
+
"analytics.remove_from_cart",
|
|
1577
|
+
"analytics.checkout_started",
|
|
1578
|
+
"analytics.checkout_step_completed",
|
|
1579
|
+
"analytics.checkout_completed",
|
|
1580
|
+
"analytics.custom"
|
|
1581
|
+
];
|
|
1582
|
+
const GTM_EVENT_MAP = {
|
|
1583
|
+
"analytics.page_view": "page_view",
|
|
1584
|
+
"analytics.product_view": "view_item",
|
|
1585
|
+
"analytics.collection_view": "view_item_list",
|
|
1586
|
+
"analytics.search_performed": "search",
|
|
1587
|
+
"analytics.add_to_cart": "add_to_cart",
|
|
1588
|
+
"analytics.remove_from_cart": "remove_from_cart",
|
|
1589
|
+
"analytics.checkout_started": "begin_checkout",
|
|
1590
|
+
"analytics.checkout_step_completed": null,
|
|
1591
|
+
"analytics.checkout_completed": "purchase",
|
|
1592
|
+
"analytics.custom": null
|
|
1593
|
+
};
|
|
1594
|
+
const META_EVENT_MAP = {
|
|
1595
|
+
"analytics.page_view": "PageView",
|
|
1596
|
+
"analytics.product_view": "ViewContent",
|
|
1597
|
+
"analytics.collection_view": "ViewContent",
|
|
1598
|
+
"analytics.search_performed": "Search",
|
|
1599
|
+
"analytics.add_to_cart": "AddToCart",
|
|
1600
|
+
"analytics.remove_from_cart": null,
|
|
1601
|
+
"analytics.checkout_started": "InitiateCheckout",
|
|
1602
|
+
"analytics.checkout_step_completed": null,
|
|
1603
|
+
"analytics.checkout_completed": "Purchase",
|
|
1604
|
+
"analytics.custom": null
|
|
1605
|
+
};
|
|
1606
|
+
const TIKTOK_EVENT_MAP = {
|
|
1607
|
+
"analytics.page_view": "PageView",
|
|
1608
|
+
"analytics.product_view": "ViewContent",
|
|
1609
|
+
"analytics.collection_view": "ViewContent",
|
|
1610
|
+
"analytics.search_performed": "Search",
|
|
1611
|
+
"analytics.add_to_cart": "AddToCart",
|
|
1612
|
+
"analytics.remove_from_cart": null,
|
|
1613
|
+
"analytics.checkout_started": "InitiateCheckout",
|
|
1614
|
+
"analytics.checkout_step_completed": null,
|
|
1615
|
+
"analytics.checkout_completed": "CompletePayment",
|
|
1616
|
+
"analytics.custom": null
|
|
1617
|
+
};
|
|
1618
|
+
const TRACKING_PROVIDER_EVENT_MAPS = {
|
|
1619
|
+
gtm: GTM_EVENT_MAP,
|
|
1620
|
+
meta: META_EVENT_MAP,
|
|
1621
|
+
tiktok: TIKTOK_EVENT_MAP
|
|
1622
|
+
};
|
|
1623
|
+
function collectSupportedEventTypes(provider) {
|
|
1624
|
+
const eventMap = TRACKING_PROVIDER_EVENT_MAPS[provider];
|
|
1625
|
+
return TRACKING_POLICY_EVENT_TYPES.filter((eventType) => eventMap[eventType] !== null);
|
|
1631
1626
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1627
|
+
({
|
|
1628
|
+
gtm: {
|
|
1629
|
+
supportedEventTypes: collectSupportedEventTypes("gtm")
|
|
1630
|
+
},
|
|
1631
|
+
meta: {
|
|
1632
|
+
supportedEventTypes: collectSupportedEventTypes("meta")
|
|
1633
|
+
},
|
|
1634
|
+
tiktok: {
|
|
1635
|
+
supportedEventTypes: collectSupportedEventTypes("tiktok")
|
|
1641
1636
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1637
|
+
});
|
|
1638
|
+
const SHARED_DEDUPE_CONVENTION = {
|
|
1639
|
+
canonicalEventIdField: "eventId",
|
|
1640
|
+
providerEventIdField: "event_id",
|
|
1641
|
+
confirmedPurchaseSource: "order.id"
|
|
1642
|
+
};
|
|
1643
|
+
const TRACKING_PROVIDER_DEDUPE_FIELDS = {
|
|
1644
|
+
gtm: SHARED_DEDUPE_CONVENTION,
|
|
1645
|
+
meta: SHARED_DEDUPE_CONVENTION,
|
|
1646
|
+
tiktok: SHARED_DEDUPE_CONVENTION
|
|
1647
|
+
};
|
|
1648
|
+
function getTrackingProviderEventName(provider, eventType) {
|
|
1649
|
+
return TRACKING_PROVIDER_EVENT_MAPS[provider][eventType];
|
|
1646
1650
|
}
|
|
1647
|
-
function
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1651
|
+
function providerSupportsTrackingEvent(provider, eventType) {
|
|
1652
|
+
return TRACKING_PROVIDER_EVENT_MAPS[provider][eventType] !== null;
|
|
1653
|
+
}
|
|
1654
|
+
function getTrackingProviderDedupeConvention(provider) {
|
|
1655
|
+
return TRACKING_PROVIDER_DEDUPE_FIELDS[provider];
|
|
1656
|
+
}
|
|
1657
|
+
const TRACKING_SCHEMA_VERSION = 1;
|
|
1658
|
+
function shouldDispatchForTrackingConsent(input) {
|
|
1659
|
+
if (input.consentState === "granted") {
|
|
1660
|
+
return true;
|
|
1652
1661
|
}
|
|
1653
|
-
|
|
1662
|
+
if (input.consentState === "denied") {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
return input.dispatchOnUnknownConsent;
|
|
1654
1666
|
}
|
|
1655
|
-
function
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
const result = await client.mutate({
|
|
1719
|
-
query: CHECKOUT_CONVERT_MUTATION
|
|
1720
|
-
});
|
|
1721
|
-
if (result.isErr()) {
|
|
1722
|
-
return neverthrow.err(result.error);
|
|
1723
|
-
}
|
|
1724
|
-
const payload = result.value.checkoutConvert;
|
|
1725
|
-
const userError = handleUserErrors(payload.userErrors);
|
|
1726
|
-
if (userError) {
|
|
1727
|
-
return neverthrow.err(userError);
|
|
1728
|
-
}
|
|
1729
|
-
if (!payload.order) {
|
|
1730
|
-
return neverthrow.err(new errors.NotFoundError("Order not found"));
|
|
1731
|
-
}
|
|
1732
|
-
storage.remove(CART_TOKEN_KEY);
|
|
1733
|
-
return neverthrow.ok(mapOrderData(payload.order, payload.paymentInstructions));
|
|
1734
|
-
},
|
|
1735
|
-
async abandon() {
|
|
1736
|
-
const token = storage.get(CART_TOKEN_KEY);
|
|
1737
|
-
if (!token) {
|
|
1738
|
-
return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
|
|
1739
|
-
}
|
|
1740
|
-
const result = await client.mutate({
|
|
1741
|
-
query: CHECKOUT_ABANDON_MUTATION
|
|
1742
|
-
});
|
|
1743
|
-
if (result.isErr()) {
|
|
1744
|
-
return neverthrow.err(result.error);
|
|
1745
|
-
}
|
|
1746
|
-
const payload = result.value.checkoutAbandon;
|
|
1747
|
-
const userError = handleUserErrors(payload.userErrors);
|
|
1748
|
-
if (userError) {
|
|
1749
|
-
return neverthrow.err(userError);
|
|
1750
|
-
}
|
|
1751
|
-
if (!payload.cart) {
|
|
1752
|
-
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
1667
|
+
function shouldDispatchBrowserAdapter(provider, eventType, consent) {
|
|
1668
|
+
if (!providerSupportsTrackingEvent(provider, eventType)) {
|
|
1669
|
+
return { allowed: false, reason: "unsupported_event" };
|
|
1670
|
+
}
|
|
1671
|
+
if (!shouldDispatchForTrackingConsent(consent)) {
|
|
1672
|
+
return { allowed: false, reason: "consent_blocked" };
|
|
1673
|
+
}
|
|
1674
|
+
return { allowed: true };
|
|
1675
|
+
}
|
|
1676
|
+
const ANALYTICS_PATH = "/analytics/ingest";
|
|
1677
|
+
const VISITOR_COOKIE_NAME = "ekomerc_vid";
|
|
1678
|
+
const SESSION_STORAGE_KEY = "ekomerc_sid";
|
|
1679
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
1680
|
+
const KEEPALIVE_MAX_BODY_BYTES = 60 * 1024;
|
|
1681
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1682
|
+
const ACCEPTED_UNKNOWN_RESPONSE = {
|
|
1683
|
+
acceptedCount: 0,
|
|
1684
|
+
duplicateCount: 0,
|
|
1685
|
+
rejectedCount: 0,
|
|
1686
|
+
errors: []
|
|
1687
|
+
};
|
|
1688
|
+
const ANALYTICS_PRESET_EVENT_MAP = {
|
|
1689
|
+
page_view: "analytics.page_view",
|
|
1690
|
+
product_view: "analytics.product_view",
|
|
1691
|
+
collection_view: "analytics.collection_view",
|
|
1692
|
+
search_performed: "analytics.search_performed",
|
|
1693
|
+
add_to_cart: "analytics.add_to_cart",
|
|
1694
|
+
remove_from_cart: "analytics.remove_from_cart",
|
|
1695
|
+
checkout_started: "analytics.checkout_started",
|
|
1696
|
+
checkout_step_completed: "analytics.checkout_step_completed",
|
|
1697
|
+
checkout_completed: "analytics.checkout_completed"
|
|
1698
|
+
};
|
|
1699
|
+
function hasBrowserContext() {
|
|
1700
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
1701
|
+
}
|
|
1702
|
+
function getDocument() {
|
|
1703
|
+
return hasBrowserContext() ? document : null;
|
|
1704
|
+
}
|
|
1705
|
+
function getWindow() {
|
|
1706
|
+
return hasBrowserContext() ? window : null;
|
|
1707
|
+
}
|
|
1708
|
+
function isUuid(value) {
|
|
1709
|
+
return UUID_REGEX.test(value);
|
|
1710
|
+
}
|
|
1711
|
+
function randomUuid() {
|
|
1712
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1713
|
+
return crypto.randomUUID();
|
|
1714
|
+
}
|
|
1715
|
+
const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
|
1716
|
+
return template.replace(/[xy]/g, (character) => {
|
|
1717
|
+
const random = Math.floor(Math.random() * 16);
|
|
1718
|
+
const value = character === "x" ? random : random & 3 | 8;
|
|
1719
|
+
return value.toString(16);
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
function getOrCreateVisitorId() {
|
|
1723
|
+
const doc = getDocument();
|
|
1724
|
+
if (doc) {
|
|
1725
|
+
const raw = doc.cookie;
|
|
1726
|
+
for (const pair of raw.split(";")) {
|
|
1727
|
+
const [name, ...valueParts] = pair.trim().split("=");
|
|
1728
|
+
if (name === VISITOR_COOKIE_NAME) {
|
|
1729
|
+
return valueParts.join("=") ? decodeURIComponent(valueParts.join("=")) : randomUuid();
|
|
1753
1730
|
}
|
|
1754
|
-
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
1755
1731
|
}
|
|
1732
|
+
}
|
|
1733
|
+
const value = randomUuid();
|
|
1734
|
+
if (doc) {
|
|
1735
|
+
const maxAge = 60 * 60 * 24 * 365 * 2;
|
|
1736
|
+
doc.cookie = `${VISITOR_COOKIE_NAME}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; SameSite=Lax`;
|
|
1737
|
+
}
|
|
1738
|
+
return value;
|
|
1739
|
+
}
|
|
1740
|
+
let fallbackSessionState = null;
|
|
1741
|
+
function getSessionStorageState() {
|
|
1742
|
+
const win = getWindow();
|
|
1743
|
+
if (!win) return fallbackSessionState;
|
|
1744
|
+
try {
|
|
1745
|
+
const raw = win.localStorage.getItem(SESSION_STORAGE_KEY);
|
|
1746
|
+
if (!raw) return fallbackSessionState;
|
|
1747
|
+
const parsed = JSON.parse(raw);
|
|
1748
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1749
|
+
const id = parsed.id;
|
|
1750
|
+
const startedAt = parsed.startedAt;
|
|
1751
|
+
const lastSeenAt = parsed.lastSeenAt;
|
|
1752
|
+
if (typeof id !== "string" || !id) return null;
|
|
1753
|
+
if (typeof startedAt !== "number" || typeof lastSeenAt !== "number") return null;
|
|
1754
|
+
return { id, startedAt, lastSeenAt };
|
|
1755
|
+
} catch {
|
|
1756
|
+
return fallbackSessionState;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function setSessionStorageState(state) {
|
|
1760
|
+
fallbackSessionState = state;
|
|
1761
|
+
const win = getWindow();
|
|
1762
|
+
if (!win) return;
|
|
1763
|
+
try {
|
|
1764
|
+
win.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(state));
|
|
1765
|
+
} catch {
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function resolveSessionState(existing, nowMs) {
|
|
1769
|
+
if (!existing) {
|
|
1770
|
+
return { id: randomUuid(), startedAt: nowMs, lastSeenAt: nowMs };
|
|
1771
|
+
}
|
|
1772
|
+
if (nowMs - existing.lastSeenAt > SESSION_TIMEOUT_MS) {
|
|
1773
|
+
return { id: randomUuid(), startedAt: nowMs, lastSeenAt: nowMs };
|
|
1774
|
+
}
|
|
1775
|
+
return { id: existing.id, startedAt: existing.startedAt, lastSeenAt: nowMs };
|
|
1776
|
+
}
|
|
1777
|
+
function resolveSessionId(nowMs) {
|
|
1778
|
+
const existing = getSessionStorageState();
|
|
1779
|
+
const state = resolveSessionState(existing, nowMs);
|
|
1780
|
+
setSessionStorageState(state);
|
|
1781
|
+
return state.id;
|
|
1782
|
+
}
|
|
1783
|
+
function normalizeNumber(value) {
|
|
1784
|
+
return Number.isFinite(value) ? value : 0;
|
|
1785
|
+
}
|
|
1786
|
+
function toCents(amount) {
|
|
1787
|
+
return Math.max(0, Math.round(normalizeNumber(amount) * 100));
|
|
1788
|
+
}
|
|
1789
|
+
function base64UrlDecode(value) {
|
|
1790
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
1791
|
+
const padding = normalized.length % 4;
|
|
1792
|
+
const padded = padding === 0 ? normalized : normalized + "=".repeat(4 - padding);
|
|
1793
|
+
return atob(padded);
|
|
1794
|
+
}
|
|
1795
|
+
function parseJWT(payload) {
|
|
1796
|
+
try {
|
|
1797
|
+
const decoded = base64UrlDecode(payload);
|
|
1798
|
+
const parsed = JSON.parse(decoded);
|
|
1799
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
1800
|
+
} catch {
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function parseCustomerIdFromToken(token) {
|
|
1805
|
+
if (!token) return null;
|
|
1806
|
+
const payload = token.split(".");
|
|
1807
|
+
const encodedPayload = payload[1];
|
|
1808
|
+
if (!encodedPayload) return null;
|
|
1809
|
+
const parsed = parseJWT(encodedPayload);
|
|
1810
|
+
if (!parsed) return null;
|
|
1811
|
+
const customerId = parsed.customerId;
|
|
1812
|
+
return typeof customerId === "string" && isUuid(customerId) ? customerId : null;
|
|
1813
|
+
}
|
|
1814
|
+
function decodeAnalyticsEntityId(value) {
|
|
1815
|
+
if (!value) return null;
|
|
1816
|
+
if (isUuid(value)) return value;
|
|
1817
|
+
if (value.startsWith("gid://")) {
|
|
1818
|
+
const parts = value.split("/");
|
|
1819
|
+
const candidate = parts[parts.length - 1];
|
|
1820
|
+
return candidate && isUuid(candidate) ? candidate : null;
|
|
1821
|
+
}
|
|
1822
|
+
try {
|
|
1823
|
+
const decoded = atob(value);
|
|
1824
|
+
const parts = decoded.split(":");
|
|
1825
|
+
const candidate = parts[parts.length - 1];
|
|
1826
|
+
return candidate && isUuid(candidate) ? candidate : null;
|
|
1827
|
+
} catch {
|
|
1828
|
+
return null;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
function resolveDeviceInfo() {
|
|
1832
|
+
const win = getWindow();
|
|
1833
|
+
if (!win) {
|
|
1834
|
+
return { deviceType: "server", deviceOs: null, deviceBrowser: null };
|
|
1835
|
+
}
|
|
1836
|
+
const ua = win.navigator.userAgent.toLowerCase();
|
|
1837
|
+
const deviceType = /ipad|tablet|playbook|silk/.test(ua) ? "tablet" : /mobi|android|iphone|ipod|windows phone/.test(ua) ? "mobile" : "desktop";
|
|
1838
|
+
let deviceOs = null;
|
|
1839
|
+
if (ua.includes("windows")) {
|
|
1840
|
+
deviceOs = "Windows";
|
|
1841
|
+
} else if (ua.includes("mac os") || ua.includes("macintosh")) {
|
|
1842
|
+
deviceOs = "macOS";
|
|
1843
|
+
} else if (ua.includes("android")) {
|
|
1844
|
+
deviceOs = "Android";
|
|
1845
|
+
} else if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ios")) {
|
|
1846
|
+
deviceOs = "iOS";
|
|
1847
|
+
} else if (ua.includes("linux")) {
|
|
1848
|
+
deviceOs = "Linux";
|
|
1849
|
+
}
|
|
1850
|
+
let deviceBrowser = null;
|
|
1851
|
+
if (ua.includes("edg/")) {
|
|
1852
|
+
deviceBrowser = "Edge";
|
|
1853
|
+
} else if (ua.includes("firefox/")) {
|
|
1854
|
+
deviceBrowser = "Firefox";
|
|
1855
|
+
} else if (ua.includes("chrome/") && !ua.includes("edg/")) {
|
|
1856
|
+
deviceBrowser = "Chrome";
|
|
1857
|
+
} else if (ua.includes("safari/") && !ua.includes("chrome/")) {
|
|
1858
|
+
deviceBrowser = "Safari";
|
|
1859
|
+
}
|
|
1860
|
+
return { deviceType, deviceOs, deviceBrowser };
|
|
1861
|
+
}
|
|
1862
|
+
function normalizePath(pathname) {
|
|
1863
|
+
return pathname.trim().length > 0 ? pathname : "/";
|
|
1864
|
+
}
|
|
1865
|
+
function toIsoTimestamp(value) {
|
|
1866
|
+
if (value == null) return null;
|
|
1867
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1868
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
1869
|
+
}
|
|
1870
|
+
function buildUtmPayload(context, trackingAttributionTtlMs) {
|
|
1871
|
+
const persisted = getTrackingAttributionSnapshot({ ttlMs: trackingAttributionTtlMs });
|
|
1872
|
+
return {
|
|
1873
|
+
schemaVersion: TRACKING_SCHEMA_VERSION,
|
|
1874
|
+
source: context.utmSource ?? persisted?.utm.source ?? null,
|
|
1875
|
+
medium: context.utmMedium ?? persisted?.utm.medium ?? null,
|
|
1876
|
+
campaign: context.utmCampaign ?? persisted?.utm.campaign ?? null,
|
|
1877
|
+
term: context.utmTerm ?? persisted?.utm.term ?? null,
|
|
1878
|
+
content: context.utmContent ?? persisted?.utm.content ?? null
|
|
1756
1879
|
};
|
|
1757
1880
|
}
|
|
1758
|
-
function
|
|
1881
|
+
function buildClickIdsPayload(context, trackingAttributionTtlMs) {
|
|
1882
|
+
const persisted = getTrackingAttributionSnapshot({ ttlMs: trackingAttributionTtlMs });
|
|
1883
|
+
const clickIds = context.clickIds;
|
|
1759
1884
|
return {
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
metaDescription: raw.metaDescription,
|
|
1768
|
-
productCount: raw.productCount,
|
|
1769
|
-
imageAsset: raw.imageAsset ? {
|
|
1770
|
-
url: resolveAssetUrl(raw.imageAsset.url, endpoint),
|
|
1771
|
-
altText: raw.imageAsset.altText,
|
|
1772
|
-
width: raw.imageAsset.width,
|
|
1773
|
-
height: raw.imageAsset.height
|
|
1774
|
-
} : null
|
|
1885
|
+
schemaVersion: TRACKING_SCHEMA_VERSION,
|
|
1886
|
+
capturedAt: toIsoTimestamp(clickIds?.capturedAt) ?? persisted?.clickIds.capturedAt ?? null,
|
|
1887
|
+
gclid: clickIds?.gclid ?? persisted?.clickIds.gclid ?? null,
|
|
1888
|
+
gbraid: clickIds?.gbraid ?? persisted?.clickIds.gbraid ?? null,
|
|
1889
|
+
wbraid: clickIds?.wbraid ?? persisted?.clickIds.wbraid ?? null,
|
|
1890
|
+
ttclid: clickIds?.ttclid ?? persisted?.clickIds.ttclid ?? null,
|
|
1891
|
+
fbclid: clickIds?.fbclid ?? persisted?.clickIds.fbclid ?? null
|
|
1775
1892
|
};
|
|
1776
1893
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1894
|
+
function buildDefaultContext() {
|
|
1895
|
+
const context = {};
|
|
1896
|
+
const device = resolveDeviceInfo();
|
|
1897
|
+
const win = getWindow();
|
|
1898
|
+
if (win) {
|
|
1899
|
+
const { pathname, search } = win.location;
|
|
1900
|
+
context.path = pathname + search;
|
|
1901
|
+
context.referrer = win.document.referrer.length > 0 ? win.document.referrer : null;
|
|
1902
|
+
const params = new URLSearchParams(win.location.search);
|
|
1903
|
+
context.utmSource = params.get("utm_source");
|
|
1904
|
+
context.utmMedium = params.get("utm_medium");
|
|
1905
|
+
context.utmCampaign = params.get("utm_campaign");
|
|
1906
|
+
context.utmTerm = params.get("utm_term");
|
|
1907
|
+
context.utmContent = params.get("utm_content");
|
|
1908
|
+
context.deviceType = device.deviceType;
|
|
1909
|
+
context.deviceOs = device.deviceOs;
|
|
1910
|
+
context.deviceBrowser = device.deviceBrowser;
|
|
1911
|
+
}
|
|
1912
|
+
if (!context.deviceType) {
|
|
1913
|
+
context.deviceType = "unknown";
|
|
1914
|
+
}
|
|
1915
|
+
context.deviceOs ??= null;
|
|
1916
|
+
context.deviceBrowser ??= null;
|
|
1917
|
+
return context;
|
|
1918
|
+
}
|
|
1919
|
+
function normalizeEventContext(context) {
|
|
1920
|
+
const defaults = buildDefaultContext();
|
|
1921
|
+
return { ...defaults, ...context };
|
|
1922
|
+
}
|
|
1923
|
+
function isIngestResponse(value) {
|
|
1924
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1925
|
+
const response = value;
|
|
1926
|
+
return typeof response.acceptedCount === "number" && typeof response.duplicateCount === "number" && typeof response.rejectedCount === "number" && Array.isArray(response.errors);
|
|
1927
|
+
}
|
|
1928
|
+
function extractErrorMessage(response) {
|
|
1929
|
+
return response.json().then((payload) => {
|
|
1930
|
+
if (typeof payload.error === "string" && payload.error.length > 0) {
|
|
1931
|
+
return payload.error;
|
|
1932
|
+
}
|
|
1933
|
+
const firstMessage = Array.isArray(payload.details) ? payload.details.map((error) => typeof error?.message === "string" ? error.message : null).find((message) => message !== null) : null;
|
|
1934
|
+
if (firstMessage) {
|
|
1935
|
+
return firstMessage;
|
|
1936
|
+
}
|
|
1937
|
+
return `Analytics ingest failed with status ${response.status}`;
|
|
1938
|
+
}).catch(() => `Analytics ingest failed with status ${response.status}`);
|
|
1939
|
+
}
|
|
1940
|
+
function parseRetryAfterHeader(response, nowMs) {
|
|
1941
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1942
|
+
if (!retryAfter) {
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
const trimmed = retryAfter.trim();
|
|
1946
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
1950
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
1951
|
+
return null;
|
|
1952
|
+
}
|
|
1953
|
+
return nowMs + seconds * 1e3;
|
|
1954
|
+
}
|
|
1955
|
+
function toError(error, message) {
|
|
1956
|
+
if (error instanceof Error) {
|
|
1957
|
+
return error;
|
|
1958
|
+
}
|
|
1959
|
+
return new Error(message);
|
|
1960
|
+
}
|
|
1961
|
+
function isPlainObject(value) {
|
|
1962
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1963
|
+
}
|
|
1964
|
+
async function flushAnalyticsRetryQueue(retryQueue, sendEventOutcome, emitDiagnostic, nowMs = Date.now()) {
|
|
1965
|
+
const queuedEntries = retryQueue.read(nowMs);
|
|
1966
|
+
const nextEntries = [];
|
|
1967
|
+
let sentCount = 0;
|
|
1968
|
+
for (const entry of queuedEntries) {
|
|
1969
|
+
if (entry.notBefore !== void 0 && entry.notBefore > nowMs) {
|
|
1970
|
+
nextEntries.push(entry);
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
const outcome = await sendEventOutcome(entry.event);
|
|
1974
|
+
if (outcome.result.isOk()) {
|
|
1975
|
+
sentCount += 1;
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
if (!outcome.retryDisposition.retryable) {
|
|
1979
|
+
continue;
|
|
1980
|
+
}
|
|
1981
|
+
nextEntries.push({
|
|
1982
|
+
event: entry.event,
|
|
1983
|
+
queuedAt: entry.queuedAt,
|
|
1984
|
+
...outcome.retryDisposition.notBefore !== void 0 ? { notBefore: outcome.retryDisposition.notBefore } : {}
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
retryQueue.replace(nextEntries);
|
|
1988
|
+
emitDiagnostic({
|
|
1989
|
+
target: "queue",
|
|
1990
|
+
status: "flushed",
|
|
1991
|
+
count: sentCount
|
|
1992
|
+
});
|
|
1993
|
+
return sentCount;
|
|
1994
|
+
}
|
|
1995
|
+
function resolveTrackEvent(eventName) {
|
|
1996
|
+
const normalized = eventName.startsWith("analytics.") ? eventName.slice("analytics.".length) : eventName;
|
|
1997
|
+
if (normalized in ANALYTICS_PRESET_EVENT_MAP) {
|
|
1998
|
+
return { eventType: ANALYTICS_PRESET_EVENT_MAP[normalized] };
|
|
1999
|
+
}
|
|
2000
|
+
return {
|
|
2001
|
+
eventType: "analytics.custom",
|
|
2002
|
+
customEventName: eventName
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
function dispatchEventToBrowserRuntime(runtime, event, resolvedAnalytics, emitDiagnostic, scheduleBestEffort) {
|
|
2006
|
+
const adapters = (runtime?.adapters ?? []).filter(
|
|
2007
|
+
(adapter) => adapter.provider !== "gtm" || resolvedAnalytics.gtm.enabled
|
|
2008
|
+
);
|
|
2009
|
+
if (adapters.length === 0) {
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
const consent = {
|
|
2013
|
+
consentState: event.consentState,
|
|
2014
|
+
dispatchOnUnknownConsent: runtime?.dispatchOnUnknownConsent ?? true
|
|
2015
|
+
};
|
|
2016
|
+
scheduleBestEffort(async () => {
|
|
2017
|
+
for (const adapter of adapters) {
|
|
2018
|
+
if (adapter.updateConsent) {
|
|
2019
|
+
try {
|
|
2020
|
+
const result = await adapter.updateConsent(consent);
|
|
2021
|
+
if (result?.status === "skipped") {
|
|
2022
|
+
emitDiagnostic({
|
|
2023
|
+
target: "consent_bridge",
|
|
2024
|
+
status: "skipped",
|
|
2025
|
+
provider: adapter.provider,
|
|
2026
|
+
consent,
|
|
2027
|
+
reason: result.reason
|
|
2028
|
+
});
|
|
2029
|
+
emitDiagnostic({
|
|
2030
|
+
target: "adapter",
|
|
2031
|
+
status: "skipped",
|
|
2032
|
+
provider: adapter.provider,
|
|
2033
|
+
event,
|
|
2034
|
+
reason: result.reason
|
|
2035
|
+
});
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
emitDiagnostic({
|
|
2039
|
+
target: "consent_bridge",
|
|
2040
|
+
status: "success",
|
|
2041
|
+
provider: adapter.provider,
|
|
2042
|
+
consent
|
|
2043
|
+
});
|
|
2044
|
+
} catch (error) {
|
|
2045
|
+
emitDiagnostic({
|
|
2046
|
+
target: "consent_bridge",
|
|
2047
|
+
status: "error",
|
|
2048
|
+
provider: adapter.provider,
|
|
2049
|
+
consent,
|
|
2050
|
+
error: toError(error, `Failed to update ${adapter.provider} consent bridge`)
|
|
2051
|
+
});
|
|
2052
|
+
emitDiagnostic({
|
|
2053
|
+
target: "adapter",
|
|
2054
|
+
status: "error",
|
|
2055
|
+
provider: adapter.provider,
|
|
2056
|
+
event,
|
|
2057
|
+
error: toError(error, `Skipped ${adapter.provider} browser tracking event due to consent bridge failure`)
|
|
2058
|
+
});
|
|
2059
|
+
continue;
|
|
1796
2060
|
}
|
|
1797
2061
|
}
|
|
1798
|
-
|
|
2062
|
+
const dispatchDecision = shouldDispatchBrowserAdapter(adapter.provider, event.eventType, consent);
|
|
2063
|
+
if (!dispatchDecision.allowed) {
|
|
2064
|
+
emitDiagnostic({
|
|
2065
|
+
target: "adapter",
|
|
2066
|
+
status: "skipped",
|
|
2067
|
+
provider: adapter.provider,
|
|
2068
|
+
event,
|
|
2069
|
+
reason: dispatchDecision.reason
|
|
2070
|
+
});
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
try {
|
|
2074
|
+
const result = await adapter.dispatch({ event, consent });
|
|
2075
|
+
if (result?.status === "skipped") {
|
|
2076
|
+
emitDiagnostic({
|
|
2077
|
+
target: "adapter",
|
|
2078
|
+
status: "skipped",
|
|
2079
|
+
provider: adapter.provider,
|
|
2080
|
+
event,
|
|
2081
|
+
reason: result.reason
|
|
2082
|
+
});
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
emitDiagnostic({
|
|
2086
|
+
target: "adapter",
|
|
2087
|
+
status: "success",
|
|
2088
|
+
provider: adapter.provider,
|
|
2089
|
+
event
|
|
2090
|
+
});
|
|
2091
|
+
} catch (error) {
|
|
2092
|
+
emitDiagnostic({
|
|
2093
|
+
target: "adapter",
|
|
2094
|
+
status: "error",
|
|
2095
|
+
provider: adapter.provider,
|
|
2096
|
+
event,
|
|
2097
|
+
error: toError(error, `Failed to dispatch ${adapter.provider} browser tracking event`)
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
function shouldMirrorEventToBrowserRuntime(eventType) {
|
|
2104
|
+
return eventType !== "analytics.checkout_completed";
|
|
2105
|
+
}
|
|
2106
|
+
function isResolvedAnalyticsConfig(input) {
|
|
2107
|
+
return Boolean(
|
|
2108
|
+
input && typeof input === "object" && "enabled" in input && "dispatchOnUnknownConsent" in input && "gtm" in input
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
function buildDefaultResolvedAnalyticsConfig(runtime) {
|
|
2112
|
+
const hasGtmAdapter = runtime?.adapters?.some((adapter) => adapter.provider === "gtm") ?? false;
|
|
2113
|
+
return {
|
|
2114
|
+
enabled: true,
|
|
2115
|
+
dispatchOnUnknownConsent: runtime?.dispatchOnUnknownConsent ?? true,
|
|
2116
|
+
gtm: {
|
|
2117
|
+
enabled: hasGtmAdapter,
|
|
2118
|
+
containerId: null
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
function createAnalyticsOperations(client, storage) {
|
|
2123
|
+
const trackingAttributionTtlMs = client.config.trackingAttributionTTL;
|
|
2124
|
+
const runtimeClient = client;
|
|
2125
|
+
let lifecycleFlushListenersRegistered = false;
|
|
2126
|
+
const endpoint = (() => {
|
|
2127
|
+
try {
|
|
2128
|
+
const parsedEndpoint = new URL(client.config.endpoint);
|
|
2129
|
+
return `${parsedEndpoint.origin}${ANALYTICS_PATH}`;
|
|
2130
|
+
} catch {
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
})();
|
|
2134
|
+
const apiKey = client.config.apiKey;
|
|
2135
|
+
function getTrackingRuntime() {
|
|
2136
|
+
return runtimeClient._analyticsRuntimeConfig ?? client.config.tracking ?? null;
|
|
2137
|
+
}
|
|
2138
|
+
function getResolvedAnalyticsConfig() {
|
|
2139
|
+
return runtimeClient._resolvedInitConfig?.analytics ?? null;
|
|
2140
|
+
}
|
|
2141
|
+
function emitDiagnostic(diagnostic) {
|
|
2142
|
+
try {
|
|
2143
|
+
getTrackingRuntime()?.onDiagnostic?.(diagnostic);
|
|
2144
|
+
} catch {
|
|
1799
2145
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
2146
|
+
}
|
|
2147
|
+
const retryQueue = createAnalyticsRetryQueue(storage, apiKey, emitDiagnostic);
|
|
2148
|
+
async function resolveConsentState(explicitConsentState) {
|
|
2149
|
+
if (explicitConsentState) {
|
|
2150
|
+
return explicitConsentState;
|
|
2151
|
+
}
|
|
2152
|
+
const trackingRuntime = getTrackingRuntime();
|
|
2153
|
+
if (!trackingRuntime?.resolveConsentState) {
|
|
2154
|
+
return "unknown";
|
|
2155
|
+
}
|
|
2156
|
+
try {
|
|
2157
|
+
return await trackingRuntime.resolveConsentState();
|
|
2158
|
+
} catch (error) {
|
|
2159
|
+
emitDiagnostic({
|
|
2160
|
+
target: "consent",
|
|
2161
|
+
status: "error",
|
|
2162
|
+
error: toError(error, "Failed to resolve tracking consent state")
|
|
2163
|
+
});
|
|
2164
|
+
return "unknown";
|
|
1805
2165
|
}
|
|
1806
2166
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
metaDescription
|
|
1820
|
-
productCount
|
|
1821
|
-
imageAsset {
|
|
1822
|
-
url
|
|
1823
|
-
altText
|
|
1824
|
-
width
|
|
1825
|
-
height
|
|
2167
|
+
function scheduleBestEffort(task) {
|
|
2168
|
+
Promise.resolve().then(task).catch(() => {
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
function flushRetryQueueInBackground() {
|
|
2172
|
+
scheduleBestEffort(async () => {
|
|
2173
|
+
await flushAnalyticsRetryQueue(retryQueue, sendEventOutcome, emitDiagnostic);
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
function registerLifecycleFlushListeners() {
|
|
2177
|
+
if (!hasBrowserContext() || lifecycleFlushListenersRegistered) {
|
|
2178
|
+
return;
|
|
1826
2179
|
}
|
|
2180
|
+
const win = getWindow();
|
|
2181
|
+
const doc = getDocument();
|
|
2182
|
+
if (!win || !doc) {
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
win.addEventListener("online", () => {
|
|
2186
|
+
flushRetryQueueInBackground();
|
|
2187
|
+
});
|
|
2188
|
+
doc.addEventListener("visibilitychange", () => {
|
|
2189
|
+
if (doc.visibilityState === "hidden") {
|
|
2190
|
+
flushRetryQueueInBackground();
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
lifecycleFlushListenersRegistered = true;
|
|
1827
2194
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
url
|
|
1844
|
-
altText
|
|
1845
|
-
width
|
|
1846
|
-
height
|
|
2195
|
+
function init(input) {
|
|
2196
|
+
if (isResolvedAnalyticsConfig(input)) {
|
|
2197
|
+
runtimeClient._resolvedInitConfig = { analytics: input };
|
|
2198
|
+
} else {
|
|
2199
|
+
runtimeClient._analyticsRuntimeConfig = {
|
|
2200
|
+
...getTrackingRuntime() ?? {},
|
|
2201
|
+
...input ?? {}
|
|
2202
|
+
};
|
|
2203
|
+
runtimeClient._resolvedInitConfig ??= {
|
|
2204
|
+
analytics: buildDefaultResolvedAnalyticsConfig(getTrackingRuntime())
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
captureLandingTrackingAttribution({ ttlMs: trackingAttributionTtlMs });
|
|
2208
|
+
if (getTrackingRuntime()?.stripUrl !== false) {
|
|
2209
|
+
stripLandingTrackingAttributionFromUrl();
|
|
1847
2210
|
}
|
|
2211
|
+
registerLifecycleFlushListeners();
|
|
2212
|
+
flushRetryQueueInBackground();
|
|
1848
2213
|
}
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
2214
|
+
function buildCanonicalEvent(eventType, properties, eventContext, consentState) {
|
|
2215
|
+
const now = new Date(eventContext.occurredAt ?? Date.now());
|
|
2216
|
+
const nowIso = Number.isNaN(now.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : now.toISOString();
|
|
2217
|
+
const customerId = eventContext.customerId === void 0 ? parseCustomerIdFromToken(client.getCustomerToken()) : eventContext.customerId;
|
|
2218
|
+
return {
|
|
2219
|
+
schemaVersion: TRACKING_SCHEMA_VERSION,
|
|
2220
|
+
eventId: randomUuid(),
|
|
2221
|
+
eventType,
|
|
2222
|
+
occurredAt: nowIso,
|
|
2223
|
+
sessionId: eventContext.sessionId ?? resolveSessionId(now.getTime()),
|
|
2224
|
+
visitorId: eventContext.visitorId ?? getOrCreateVisitorId(),
|
|
2225
|
+
customerId: customerId ?? null,
|
|
2226
|
+
consentState,
|
|
2227
|
+
context: {
|
|
2228
|
+
schemaVersion: TRACKING_SCHEMA_VERSION,
|
|
2229
|
+
path: normalizePath(eventContext.path ?? "/")
|
|
2230
|
+
},
|
|
2231
|
+
referrer: eventContext.referrer ?? null,
|
|
2232
|
+
utm: buildUtmPayload(eventContext, trackingAttributionTtlMs),
|
|
2233
|
+
clickIds: buildClickIdsPayload(eventContext, trackingAttributionTtlMs),
|
|
2234
|
+
device: {
|
|
2235
|
+
deviceType: eventContext.deviceType ?? "unknown",
|
|
2236
|
+
deviceOs: eventContext.deviceOs ?? null,
|
|
2237
|
+
deviceBrowser: eventContext.deviceBrowser ?? null
|
|
2238
|
+
},
|
|
2239
|
+
properties
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
async function sendEventOutcome(event) {
|
|
2243
|
+
if (!endpoint) {
|
|
2244
|
+
return {
|
|
2245
|
+
result: neverthrow.err(new errors.NetworkError("Invalid storefront endpoint")),
|
|
2246
|
+
retryDisposition: { retryable: true }
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
const payload = {
|
|
2250
|
+
events: [event]
|
|
2251
|
+
};
|
|
2252
|
+
const body = JSON.stringify(payload);
|
|
2253
|
+
const keepalive = new Blob([body]).size <= KEEPALIVE_MAX_BODY_BYTES;
|
|
2254
|
+
try {
|
|
2255
|
+
const response = await fetch(endpoint, {
|
|
2256
|
+
method: "POST",
|
|
2257
|
+
headers: {
|
|
2258
|
+
"content-type": "application/json",
|
|
2259
|
+
"x-storefront-key": apiKey
|
|
2260
|
+
},
|
|
2261
|
+
body,
|
|
2262
|
+
...keepalive ? { keepalive: true } : {}
|
|
2263
|
+
});
|
|
2264
|
+
if (!response.ok) {
|
|
2265
|
+
const message = response.headers.get("content-type")?.includes("application/json") ? await extractErrorMessage(response) : `Analytics ingest failed with status ${response.status}`;
|
|
2266
|
+
const networkError = new errors.NetworkError(message);
|
|
2267
|
+
const nowMs = Date.now();
|
|
2268
|
+
const retryDisposition = response.status === 429 ? (() => {
|
|
2269
|
+
const notBefore = parseRetryAfterHeader(response, nowMs);
|
|
2270
|
+
return notBefore ? { retryable: true, notBefore } : { retryable: false };
|
|
2271
|
+
})() : response.status >= 500 ? { retryable: true } : { retryable: false };
|
|
2272
|
+
emitDiagnostic({
|
|
2273
|
+
target: "ingest",
|
|
2274
|
+
status: "error",
|
|
2275
|
+
event,
|
|
2276
|
+
error: networkError
|
|
2277
|
+
});
|
|
2278
|
+
return {
|
|
2279
|
+
result: neverthrow.err(networkError),
|
|
2280
|
+
retryDisposition
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
let parsed;
|
|
2284
|
+
try {
|
|
2285
|
+
parsed = await response.json();
|
|
2286
|
+
} catch {
|
|
2287
|
+
emitDiagnostic({
|
|
2288
|
+
target: "ingest",
|
|
2289
|
+
status: "accepted_unknown",
|
|
2290
|
+
event
|
|
2291
|
+
});
|
|
2292
|
+
return {
|
|
2293
|
+
result: neverthrow.ok(ACCEPTED_UNKNOWN_RESPONSE),
|
|
2294
|
+
retryDisposition: { retryable: false }
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
if (!isIngestResponse(parsed)) {
|
|
2298
|
+
const networkError = new errors.NetworkError("Analytics response shape is invalid");
|
|
2299
|
+
emitDiagnostic({
|
|
2300
|
+
target: "ingest",
|
|
2301
|
+
status: "error",
|
|
2302
|
+
event,
|
|
2303
|
+
error: networkError
|
|
2304
|
+
});
|
|
2305
|
+
return {
|
|
2306
|
+
result: neverthrow.err(networkError),
|
|
2307
|
+
retryDisposition: { retryable: false }
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
emitDiagnostic({
|
|
2311
|
+
target: "ingest",
|
|
2312
|
+
status: "success",
|
|
2313
|
+
event,
|
|
2314
|
+
response: {
|
|
2315
|
+
acceptedCount: parsed.acceptedCount,
|
|
2316
|
+
duplicateCount: parsed.duplicateCount,
|
|
2317
|
+
rejectedCount: parsed.rejectedCount
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
return {
|
|
2321
|
+
result: neverthrow.ok(parsed),
|
|
2322
|
+
retryDisposition: { retryable: false }
|
|
2323
|
+
};
|
|
2324
|
+
} catch (error) {
|
|
2325
|
+
const networkError = new errors.NetworkError("Failed to send analytics event", {
|
|
2326
|
+
cause: error instanceof Error ? error : void 0
|
|
2327
|
+
});
|
|
2328
|
+
emitDiagnostic({
|
|
2329
|
+
target: "ingest",
|
|
2330
|
+
status: "error",
|
|
2331
|
+
event,
|
|
2332
|
+
error: networkError
|
|
2333
|
+
});
|
|
2334
|
+
return {
|
|
2335
|
+
result: neverthrow.err(networkError),
|
|
2336
|
+
retryDisposition: { retryable: true }
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
async function track(eventName, eventPayload, context) {
|
|
2341
|
+
let eventContext = context;
|
|
2342
|
+
const normalized = resolveTrackEvent(eventName);
|
|
2343
|
+
let eventType;
|
|
2344
|
+
let properties;
|
|
2345
|
+
if (normalized.eventType !== "analytics.custom") {
|
|
2346
|
+
switch (normalized.eventType) {
|
|
2347
|
+
case "analytics.page_view": {
|
|
2348
|
+
eventType = "analytics.page_view";
|
|
2349
|
+
properties = {};
|
|
2350
|
+
eventContext = eventContext ?? (isPlainObject(eventPayload) ? eventPayload : void 0);
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2353
|
+
case "analytics.product_view": {
|
|
2354
|
+
if (!isPlainObject(eventPayload)) {
|
|
2355
|
+
return neverthrow.err(new errors.NetworkError("productId is required"));
|
|
1874
2356
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
values {
|
|
1880
|
-
id
|
|
1881
|
-
value
|
|
1882
|
-
position
|
|
1883
|
-
}
|
|
2357
|
+
const payload = eventPayload;
|
|
2358
|
+
const decodedProductId = decodeAnalyticsEntityId(payload.productId);
|
|
2359
|
+
if (!decodedProductId) {
|
|
2360
|
+
return neverthrow.err(new errors.NetworkError("Invalid productId"));
|
|
1884
2361
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
requiresShipping
|
|
1894
|
-
availableForSale
|
|
1895
|
-
quantity
|
|
1896
|
-
selectedOptions {
|
|
1897
|
-
id
|
|
1898
|
-
value
|
|
1899
|
-
position
|
|
1900
|
-
}
|
|
1901
|
-
image {
|
|
1902
|
-
id
|
|
1903
|
-
url
|
|
1904
|
-
altText
|
|
1905
|
-
position
|
|
1906
|
-
}
|
|
2362
|
+
const decodedVariantId = decodeAnalyticsEntityId(payload.variantId);
|
|
2363
|
+
eventType = "analytics.product_view";
|
|
2364
|
+
properties = { productId: decodedProductId, variantId: decodedVariantId };
|
|
2365
|
+
break;
|
|
2366
|
+
}
|
|
2367
|
+
case "analytics.collection_view": {
|
|
2368
|
+
if (!isPlainObject(eventPayload)) {
|
|
2369
|
+
return neverthrow.err(new errors.NetworkError("collectionId is required"));
|
|
1907
2370
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
description
|
|
2371
|
+
const payload = eventPayload;
|
|
2372
|
+
const decodedCollectionId = decodeAnalyticsEntityId(payload.collectionId);
|
|
2373
|
+
if (!decodedCollectionId) {
|
|
2374
|
+
return neverthrow.err(new errors.NetworkError("Invalid collectionId"));
|
|
1913
2375
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2376
|
+
eventType = "analytics.collection_view";
|
|
2377
|
+
properties = { collectionId: decodedCollectionId };
|
|
2378
|
+
break;
|
|
2379
|
+
}
|
|
2380
|
+
case "analytics.search_performed": {
|
|
2381
|
+
if (!isPlainObject(eventPayload)) {
|
|
2382
|
+
return neverthrow.err(new errors.NetworkError("query is required"));
|
|
1918
2383
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2384
|
+
const payload = eventPayload;
|
|
2385
|
+
const trimmed = payload.query.trim();
|
|
2386
|
+
if (!trimmed) {
|
|
2387
|
+
return neverthrow.err(new errors.NetworkError("query is required"));
|
|
1922
2388
|
}
|
|
2389
|
+
eventType = "analytics.search_performed";
|
|
2390
|
+
properties = { query: trimmed, resultsCount: Math.max(0, Math.floor(payload.resultsCount)) };
|
|
2391
|
+
break;
|
|
1923
2392
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2393
|
+
case "analytics.add_to_cart": {
|
|
2394
|
+
if (!isPlainObject(eventPayload)) {
|
|
2395
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
2396
|
+
}
|
|
2397
|
+
const payload = eventPayload;
|
|
2398
|
+
const cartId = decodeAnalyticsEntityId(payload.cartId);
|
|
2399
|
+
if (!cartId) {
|
|
2400
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
2401
|
+
}
|
|
2402
|
+
eventType = "analytics.add_to_cart";
|
|
2403
|
+
properties = {
|
|
2404
|
+
cartId,
|
|
2405
|
+
quantity: Math.max(1, Math.floor(payload.quantity)),
|
|
2406
|
+
itemsCount: Math.max(0, Math.floor(payload.itemsCount)),
|
|
2407
|
+
cartValueCents: toCents(payload.cartValue)
|
|
2408
|
+
};
|
|
2409
|
+
break;
|
|
2410
|
+
}
|
|
2411
|
+
case "analytics.remove_from_cart": {
|
|
2412
|
+
if (!isPlainObject(eventPayload)) {
|
|
2413
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
2414
|
+
}
|
|
2415
|
+
const payload = eventPayload;
|
|
2416
|
+
const cartId = decodeAnalyticsEntityId(payload.cartId);
|
|
2417
|
+
if (!cartId) {
|
|
2418
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
2419
|
+
}
|
|
2420
|
+
eventType = "analytics.remove_from_cart";
|
|
2421
|
+
properties = {
|
|
2422
|
+
cartId,
|
|
2423
|
+
quantity: Math.max(1, Math.floor(payload.quantity)),
|
|
2424
|
+
itemsCount: Math.max(0, Math.floor(payload.itemsCount)),
|
|
2425
|
+
cartValueCents: toCents(payload.cartValue)
|
|
2426
|
+
};
|
|
2427
|
+
break;
|
|
2428
|
+
}
|
|
2429
|
+
case "analytics.checkout_started": {
|
|
2430
|
+
if (!isPlainObject(eventPayload)) {
|
|
2431
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
1959
2432
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
values {
|
|
1965
|
-
id
|
|
1966
|
-
value
|
|
1967
|
-
position
|
|
1968
|
-
}
|
|
2433
|
+
const payload = eventPayload;
|
|
2434
|
+
const decodedCartId = decodeAnalyticsEntityId(payload.cartId);
|
|
2435
|
+
if (!decodedCartId) {
|
|
2436
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
1969
2437
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
weightUnit
|
|
1978
|
-
requiresShipping
|
|
1979
|
-
availableForSale
|
|
1980
|
-
quantity
|
|
1981
|
-
selectedOptions {
|
|
1982
|
-
id
|
|
1983
|
-
value
|
|
1984
|
-
position
|
|
1985
|
-
}
|
|
1986
|
-
image {
|
|
1987
|
-
id
|
|
1988
|
-
url
|
|
1989
|
-
altText
|
|
1990
|
-
position
|
|
1991
|
-
}
|
|
2438
|
+
eventType = "analytics.checkout_started";
|
|
2439
|
+
properties = { cartId: decodedCartId };
|
|
2440
|
+
break;
|
|
2441
|
+
}
|
|
2442
|
+
case "analytics.checkout_step_completed": {
|
|
2443
|
+
if (!isPlainObject(eventPayload)) {
|
|
2444
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
1992
2445
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
description
|
|
2446
|
+
const payload = eventPayload;
|
|
2447
|
+
const decodedCartId = decodeAnalyticsEntityId(payload.cartId);
|
|
2448
|
+
if (!decodedCartId) {
|
|
2449
|
+
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
1998
2450
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2451
|
+
eventType = "analytics.checkout_step_completed";
|
|
2452
|
+
properties = { cartId: decodedCartId, step: payload.step };
|
|
2453
|
+
break;
|
|
2454
|
+
}
|
|
2455
|
+
case "analytics.checkout_completed": {
|
|
2456
|
+
if (!isPlainObject(eventPayload)) {
|
|
2457
|
+
return neverthrow.err(new errors.NetworkError("Invalid orderId or cartId"));
|
|
2003
2458
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2459
|
+
const payload = eventPayload;
|
|
2460
|
+
const orderId = decodeAnalyticsEntityId(payload.orderId);
|
|
2461
|
+
const cartId = decodeAnalyticsEntityId(payload.cartId);
|
|
2462
|
+
if (!orderId || !cartId) {
|
|
2463
|
+
return neverthrow.err(new errors.NetworkError("Invalid orderId or cartId"));
|
|
2007
2464
|
}
|
|
2465
|
+
eventType = "analytics.checkout_completed";
|
|
2466
|
+
properties = {
|
|
2467
|
+
orderId,
|
|
2468
|
+
cartId,
|
|
2469
|
+
orderTotalCents: toCents(payload.orderTotal)
|
|
2470
|
+
};
|
|
2471
|
+
break;
|
|
2008
2472
|
}
|
|
2009
|
-
cursor
|
|
2010
|
-
}
|
|
2011
|
-
pageInfo {
|
|
2012
|
-
hasNextPage
|
|
2013
|
-
hasPreviousPage
|
|
2014
|
-
startCursor
|
|
2015
|
-
endCursor
|
|
2016
2473
|
}
|
|
2474
|
+
} else {
|
|
2475
|
+
eventType = "analytics.custom";
|
|
2476
|
+
properties = {
|
|
2477
|
+
...isPlainObject(eventPayload) ? eventPayload : {},
|
|
2478
|
+
eventName: normalized.customEventName ?? eventName
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
const normalizedContext = normalizeEventContext(eventContext);
|
|
2482
|
+
const resolvedAnalytics = getResolvedAnalyticsConfig() ?? buildDefaultResolvedAnalyticsConfig(getTrackingRuntime());
|
|
2483
|
+
if (!resolvedAnalytics.enabled) {
|
|
2484
|
+
return neverthrow.ok(ACCEPTED_UNKNOWN_RESPONSE);
|
|
2485
|
+
}
|
|
2486
|
+
const trackingRuntime = getTrackingRuntime();
|
|
2487
|
+
const consentState = await resolveConsentState(normalizedContext.consentState);
|
|
2488
|
+
const event = buildCanonicalEvent(eventType, properties, normalizedContext, consentState);
|
|
2489
|
+
if (shouldMirrorEventToBrowserRuntime(event.eventType)) {
|
|
2490
|
+
dispatchEventToBrowserRuntime(trackingRuntime, event, resolvedAnalytics, emitDiagnostic, scheduleBestEffort);
|
|
2491
|
+
}
|
|
2492
|
+
const outcome = await sendEventOutcome(event);
|
|
2493
|
+
if (outcome.result.isErr() && outcome.retryDisposition.retryable) {
|
|
2494
|
+
retryQueue.enqueue({
|
|
2495
|
+
event,
|
|
2496
|
+
queuedAt: Date.now(),
|
|
2497
|
+
...outcome.retryDisposition.notBefore !== void 0 ? { notBefore: outcome.retryDisposition.notBefore } : {}
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
if (outcome.result.isOk() && retryQueue.read().length > 0) {
|
|
2501
|
+
flushRetryQueueInBackground();
|
|
2017
2502
|
}
|
|
2503
|
+
return outcome.result;
|
|
2018
2504
|
}
|
|
2505
|
+
return {
|
|
2506
|
+
init,
|
|
2507
|
+
track
|
|
2508
|
+
};
|
|
2019
2509
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2510
|
+
function dispatchConfirmedPurchaseBrowserEvent(client, purchaseTracking) {
|
|
2511
|
+
const trackingAttributionTtlMs = client.config.trackingAttributionTTL;
|
|
2512
|
+
const runtimeClient = client;
|
|
2513
|
+
const trackingRuntime = runtimeClient._analyticsRuntimeConfig ?? client.config.tracking ?? null;
|
|
2514
|
+
const resolvedAnalytics = runtimeClient._resolvedInitConfig?.analytics ?? buildDefaultResolvedAnalyticsConfig(trackingRuntime);
|
|
2515
|
+
if (!trackingRuntime || !resolvedAnalytics.enabled || (trackingRuntime.adapters?.length ?? 0) === 0) {
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
const runtime = trackingRuntime;
|
|
2519
|
+
function emitDiagnostic(diagnostic) {
|
|
2023
2520
|
try {
|
|
2024
|
-
|
|
2025
|
-
return decoded.includes(":");
|
|
2521
|
+
runtime.onDiagnostic?.(diagnostic);
|
|
2026
2522
|
} catch {
|
|
2027
|
-
return false;
|
|
2028
2523
|
}
|
|
2029
2524
|
}
|
|
2030
|
-
|
|
2525
|
+
function scheduleBestEffort(task) {
|
|
2526
|
+
Promise.resolve().then(task).catch(() => {
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
async function resolveConsentState() {
|
|
2530
|
+
if (!runtime.resolveConsentState) {
|
|
2531
|
+
return "unknown";
|
|
2532
|
+
}
|
|
2533
|
+
try {
|
|
2534
|
+
return await runtime.resolveConsentState();
|
|
2535
|
+
} catch (error) {
|
|
2536
|
+
emitDiagnostic({
|
|
2537
|
+
target: "consent",
|
|
2538
|
+
status: "error",
|
|
2539
|
+
error: toError(error, "Failed to resolve tracking consent state")
|
|
2540
|
+
});
|
|
2541
|
+
return "unknown";
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
function buildLineItemsPayload(lineItems) {
|
|
2545
|
+
return lineItems.map((item) => ({
|
|
2546
|
+
id: item.contentId,
|
|
2547
|
+
content_id: item.contentId,
|
|
2548
|
+
productId: item.productId,
|
|
2549
|
+
variantId: item.variantId,
|
|
2550
|
+
sku: item.sku,
|
|
2551
|
+
productTitle: item.productTitle,
|
|
2552
|
+
variantTitle: item.variantTitle,
|
|
2553
|
+
quantity: item.quantity,
|
|
2554
|
+
unitPrice: item.unitPrice,
|
|
2555
|
+
unitPriceCents: toCents(item.unitPrice),
|
|
2556
|
+
lineTotal: item.lineTotal,
|
|
2557
|
+
lineTotalCents: toCents(item.lineTotal)
|
|
2558
|
+
}));
|
|
2559
|
+
}
|
|
2560
|
+
scheduleBestEffort(async () => {
|
|
2561
|
+
const normalizedContext = normalizeEventContext(void 0);
|
|
2562
|
+
const consentState = await resolveConsentState();
|
|
2563
|
+
const now = /* @__PURE__ */ new Date();
|
|
2564
|
+
const event = {
|
|
2565
|
+
schemaVersion: TRACKING_SCHEMA_VERSION,
|
|
2566
|
+
eventId: purchaseTracking.eventId,
|
|
2567
|
+
eventType: "analytics.checkout_completed",
|
|
2568
|
+
occurredAt: now.toISOString(),
|
|
2569
|
+
sessionId: resolveSessionId(now.getTime()),
|
|
2570
|
+
visitorId: getOrCreateVisitorId(),
|
|
2571
|
+
customerId: parseCustomerIdFromToken(client.getCustomerToken()),
|
|
2572
|
+
consentState,
|
|
2573
|
+
context: {
|
|
2574
|
+
schemaVersion: TRACKING_SCHEMA_VERSION,
|
|
2575
|
+
path: normalizePath(normalizedContext.path ?? "/")
|
|
2576
|
+
},
|
|
2577
|
+
referrer: normalizedContext.referrer ?? null,
|
|
2578
|
+
utm: buildUtmPayload(normalizedContext, trackingAttributionTtlMs),
|
|
2579
|
+
clickIds: buildClickIdsPayload(normalizedContext, trackingAttributionTtlMs),
|
|
2580
|
+
device: {
|
|
2581
|
+
deviceType: normalizedContext.deviceType ?? "unknown",
|
|
2582
|
+
deviceOs: normalizedContext.deviceOs ?? null,
|
|
2583
|
+
deviceBrowser: normalizedContext.deviceBrowser ?? null
|
|
2584
|
+
},
|
|
2585
|
+
properties: {
|
|
2586
|
+
orderId: purchaseTracking.orderId,
|
|
2587
|
+
cartId: purchaseTracking.cartId,
|
|
2588
|
+
orderTotalCents: toCents(purchaseTracking.total),
|
|
2589
|
+
totalCents: toCents(purchaseTracking.total),
|
|
2590
|
+
currency: purchaseTracking.currency,
|
|
2591
|
+
numItems: purchaseTracking.numItems,
|
|
2592
|
+
lineItems: buildLineItemsPayload(purchaseTracking.lineItems)
|
|
2593
|
+
}
|
|
2594
|
+
};
|
|
2595
|
+
dispatchEventToBrowserRuntime(runtime, event, resolvedAnalytics, emitDiagnostic, scheduleBestEffort);
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
const CHECKOUT_PURCHASE_DISPATCH_IDS_KEY = "ekomerc_checkout_purchase_dispatch_ids";
|
|
2599
|
+
const MAX_STORED_PURCHASE_DISPATCH_IDS = 20;
|
|
2600
|
+
const CHECKOUT_START_MUTATION = `
|
|
2601
|
+
mutation CheckoutStart {
|
|
2602
|
+
checkoutStart {
|
|
2603
|
+
cart {
|
|
2604
|
+
${CART_FRAGMENT}
|
|
2605
|
+
}
|
|
2606
|
+
userErrors {
|
|
2607
|
+
field
|
|
2608
|
+
message
|
|
2609
|
+
code
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
`;
|
|
2614
|
+
const CHECKOUT_UPDATE_MUTATION = `
|
|
2615
|
+
mutation CheckoutUpdate($input: CheckoutUpdateInput!) {
|
|
2616
|
+
checkoutUpdate(input: $input) {
|
|
2617
|
+
cart {
|
|
2618
|
+
${CART_FRAGMENT}
|
|
2619
|
+
}
|
|
2620
|
+
userErrors {
|
|
2621
|
+
field
|
|
2622
|
+
message
|
|
2623
|
+
code
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
`;
|
|
2628
|
+
const CHECKOUT_CONVERT_MUTATION = `
|
|
2629
|
+
mutation CheckoutConvert {
|
|
2630
|
+
checkoutConvert {
|
|
2631
|
+
order {
|
|
2632
|
+
id
|
|
2633
|
+
orderNumber
|
|
2634
|
+
status
|
|
2635
|
+
customerEmail
|
|
2636
|
+
customerPhone
|
|
2637
|
+
shippingAddress
|
|
2638
|
+
billingAddress
|
|
2639
|
+
subtotal
|
|
2640
|
+
total
|
|
2641
|
+
note
|
|
2642
|
+
items {
|
|
2643
|
+
id
|
|
2644
|
+
productTitle
|
|
2645
|
+
variantTitle
|
|
2646
|
+
sku
|
|
2647
|
+
quantity
|
|
2648
|
+
unitPrice
|
|
2649
|
+
totalPrice
|
|
2650
|
+
}
|
|
2651
|
+
createdAt
|
|
2652
|
+
}
|
|
2653
|
+
paymentInstructions {
|
|
2654
|
+
bankAccount
|
|
2655
|
+
recipientName
|
|
2656
|
+
referenceNumber
|
|
2657
|
+
ipsQrCodeBase64
|
|
2658
|
+
expiresAt
|
|
2659
|
+
}
|
|
2660
|
+
purchaseTracking {
|
|
2661
|
+
eventId
|
|
2662
|
+
orderId
|
|
2663
|
+
cartId
|
|
2664
|
+
total
|
|
2665
|
+
currency
|
|
2666
|
+
numItems
|
|
2667
|
+
lineItems {
|
|
2668
|
+
contentId
|
|
2669
|
+
productId
|
|
2670
|
+
variantId
|
|
2671
|
+
sku
|
|
2672
|
+
productTitle
|
|
2673
|
+
variantTitle
|
|
2674
|
+
quantity
|
|
2675
|
+
unitPrice
|
|
2676
|
+
lineTotal
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
userErrors {
|
|
2680
|
+
field
|
|
2681
|
+
message
|
|
2682
|
+
code
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
`;
|
|
2687
|
+
const CHECKOUT_ABANDON_MUTATION = `
|
|
2688
|
+
mutation CheckoutAbandon {
|
|
2689
|
+
checkoutAbandon {
|
|
2690
|
+
cart {
|
|
2691
|
+
${CART_FRAGMENT}
|
|
2692
|
+
}
|
|
2693
|
+
userErrors {
|
|
2694
|
+
field
|
|
2695
|
+
message
|
|
2696
|
+
code
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
`;
|
|
2701
|
+
function mapOrderData(data, paymentInstructions) {
|
|
2702
|
+
return {
|
|
2703
|
+
id: data.id,
|
|
2704
|
+
orderNumber: data.orderNumber,
|
|
2705
|
+
email: data.customerEmail,
|
|
2706
|
+
phone: data.customerPhone,
|
|
2707
|
+
status: data.status,
|
|
2708
|
+
shippingAddress: data.shippingAddress,
|
|
2709
|
+
subtotal: data.subtotal,
|
|
2710
|
+
total: data.total,
|
|
2711
|
+
note: data.note,
|
|
2712
|
+
items: data.items.map((item) => ({
|
|
2713
|
+
id: item.id,
|
|
2714
|
+
productTitle: item.productTitle,
|
|
2715
|
+
variantTitle: item.variantTitle,
|
|
2716
|
+
sku: item.sku,
|
|
2717
|
+
quantity: item.quantity,
|
|
2718
|
+
unitPrice: item.unitPrice,
|
|
2719
|
+
totalPrice: item.totalPrice
|
|
2720
|
+
})),
|
|
2721
|
+
paymentInstructions: paymentInstructions ?? null,
|
|
2722
|
+
createdAt: data.createdAt
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function handleUserErrors(userErrors) {
|
|
2726
|
+
if (userErrors.length === 0) return null;
|
|
2727
|
+
const messages = userErrors.map((e) => e.message).join("; ");
|
|
2728
|
+
const stateErrorCodes = ["CART_NOT_IN_CHECKOUT", "CHECKOUT_START_ERROR", "CHECKOUT_ABANDON_ERROR"];
|
|
2729
|
+
const stateError = userErrors.find(
|
|
2730
|
+
(e) => e.code?.includes("STATE") || e.message.includes("state") || e.code && stateErrorCodes.includes(e.code)
|
|
2731
|
+
);
|
|
2732
|
+
if (stateError) {
|
|
2733
|
+
return new errors.StateError(messages, "unknown");
|
|
2734
|
+
}
|
|
2735
|
+
return new errors.ValidationError(
|
|
2736
|
+
messages,
|
|
2737
|
+
userErrors.map((e) => ({ field: e.field, message: e.message }))
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
function checkCartIsInCheckout(status) {
|
|
2741
|
+
if (status !== "checkout") {
|
|
2742
|
+
return neverthrow.err(
|
|
2743
|
+
new errors.StateError(`Cart must be in 'checkout' state for this operation. Current state: '${status}'.`, status)
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
return neverthrow.ok(void 0);
|
|
2747
|
+
}
|
|
2748
|
+
function readHandledPurchaseDispatchIds(storage) {
|
|
2749
|
+
const raw = storage.get(CHECKOUT_PURCHASE_DISPATCH_IDS_KEY);
|
|
2750
|
+
if (!raw) {
|
|
2751
|
+
return [];
|
|
2752
|
+
}
|
|
2753
|
+
try {
|
|
2754
|
+
const parsed = JSON.parse(raw);
|
|
2755
|
+
if (!Array.isArray(parsed)) {
|
|
2756
|
+
return [];
|
|
2757
|
+
}
|
|
2758
|
+
return parsed.filter((value) => typeof value === "string" && value.length > 0);
|
|
2759
|
+
} catch {
|
|
2760
|
+
return [];
|
|
2761
|
+
}
|
|
2031
2762
|
}
|
|
2032
|
-
function
|
|
2763
|
+
function hasHandledPurchaseDispatch(storage, eventId) {
|
|
2764
|
+
return readHandledPurchaseDispatchIds(storage).includes(eventId);
|
|
2765
|
+
}
|
|
2766
|
+
function markPurchaseDispatchHandled(storage, eventId) {
|
|
2767
|
+
const next = [eventId, ...readHandledPurchaseDispatchIds(storage).filter((value) => value !== eventId)].slice(
|
|
2768
|
+
0,
|
|
2769
|
+
MAX_STORED_PURCHASE_DISPATCH_IDS
|
|
2770
|
+
);
|
|
2771
|
+
storage.set(CHECKOUT_PURCHASE_DISPATCH_IDS_KEY, JSON.stringify(next));
|
|
2772
|
+
}
|
|
2773
|
+
function createCheckoutOperations(client, storage) {
|
|
2033
2774
|
return {
|
|
2034
|
-
async
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
first: options?.first,
|
|
2039
|
-
after: options?.after,
|
|
2040
|
-
search: options?.search
|
|
2041
|
-
}
|
|
2042
|
-
});
|
|
2043
|
-
if (result.isErr()) {
|
|
2044
|
-
return neverthrow.err(result.error);
|
|
2775
|
+
async start() {
|
|
2776
|
+
const token = storage.get(CART_TOKEN_KEY);
|
|
2777
|
+
if (!token) {
|
|
2778
|
+
return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
|
|
2045
2779
|
}
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2048
|
-
items: connection.edges.map((edge) => mapRawCollection(edge.node, client.config.endpoint)),
|
|
2049
|
-
pageInfo: {
|
|
2050
|
-
hasNextPage: connection.pageInfo.hasNextPage,
|
|
2051
|
-
hasPreviousPage: connection.pageInfo.hasPreviousPage,
|
|
2052
|
-
startCursor: connection.pageInfo.startCursor,
|
|
2053
|
-
endCursor: connection.pageInfo.endCursor
|
|
2054
|
-
}
|
|
2055
|
-
});
|
|
2056
|
-
},
|
|
2057
|
-
async get(idOrHandle) {
|
|
2058
|
-
const useId = isGlobalId$1(idOrHandle);
|
|
2059
|
-
const result = await client.query({
|
|
2060
|
-
query: useId ? COLLECTION_BY_ID_QUERY : COLLECTION_BY_HANDLE_QUERY,
|
|
2061
|
-
variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
|
|
2780
|
+
const result = await client.mutate({
|
|
2781
|
+
query: CHECKOUT_START_MUTATION
|
|
2062
2782
|
});
|
|
2063
2783
|
if (result.isErr()) {
|
|
2064
2784
|
return neverthrow.err(result.error);
|
|
2065
2785
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2786
|
+
const payload = result.value.checkoutStart;
|
|
2787
|
+
const userError = handleUserErrors(payload.userErrors);
|
|
2788
|
+
if (userError) {
|
|
2789
|
+
return neverthrow.err(userError);
|
|
2068
2790
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
},
|
|
2072
|
-
async getProducts(idOrHandle, options) {
|
|
2073
|
-
const useId = isGlobalId$1(idOrHandle);
|
|
2074
|
-
const result = await client.query({
|
|
2075
|
-
query: useId ? COLLECTION_PRODUCTS_BY_ID_QUERY : COLLECTION_PRODUCTS_BY_HANDLE_QUERY,
|
|
2076
|
-
variables: {
|
|
2077
|
-
...useId ? { id: idOrHandle } : { handle: idOrHandle },
|
|
2078
|
-
first: options?.first,
|
|
2079
|
-
after: options?.after,
|
|
2080
|
-
...options?.sort !== void 0 && { sort: options.sort }
|
|
2081
|
-
}
|
|
2082
|
-
});
|
|
2083
|
-
if (result.isErr()) {
|
|
2084
|
-
return neverthrow.err(result.error);
|
|
2791
|
+
if (!payload.cart) {
|
|
2792
|
+
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
2085
2793
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2794
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
2795
|
+
},
|
|
2796
|
+
async update(data) {
|
|
2797
|
+
const token = storage.get(CART_TOKEN_KEY);
|
|
2798
|
+
if (!token) {
|
|
2799
|
+
return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
|
|
2088
2800
|
}
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
}
|
|
2102
|
-
function mapHttpError(status, body) {
|
|
2103
|
-
if (status === 401 || status === 403) {
|
|
2104
|
-
let message = "Authentication failed";
|
|
2105
|
-
try {
|
|
2106
|
-
const parsed = JSON.parse(body);
|
|
2107
|
-
message = parsed.error ?? parsed.message ?? message;
|
|
2108
|
-
} catch {
|
|
2109
|
-
}
|
|
2110
|
-
return new errors.AuthError(message);
|
|
2111
|
-
}
|
|
2112
|
-
if (status === 404) {
|
|
2113
|
-
return new errors.NotFoundError("Resource not found");
|
|
2114
|
-
}
|
|
2115
|
-
return new errors.GraphQLError(`HTTP error ${status}`, [{ message: body }]);
|
|
2116
|
-
}
|
|
2117
|
-
function mapGraphQLErrors(errors$1) {
|
|
2118
|
-
const messages = errors$1.map((e) => e.message).join("; ");
|
|
2119
|
-
return new errors.GraphQLError(messages, errors$1);
|
|
2120
|
-
}
|
|
2121
|
-
function getCacheKey(request) {
|
|
2122
|
-
return JSON.stringify({
|
|
2123
|
-
query: request.query,
|
|
2124
|
-
variables: request.variables ?? {},
|
|
2125
|
-
operationName: request.operationName
|
|
2126
|
-
});
|
|
2127
|
-
}
|
|
2128
|
-
function createGraphQLClient(config) {
|
|
2129
|
-
async function execute(request) {
|
|
2130
|
-
const headers = {
|
|
2131
|
-
"Content-Type": "application/json",
|
|
2132
|
-
"x-storefront-key": config.apiKey
|
|
2133
|
-
};
|
|
2134
|
-
const cartToken = config.getCartToken();
|
|
2135
|
-
if (cartToken) {
|
|
2136
|
-
headers["x-cart-token"] = cartToken;
|
|
2137
|
-
}
|
|
2138
|
-
const customerToken = config.getCustomerToken();
|
|
2139
|
-
if (customerToken) {
|
|
2140
|
-
headers["Authorization"] = `Bearer ${customerToken}`;
|
|
2141
|
-
}
|
|
2142
|
-
let response;
|
|
2143
|
-
try {
|
|
2144
|
-
response = await fetch(config.endpoint, {
|
|
2145
|
-
method: "POST",
|
|
2146
|
-
headers,
|
|
2147
|
-
body: JSON.stringify({
|
|
2148
|
-
query: request.query,
|
|
2149
|
-
variables: request.variables,
|
|
2150
|
-
operationName: request.operationName
|
|
2151
|
-
})
|
|
2801
|
+
const input = {};
|
|
2802
|
+
if (data.email !== void 0) input.customerEmail = data.email;
|
|
2803
|
+
if (data.phone !== void 0) input.customerPhone = data.phone;
|
|
2804
|
+
if (data.shippingAddress !== void 0) input.shippingAddress = data.shippingAddress;
|
|
2805
|
+
if (data.billingAddress !== void 0) input.billingAddress = data.billingAddress;
|
|
2806
|
+
if (data.notes !== void 0) input.notes = data.notes;
|
|
2807
|
+
if (data.emailMarketingConsent !== void 0) input.emailMarketingConsent = data.emailMarketingConsent;
|
|
2808
|
+
if (data.paymentMethod !== void 0) input.paymentMethod = data.paymentMethod;
|
|
2809
|
+
if (data.shippingRateId !== void 0) input.shippingRateId = data.shippingRateId;
|
|
2810
|
+
const result = await client.mutate({
|
|
2811
|
+
query: CHECKOUT_UPDATE_MUTATION,
|
|
2812
|
+
variables: { input }
|
|
2152
2813
|
});
|
|
2153
|
-
} catch (error) {
|
|
2154
|
-
const message = error instanceof Error ? error.message : "Network request failed";
|
|
2155
|
-
return neverthrow.err(new errors.NetworkError(message, { cause: error instanceof Error ? error : void 0 }));
|
|
2156
|
-
}
|
|
2157
|
-
if (!response.ok) {
|
|
2158
|
-
const body = await response.text().catch(() => "");
|
|
2159
|
-
return neverthrow.err(mapHttpError(response.status, body));
|
|
2160
|
-
}
|
|
2161
|
-
let json;
|
|
2162
|
-
try {
|
|
2163
|
-
json = await response.json();
|
|
2164
|
-
} catch {
|
|
2165
|
-
return neverthrow.err(new errors.GraphQLError("Invalid JSON response", [{ message: "Failed to parse response" }]));
|
|
2166
|
-
}
|
|
2167
|
-
return neverthrow.ok(json);
|
|
2168
|
-
}
|
|
2169
|
-
return {
|
|
2170
|
-
async query(request, options) {
|
|
2171
|
-
const useCache = options?.cache !== false;
|
|
2172
|
-
const cacheKey = getCacheKey(request);
|
|
2173
|
-
if (useCache) {
|
|
2174
|
-
const cached = config.cache.get(cacheKey);
|
|
2175
|
-
if (cached !== null) {
|
|
2176
|
-
return neverthrow.ok(cached);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
const result = await execute(request);
|
|
2180
2814
|
if (result.isErr()) {
|
|
2181
2815
|
return neverthrow.err(result.error);
|
|
2182
2816
|
}
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2817
|
+
const payload = result.value.checkoutUpdate;
|
|
2818
|
+
const userError = handleUserErrors(payload.userErrors);
|
|
2819
|
+
if (userError) {
|
|
2820
|
+
return neverthrow.err(userError);
|
|
2186
2821
|
}
|
|
2187
|
-
if (!
|
|
2188
|
-
return neverthrow.err(new errors.
|
|
2822
|
+
if (!payload.cart) {
|
|
2823
|
+
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
2189
2824
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2825
|
+
const stateCheck = checkCartIsInCheckout(payload.cart.status);
|
|
2826
|
+
if (stateCheck.isErr()) {
|
|
2827
|
+
return neverthrow.err(stateCheck.error);
|
|
2192
2828
|
}
|
|
2193
|
-
return neverthrow.ok(
|
|
2829
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
2194
2830
|
},
|
|
2195
|
-
async
|
|
2196
|
-
const
|
|
2831
|
+
async complete() {
|
|
2832
|
+
const token = storage.get(CART_TOKEN_KEY);
|
|
2833
|
+
if (!token) {
|
|
2834
|
+
return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
|
|
2835
|
+
}
|
|
2836
|
+
const result = await client.mutate({
|
|
2837
|
+
query: CHECKOUT_CONVERT_MUTATION
|
|
2838
|
+
});
|
|
2197
2839
|
if (result.isErr()) {
|
|
2198
2840
|
return neverthrow.err(result.error);
|
|
2199
2841
|
}
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2842
|
+
const payload = result.value.checkoutConvert;
|
|
2843
|
+
const userError = handleUserErrors(payload.userErrors);
|
|
2844
|
+
if (userError) {
|
|
2845
|
+
return neverthrow.err(userError);
|
|
2203
2846
|
}
|
|
2204
|
-
if (!
|
|
2205
|
-
return neverthrow.err(new errors.
|
|
2847
|
+
if (!payload.order) {
|
|
2848
|
+
return neverthrow.err(new errors.NotFoundError("Order not found"));
|
|
2206
2849
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
additionalFee
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
`;
|
|
2232
|
-
function mapPaymentMethod(data) {
|
|
2233
|
-
return {
|
|
2234
|
-
method: data.method,
|
|
2235
|
-
displayName: data.displayName,
|
|
2236
|
-
additionalFee: data.additionalFee
|
|
2237
|
-
};
|
|
2238
|
-
}
|
|
2239
|
-
function createPaymentsOperations(client) {
|
|
2240
|
-
return {
|
|
2241
|
-
async getAvailableMethods() {
|
|
2242
|
-
const result = await client.query(
|
|
2243
|
-
{ query: AVAILABLE_PAYMENT_METHODS_QUERY },
|
|
2244
|
-
{ cache: true }
|
|
2245
|
-
);
|
|
2850
|
+
storage.remove(CART_TOKEN_KEY);
|
|
2851
|
+
if (payload.purchaseTracking) {
|
|
2852
|
+
const confirmedPurchaseTracking = {
|
|
2853
|
+
...payload.purchaseTracking,
|
|
2854
|
+
eventId: payload.purchaseTracking.orderId
|
|
2855
|
+
};
|
|
2856
|
+
if (!hasHandledPurchaseDispatch(storage, confirmedPurchaseTracking.eventId)) {
|
|
2857
|
+
markPurchaseDispatchHandled(storage, confirmedPurchaseTracking.eventId);
|
|
2858
|
+
dispatchConfirmedPurchaseBrowserEvent(client, confirmedPurchaseTracking);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
return neverthrow.ok(mapOrderData(payload.order, payload.paymentInstructions));
|
|
2862
|
+
},
|
|
2863
|
+
async abandon() {
|
|
2864
|
+
const token = storage.get(CART_TOKEN_KEY);
|
|
2865
|
+
if (!token) {
|
|
2866
|
+
return neverthrow.err(new errors.NotFoundError("No cart exists. Call cart.create() first."));
|
|
2867
|
+
}
|
|
2868
|
+
const result = await client.mutate({
|
|
2869
|
+
query: CHECKOUT_ABANDON_MUTATION
|
|
2870
|
+
});
|
|
2246
2871
|
if (result.isErr()) {
|
|
2247
2872
|
return neverthrow.err(result.error);
|
|
2248
2873
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2874
|
+
const payload = result.value.checkoutAbandon;
|
|
2875
|
+
const userError = handleUserErrors(payload.userErrors);
|
|
2876
|
+
if (userError) {
|
|
2877
|
+
return neverthrow.err(userError);
|
|
2251
2878
|
}
|
|
2252
|
-
|
|
2879
|
+
if (!payload.cart) {
|
|
2880
|
+
return neverthrow.err(new errors.NotFoundError("Cart not found"));
|
|
2881
|
+
}
|
|
2882
|
+
return neverthrow.ok(mapCartData(payload.cart, client.config.endpoint));
|
|
2253
2883
|
}
|
|
2254
2884
|
};
|
|
2255
2885
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2886
|
+
function mapRawCollection(raw, endpoint) {
|
|
2887
|
+
return {
|
|
2888
|
+
id: raw.id,
|
|
2889
|
+
handle: raw.handle,
|
|
2890
|
+
title: raw.title,
|
|
2891
|
+
description: raw.description,
|
|
2892
|
+
type: raw.type,
|
|
2893
|
+
sortOrder: raw.sortOrder,
|
|
2894
|
+
metaTitle: raw.metaTitle,
|
|
2895
|
+
metaDescription: raw.metaDescription,
|
|
2896
|
+
productCount: raw.productCount,
|
|
2897
|
+
imageAsset: raw.imageAsset ? {
|
|
2898
|
+
url: resolveAssetUrl(raw.imageAsset.url, endpoint),
|
|
2899
|
+
altText: raw.imageAsset.altText,
|
|
2900
|
+
width: raw.imageAsset.width,
|
|
2901
|
+
height: raw.imageAsset.height
|
|
2902
|
+
} : null
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
const COLLECTIONS_QUERY = `
|
|
2906
|
+
query Collections($first: Int, $after: String, $search: String) {
|
|
2907
|
+
collections(first: $first, after: $after, search: $search) {
|
|
2259
2908
|
edges {
|
|
2260
2909
|
node {
|
|
2261
2910
|
id
|
|
2262
2911
|
handle
|
|
2263
2912
|
title
|
|
2264
2913
|
description
|
|
2265
|
-
|
|
2266
|
-
|
|
2914
|
+
type
|
|
2915
|
+
sortOrder
|
|
2267
2916
|
metaTitle
|
|
2268
2917
|
metaDescription
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
availableForSale
|
|
2272
|
-
media {
|
|
2273
|
-
id
|
|
2918
|
+
productCount
|
|
2919
|
+
imageAsset {
|
|
2274
2920
|
url
|
|
2275
2921
|
altText
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
options {
|
|
2279
|
-
id
|
|
2280
|
-
name
|
|
2281
|
-
position
|
|
2282
|
-
values {
|
|
2283
|
-
id
|
|
2284
|
-
value
|
|
2285
|
-
position
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
variants {
|
|
2289
|
-
id
|
|
2290
|
-
title
|
|
2291
|
-
sku
|
|
2292
|
-
price
|
|
2293
|
-
compareAtPrice
|
|
2294
|
-
weight
|
|
2295
|
-
weightUnit
|
|
2296
|
-
requiresShipping
|
|
2297
|
-
availableForSale
|
|
2298
|
-
quantity
|
|
2299
|
-
selectedOptions {
|
|
2300
|
-
id
|
|
2301
|
-
value
|
|
2302
|
-
position
|
|
2303
|
-
}
|
|
2304
|
-
image {
|
|
2305
|
-
id
|
|
2306
|
-
url
|
|
2307
|
-
altText
|
|
2308
|
-
position
|
|
2309
|
-
}
|
|
2310
|
-
isOnSale
|
|
2311
|
-
quantityPricing {
|
|
2312
|
-
minQuantity
|
|
2313
|
-
price
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2316
|
-
categories {
|
|
2317
|
-
id
|
|
2318
|
-
name
|
|
2319
|
-
handle
|
|
2320
|
-
description
|
|
2321
|
-
}
|
|
2322
|
-
primaryCategory {
|
|
2323
|
-
id
|
|
2324
|
-
name
|
|
2325
|
-
handle
|
|
2326
|
-
}
|
|
2327
|
-
tags {
|
|
2328
|
-
id
|
|
2329
|
-
name
|
|
2922
|
+
width
|
|
2923
|
+
height
|
|
2330
2924
|
}
|
|
2331
2925
|
}
|
|
2332
2926
|
cursor
|
|
2333
2927
|
}
|
|
2334
2928
|
pageInfo {
|
|
2335
2929
|
hasNextPage
|
|
2336
|
-
hasPreviousPage
|
|
2337
|
-
startCursor
|
|
2338
|
-
endCursor
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
`;
|
|
2343
|
-
const PRODUCT_BY_ID_QUERY = `
|
|
2344
|
-
query ProductById($id: ID!) {
|
|
2345
|
-
product(id: $id) {
|
|
2346
|
-
id
|
|
2347
|
-
handle
|
|
2348
|
-
title
|
|
2349
|
-
description
|
|
2350
|
-
vendor
|
|
2351
|
-
productType
|
|
2352
|
-
metaTitle
|
|
2353
|
-
metaDescription
|
|
2354
|
-
publishedAt
|
|
2355
|
-
createdAt
|
|
2356
|
-
availableForSale
|
|
2357
|
-
media {
|
|
2358
|
-
id
|
|
2359
|
-
url
|
|
2360
|
-
altText
|
|
2361
|
-
position
|
|
2362
|
-
}
|
|
2363
|
-
options {
|
|
2364
|
-
id
|
|
2365
|
-
name
|
|
2366
|
-
position
|
|
2367
|
-
values {
|
|
2368
|
-
id
|
|
2369
|
-
value
|
|
2370
|
-
position
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
variants {
|
|
2374
|
-
id
|
|
2375
|
-
title
|
|
2376
|
-
sku
|
|
2377
|
-
price
|
|
2378
|
-
compareAtPrice
|
|
2379
|
-
weight
|
|
2380
|
-
weightUnit
|
|
2381
|
-
requiresShipping
|
|
2382
|
-
availableForSale
|
|
2383
|
-
quantity
|
|
2384
|
-
selectedOptions {
|
|
2385
|
-
id
|
|
2386
|
-
value
|
|
2387
|
-
position
|
|
2388
|
-
}
|
|
2389
|
-
image {
|
|
2390
|
-
id
|
|
2391
|
-
url
|
|
2392
|
-
altText
|
|
2393
|
-
position
|
|
2394
|
-
}
|
|
2395
|
-
isOnSale
|
|
2396
|
-
quantityPricing {
|
|
2397
|
-
minQuantity
|
|
2398
|
-
price
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
categories {
|
|
2402
|
-
id
|
|
2403
|
-
name
|
|
2404
|
-
handle
|
|
2405
|
-
description
|
|
2406
|
-
}
|
|
2407
|
-
tags {
|
|
2408
|
-
id
|
|
2409
|
-
name
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
`;
|
|
2414
|
-
const PRODUCT_BY_HANDLE_QUERY = `
|
|
2415
|
-
query ProductByHandle($handle: String!) {
|
|
2416
|
-
product(handle: $handle) {
|
|
2417
|
-
id
|
|
2418
|
-
handle
|
|
2419
|
-
title
|
|
2420
|
-
description
|
|
2421
|
-
vendor
|
|
2422
|
-
productType
|
|
2423
|
-
metaTitle
|
|
2424
|
-
metaDescription
|
|
2425
|
-
publishedAt
|
|
2426
|
-
createdAt
|
|
2427
|
-
availableForSale
|
|
2428
|
-
media {
|
|
2429
|
-
id
|
|
2430
|
-
url
|
|
2431
|
-
altText
|
|
2432
|
-
position
|
|
2433
|
-
}
|
|
2434
|
-
options {
|
|
2435
|
-
id
|
|
2436
|
-
name
|
|
2437
|
-
position
|
|
2438
|
-
values {
|
|
2439
|
-
id
|
|
2440
|
-
value
|
|
2441
|
-
position
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
variants {
|
|
2445
|
-
id
|
|
2446
|
-
title
|
|
2447
|
-
sku
|
|
2448
|
-
price
|
|
2449
|
-
compareAtPrice
|
|
2450
|
-
weight
|
|
2451
|
-
weightUnit
|
|
2452
|
-
requiresShipping
|
|
2453
|
-
availableForSale
|
|
2454
|
-
quantity
|
|
2455
|
-
selectedOptions {
|
|
2456
|
-
id
|
|
2457
|
-
value
|
|
2458
|
-
position
|
|
2459
|
-
}
|
|
2460
|
-
image {
|
|
2461
|
-
id
|
|
2462
|
-
url
|
|
2463
|
-
altText
|
|
2464
|
-
position
|
|
2465
|
-
}
|
|
2466
|
-
isOnSale
|
|
2467
|
-
quantityPricing {
|
|
2468
|
-
minQuantity
|
|
2469
|
-
price
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
categories {
|
|
2473
|
-
id
|
|
2474
|
-
name
|
|
2475
|
-
handle
|
|
2476
|
-
description
|
|
2477
|
-
}
|
|
2478
|
-
tags {
|
|
2479
|
-
id
|
|
2480
|
-
name
|
|
2930
|
+
hasPreviousPage
|
|
2931
|
+
startCursor
|
|
2932
|
+
endCursor
|
|
2481
2933
|
}
|
|
2482
2934
|
}
|
|
2483
2935
|
}
|
|
2484
2936
|
`;
|
|
2485
|
-
const
|
|
2486
|
-
query
|
|
2487
|
-
|
|
2937
|
+
const COLLECTION_BY_ID_QUERY = `
|
|
2938
|
+
query CollectionById($id: GID!) {
|
|
2939
|
+
collection(id: $id) {
|
|
2488
2940
|
id
|
|
2489
2941
|
handle
|
|
2490
2942
|
title
|
|
2491
2943
|
description
|
|
2492
|
-
|
|
2493
|
-
|
|
2944
|
+
type
|
|
2945
|
+
sortOrder
|
|
2494
2946
|
metaTitle
|
|
2495
2947
|
metaDescription
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
availableForSale
|
|
2499
|
-
media {
|
|
2500
|
-
id
|
|
2948
|
+
productCount
|
|
2949
|
+
imageAsset {
|
|
2501
2950
|
url
|
|
2502
2951
|
altText
|
|
2503
|
-
|
|
2952
|
+
width
|
|
2953
|
+
height
|
|
2504
2954
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
`;
|
|
2958
|
+
const COLLECTION_BY_HANDLE_QUERY = `
|
|
2959
|
+
query CollectionByHandle($handle: String!) {
|
|
2960
|
+
collection(handle: $handle) {
|
|
2961
|
+
id
|
|
2962
|
+
handle
|
|
2963
|
+
title
|
|
2964
|
+
description
|
|
2965
|
+
type
|
|
2966
|
+
sortOrder
|
|
2967
|
+
metaTitle
|
|
2968
|
+
metaDescription
|
|
2969
|
+
productCount
|
|
2970
|
+
imageAsset {
|
|
2971
|
+
url
|
|
2972
|
+
altText
|
|
2973
|
+
width
|
|
2974
|
+
height
|
|
2514
2975
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
`;
|
|
2979
|
+
const COLLECTION_PRODUCTS_BY_ID_QUERY = `
|
|
2980
|
+
query CollectionProductsById($id: GID!, $first: Int, $after: String, $sort: CollectionSortOrder) {
|
|
2981
|
+
collection(id: $id) {
|
|
2982
|
+
id
|
|
2983
|
+
products(first: $first, after: $after, sort: $sort) {
|
|
2984
|
+
edges {
|
|
2985
|
+
node {
|
|
2986
|
+
id
|
|
2987
|
+
handle
|
|
2988
|
+
title
|
|
2989
|
+
description
|
|
2990
|
+
vendor
|
|
2991
|
+
productType
|
|
2992
|
+
metaTitle
|
|
2993
|
+
metaDescription
|
|
2994
|
+
publishedAt
|
|
2995
|
+
createdAt
|
|
2996
|
+
availableForSale
|
|
2997
|
+
media {
|
|
2998
|
+
id
|
|
2999
|
+
url
|
|
3000
|
+
altText
|
|
3001
|
+
position
|
|
3002
|
+
}
|
|
3003
|
+
options {
|
|
3004
|
+
id
|
|
3005
|
+
name
|
|
3006
|
+
position
|
|
3007
|
+
values {
|
|
3008
|
+
id
|
|
3009
|
+
value
|
|
3010
|
+
position
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
variants {
|
|
3014
|
+
id
|
|
3015
|
+
title
|
|
3016
|
+
sku
|
|
3017
|
+
price
|
|
3018
|
+
compareAtPrice
|
|
3019
|
+
weight
|
|
3020
|
+
weightUnit
|
|
3021
|
+
requiresShipping
|
|
3022
|
+
availableForSale
|
|
3023
|
+
quantity
|
|
3024
|
+
selectedOptions {
|
|
3025
|
+
id
|
|
3026
|
+
value
|
|
3027
|
+
position
|
|
3028
|
+
}
|
|
3029
|
+
image {
|
|
3030
|
+
id
|
|
3031
|
+
url
|
|
3032
|
+
altText
|
|
3033
|
+
position
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
categories {
|
|
3037
|
+
id
|
|
3038
|
+
name
|
|
3039
|
+
handle
|
|
3040
|
+
description
|
|
3041
|
+
}
|
|
3042
|
+
primaryCategory {
|
|
3043
|
+
id
|
|
3044
|
+
name
|
|
3045
|
+
handle
|
|
3046
|
+
}
|
|
3047
|
+
tags {
|
|
3048
|
+
id
|
|
3049
|
+
name
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
cursor
|
|
2536
3053
|
}
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
3054
|
+
pageInfo {
|
|
3055
|
+
hasNextPage
|
|
3056
|
+
hasPreviousPage
|
|
3057
|
+
startCursor
|
|
3058
|
+
endCursor
|
|
2541
3059
|
}
|
|
2542
3060
|
}
|
|
2543
|
-
categories {
|
|
2544
|
-
id
|
|
2545
|
-
name
|
|
2546
|
-
handle
|
|
2547
|
-
description
|
|
2548
|
-
}
|
|
2549
|
-
tags {
|
|
2550
|
-
id
|
|
2551
|
-
name
|
|
2552
|
-
}
|
|
2553
3061
|
}
|
|
2554
3062
|
}
|
|
2555
3063
|
`;
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
3064
|
+
const COLLECTION_PRODUCTS_BY_HANDLE_QUERY = `
|
|
3065
|
+
query CollectionProductsByHandle($handle: String!, $first: Int, $after: String, $sort: CollectionSortOrder) {
|
|
3066
|
+
collection(handle: $handle) {
|
|
3067
|
+
id
|
|
3068
|
+
products(first: $first, after: $after, sort: $sort) {
|
|
3069
|
+
edges {
|
|
3070
|
+
node {
|
|
3071
|
+
id
|
|
3072
|
+
handle
|
|
3073
|
+
title
|
|
3074
|
+
description
|
|
3075
|
+
vendor
|
|
3076
|
+
productType
|
|
3077
|
+
metaTitle
|
|
3078
|
+
metaDescription
|
|
3079
|
+
publishedAt
|
|
3080
|
+
createdAt
|
|
3081
|
+
availableForSale
|
|
3082
|
+
media {
|
|
3083
|
+
id
|
|
3084
|
+
url
|
|
3085
|
+
altText
|
|
3086
|
+
position
|
|
3087
|
+
}
|
|
3088
|
+
options {
|
|
3089
|
+
id
|
|
3090
|
+
name
|
|
3091
|
+
position
|
|
3092
|
+
values {
|
|
3093
|
+
id
|
|
3094
|
+
value
|
|
3095
|
+
position
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
variants {
|
|
3099
|
+
id
|
|
3100
|
+
title
|
|
3101
|
+
sku
|
|
3102
|
+
price
|
|
3103
|
+
compareAtPrice
|
|
3104
|
+
weight
|
|
3105
|
+
weightUnit
|
|
3106
|
+
requiresShipping
|
|
3107
|
+
availableForSale
|
|
3108
|
+
quantity
|
|
3109
|
+
selectedOptions {
|
|
3110
|
+
id
|
|
3111
|
+
value
|
|
3112
|
+
position
|
|
3113
|
+
}
|
|
3114
|
+
image {
|
|
3115
|
+
id
|
|
3116
|
+
url
|
|
3117
|
+
altText
|
|
3118
|
+
position
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
categories {
|
|
3122
|
+
id
|
|
3123
|
+
name
|
|
3124
|
+
handle
|
|
3125
|
+
description
|
|
3126
|
+
}
|
|
3127
|
+
primaryCategory {
|
|
3128
|
+
id
|
|
3129
|
+
name
|
|
3130
|
+
handle
|
|
3131
|
+
}
|
|
3132
|
+
tags {
|
|
3133
|
+
id
|
|
3134
|
+
name
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
cursor
|
|
3138
|
+
}
|
|
3139
|
+
pageInfo {
|
|
3140
|
+
hasNextPage
|
|
3141
|
+
hasPreviousPage
|
|
3142
|
+
startCursor
|
|
3143
|
+
endCursor
|
|
3144
|
+
}
|
|
2563
3145
|
}
|
|
2564
3146
|
}
|
|
2565
|
-
return false;
|
|
2566
3147
|
}
|
|
2567
|
-
|
|
3148
|
+
`;
|
|
3149
|
+
function createCollectionsOperations(client) {
|
|
2568
3150
|
return {
|
|
2569
3151
|
async list(options) {
|
|
2570
3152
|
const result = await client.query({
|
|
2571
|
-
query:
|
|
3153
|
+
query: COLLECTIONS_QUERY,
|
|
2572
3154
|
variables: {
|
|
2573
3155
|
first: options?.first,
|
|
2574
3156
|
after: options?.after,
|
|
2575
|
-
|
|
2576
|
-
sort: options?.sort
|
|
3157
|
+
search: options?.search
|
|
2577
3158
|
}
|
|
2578
3159
|
});
|
|
2579
3160
|
if (result.isErr()) {
|
|
2580
3161
|
return neverthrow.err(result.error);
|
|
2581
3162
|
}
|
|
2582
|
-
const connection = result.value.
|
|
3163
|
+
const connection = result.value.collections;
|
|
2583
3164
|
return neverthrow.ok({
|
|
2584
|
-
items: connection.edges.map((edge) =>
|
|
3165
|
+
items: connection.edges.map((edge) => mapRawCollection(edge.node, client.config.endpoint)),
|
|
2585
3166
|
pageInfo: {
|
|
2586
3167
|
hasNextPage: connection.pageInfo.hasNextPage,
|
|
2587
3168
|
hasPreviousPage: connection.pageInfo.hasPreviousPage,
|
|
@@ -2593,547 +3174,624 @@ function createProductsOperations(client) {
|
|
|
2593
3174
|
async get(idOrHandle) {
|
|
2594
3175
|
const useId = isGlobalId(idOrHandle);
|
|
2595
3176
|
const result = await client.query({
|
|
2596
|
-
query: useId ?
|
|
3177
|
+
query: useId ? COLLECTION_BY_ID_QUERY : COLLECTION_BY_HANDLE_QUERY,
|
|
2597
3178
|
variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
|
|
2598
3179
|
});
|
|
2599
3180
|
if (result.isErr()) {
|
|
2600
3181
|
return neverthrow.err(result.error);
|
|
2601
3182
|
}
|
|
2602
|
-
if (!result.value.
|
|
2603
|
-
return neverthrow.err(new errors.NotFoundError(`
|
|
3183
|
+
if (!result.value.collection) {
|
|
3184
|
+
return neverthrow.err(new errors.NotFoundError(`Collection not found: ${idOrHandle}`));
|
|
2604
3185
|
}
|
|
2605
|
-
|
|
3186
|
+
const collection = mapRawCollection(result.value.collection, client.config.endpoint);
|
|
3187
|
+
return neverthrow.ok(normalizeCollectionAssetUrls(collection, client.config.endpoint));
|
|
2606
3188
|
},
|
|
2607
|
-
async
|
|
2608
|
-
|
|
2609
|
-
return neverthrow.ok([]);
|
|
2610
|
-
}
|
|
3189
|
+
async getProducts(idOrHandle, options) {
|
|
3190
|
+
const useId = isGlobalId(idOrHandle);
|
|
2611
3191
|
const result = await client.query({
|
|
2612
|
-
query:
|
|
2613
|
-
variables: {
|
|
3192
|
+
query: useId ? COLLECTION_PRODUCTS_BY_ID_QUERY : COLLECTION_PRODUCTS_BY_HANDLE_QUERY,
|
|
3193
|
+
variables: {
|
|
3194
|
+
...useId ? { id: idOrHandle } : { handle: idOrHandle },
|
|
3195
|
+
first: options?.first,
|
|
3196
|
+
after: options?.after,
|
|
3197
|
+
...options?.sort !== void 0 && { sort: options.sort }
|
|
3198
|
+
}
|
|
2614
3199
|
});
|
|
2615
3200
|
if (result.isErr()) {
|
|
2616
3201
|
return neverthrow.err(result.error);
|
|
2617
3202
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
3203
|
+
if (!result.value.collection) {
|
|
3204
|
+
return neverthrow.err(new errors.NotFoundError(`Collection not found: ${idOrHandle}`));
|
|
3205
|
+
}
|
|
3206
|
+
const connection = result.value.collection.products;
|
|
3207
|
+
return neverthrow.ok({
|
|
3208
|
+
items: connection.edges.map((edge) => normalizeProductAssetUrls(edge.node, client.config.endpoint)),
|
|
3209
|
+
pageInfo: {
|
|
3210
|
+
hasNextPage: connection.pageInfo.hasNextPage,
|
|
3211
|
+
hasPreviousPage: connection.pageInfo.hasPreviousPage,
|
|
3212
|
+
startCursor: connection.pageInfo.startCursor,
|
|
3213
|
+
endCursor: connection.pageInfo.endCursor
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
2623
3216
|
}
|
|
2624
3217
|
};
|
|
2625
3218
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
const
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
page_view: "analytics.page_view",
|
|
2634
|
-
product_view: "analytics.product_view",
|
|
2635
|
-
collection_view: "analytics.collection_view",
|
|
2636
|
-
search_performed: "analytics.search_performed",
|
|
2637
|
-
add_to_cart: "analytics.add_to_cart",
|
|
2638
|
-
remove_from_cart: "analytics.remove_from_cart",
|
|
2639
|
-
checkout_started: "analytics.checkout_started",
|
|
2640
|
-
checkout_step_completed: "analytics.checkout_step_completed",
|
|
2641
|
-
checkout_completed: "analytics.checkout_completed"
|
|
2642
|
-
};
|
|
2643
|
-
function hasBrowserContext() {
|
|
2644
|
-
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
2645
|
-
}
|
|
2646
|
-
function getDocument() {
|
|
2647
|
-
return hasBrowserContext() ? document : null;
|
|
2648
|
-
}
|
|
2649
|
-
function getWindow() {
|
|
2650
|
-
return hasBrowserContext() ? window : null;
|
|
2651
|
-
}
|
|
2652
|
-
function isUuid(value) {
|
|
2653
|
-
return UUID_REGEX.test(value);
|
|
2654
|
-
}
|
|
2655
|
-
function randomUuid() {
|
|
2656
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
2657
|
-
return crypto.randomUUID();
|
|
2658
|
-
}
|
|
2659
|
-
const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
|
2660
|
-
return template.replace(/[xy]/g, (character) => {
|
|
2661
|
-
const random = Math.floor(Math.random() * 16);
|
|
2662
|
-
const value = character === "x" ? random : random & 3 | 8;
|
|
2663
|
-
return value.toString(16);
|
|
2664
|
-
});
|
|
2665
|
-
}
|
|
2666
|
-
function getOrCreateVisitorId() {
|
|
2667
|
-
const doc = getDocument();
|
|
2668
|
-
if (doc) {
|
|
2669
|
-
const raw = doc.cookie;
|
|
2670
|
-
for (const pair of raw.split(";")) {
|
|
2671
|
-
const [name, ...valueParts] = pair.trim().split("=");
|
|
2672
|
-
if (name === VISITOR_COOKIE_NAME) {
|
|
2673
|
-
return valueParts.join("=") ? decodeURIComponent(valueParts.join("=")) : randomUuid();
|
|
2674
|
-
}
|
|
3219
|
+
function mapHttpError(status, body) {
|
|
3220
|
+
if (status === 401 || status === 403) {
|
|
3221
|
+
let message = "Authentication failed";
|
|
3222
|
+
try {
|
|
3223
|
+
const parsed = JSON.parse(body);
|
|
3224
|
+
message = parsed.error ?? parsed.message ?? message;
|
|
3225
|
+
} catch {
|
|
2675
3226
|
}
|
|
3227
|
+
return new errors.AuthError(message);
|
|
2676
3228
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
const maxAge = 60 * 60 * 24 * 365 * 2;
|
|
2680
|
-
doc.cookie = `${VISITOR_COOKIE_NAME}=${encodeURIComponent(value)}; Max-Age=${maxAge}; Path=/; SameSite=Lax`;
|
|
2681
|
-
}
|
|
2682
|
-
return value;
|
|
2683
|
-
}
|
|
2684
|
-
let fallbackSessionState = null;
|
|
2685
|
-
function getSessionStorageState() {
|
|
2686
|
-
const win = getWindow();
|
|
2687
|
-
if (!win) return fallbackSessionState;
|
|
2688
|
-
try {
|
|
2689
|
-
const raw = win.localStorage.getItem(SESSION_STORAGE_KEY);
|
|
2690
|
-
if (!raw) return fallbackSessionState;
|
|
2691
|
-
const parsed = JSON.parse(raw);
|
|
2692
|
-
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2693
|
-
const id = parsed.id;
|
|
2694
|
-
const startedAt = parsed.startedAt;
|
|
2695
|
-
const lastSeenAt = parsed.lastSeenAt;
|
|
2696
|
-
if (typeof id !== "string" || !id) return null;
|
|
2697
|
-
if (typeof startedAt !== "number" || typeof lastSeenAt !== "number") return null;
|
|
2698
|
-
return { id, startedAt, lastSeenAt };
|
|
2699
|
-
} catch {
|
|
2700
|
-
return fallbackSessionState;
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
function setSessionStorageState(state) {
|
|
2704
|
-
fallbackSessionState = state;
|
|
2705
|
-
const win = getWindow();
|
|
2706
|
-
if (!win) return;
|
|
2707
|
-
try {
|
|
2708
|
-
win.localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(state));
|
|
2709
|
-
} catch {
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
function resolveSessionState(existing, nowMs) {
|
|
2713
|
-
if (!existing) {
|
|
2714
|
-
return { id: randomUuid(), startedAt: nowMs, lastSeenAt: nowMs };
|
|
2715
|
-
}
|
|
2716
|
-
if (nowMs - existing.lastSeenAt > SESSION_TIMEOUT_MS) {
|
|
2717
|
-
return { id: randomUuid(), startedAt: nowMs, lastSeenAt: nowMs };
|
|
3229
|
+
if (status === 404) {
|
|
3230
|
+
return new errors.NotFoundError("Resource not found");
|
|
2718
3231
|
}
|
|
2719
|
-
return
|
|
2720
|
-
}
|
|
2721
|
-
function getSessionId(nowMs) {
|
|
2722
|
-
const existing = getSessionStorageState();
|
|
2723
|
-
const state = resolveSessionState(existing, nowMs);
|
|
2724
|
-
setSessionStorageState(state);
|
|
2725
|
-
return state.id;
|
|
2726
|
-
}
|
|
2727
|
-
function normalizeNumber(value) {
|
|
2728
|
-
return Number.isFinite(value) ? value : 0;
|
|
3232
|
+
return new errors.GraphQLError(`HTTP error ${status}`, [{ message: body }]);
|
|
2729
3233
|
}
|
|
2730
|
-
function
|
|
2731
|
-
|
|
3234
|
+
function mapGraphQLErrors(errors$1) {
|
|
3235
|
+
const messages = errors$1.map((e) => e.message).join("; ");
|
|
3236
|
+
return new errors.GraphQLError(messages, errors$1);
|
|
2732
3237
|
}
|
|
2733
|
-
function
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
3238
|
+
function getCacheKey(request) {
|
|
3239
|
+
return JSON.stringify({
|
|
3240
|
+
query: request.query,
|
|
3241
|
+
variables: request.variables ?? {},
|
|
3242
|
+
operationName: request.operationName
|
|
3243
|
+
});
|
|
2738
3244
|
}
|
|
2739
|
-
function
|
|
2740
|
-
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3245
|
+
function createGraphQLClient(config) {
|
|
3246
|
+
async function execute(request) {
|
|
3247
|
+
const headers = {
|
|
3248
|
+
"Content-Type": "application/json",
|
|
3249
|
+
"x-storefront-key": config.apiKey
|
|
3250
|
+
};
|
|
3251
|
+
const cartToken = config.getCartToken();
|
|
3252
|
+
if (cartToken) {
|
|
3253
|
+
headers["x-cart-token"] = cartToken;
|
|
3254
|
+
}
|
|
3255
|
+
const customerToken = config.getCustomerToken();
|
|
3256
|
+
if (customerToken) {
|
|
3257
|
+
headers["Authorization"] = `Bearer ${customerToken}`;
|
|
3258
|
+
}
|
|
3259
|
+
let response;
|
|
3260
|
+
try {
|
|
3261
|
+
response = await fetch(config.endpoint, {
|
|
3262
|
+
method: "POST",
|
|
3263
|
+
headers,
|
|
3264
|
+
body: JSON.stringify({
|
|
3265
|
+
query: request.query,
|
|
3266
|
+
variables: request.variables,
|
|
3267
|
+
operationName: request.operationName
|
|
3268
|
+
})
|
|
3269
|
+
});
|
|
3270
|
+
} catch (error) {
|
|
3271
|
+
const message = error instanceof Error ? error.message : "Network request failed";
|
|
3272
|
+
return neverthrow.err(new errors.NetworkError(message, { cause: error instanceof Error ? error : void 0 }));
|
|
3273
|
+
}
|
|
3274
|
+
if (!response.ok) {
|
|
3275
|
+
const body = await response.text().catch(() => "");
|
|
3276
|
+
return neverthrow.err(mapHttpError(response.status, body));
|
|
3277
|
+
}
|
|
3278
|
+
let json;
|
|
3279
|
+
try {
|
|
3280
|
+
json = await response.json();
|
|
3281
|
+
} catch {
|
|
3282
|
+
return neverthrow.err(new errors.GraphQLError("Invalid JSON response", [{ message: "Failed to parse response" }]));
|
|
3283
|
+
}
|
|
3284
|
+
return neverthrow.ok(json);
|
|
2746
3285
|
}
|
|
3286
|
+
return {
|
|
3287
|
+
async query(request, options) {
|
|
3288
|
+
const useCache = options?.cache !== false;
|
|
3289
|
+
const cacheKey = getCacheKey(request);
|
|
3290
|
+
if (useCache) {
|
|
3291
|
+
const cached = config.cache.get(cacheKey);
|
|
3292
|
+
if (cached !== null) {
|
|
3293
|
+
return neverthrow.ok(cached);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
const result = await execute(request);
|
|
3297
|
+
if (result.isErr()) {
|
|
3298
|
+
return neverthrow.err(result.error);
|
|
3299
|
+
}
|
|
3300
|
+
const response = result.value;
|
|
3301
|
+
if (response.errors && response.errors.length > 0) {
|
|
3302
|
+
return neverthrow.err(mapGraphQLErrors(response.errors));
|
|
3303
|
+
}
|
|
3304
|
+
if (!response.data) {
|
|
3305
|
+
return neverthrow.err(new errors.GraphQLError("No data in response", [{ message: "Response has no data" }]));
|
|
3306
|
+
}
|
|
3307
|
+
if (useCache) {
|
|
3308
|
+
config.cache.set(cacheKey, response.data, config.cacheTTL);
|
|
3309
|
+
}
|
|
3310
|
+
return neverthrow.ok(response.data);
|
|
3311
|
+
},
|
|
3312
|
+
async mutate(request) {
|
|
3313
|
+
const result = await execute(request);
|
|
3314
|
+
if (result.isErr()) {
|
|
3315
|
+
return neverthrow.err(result.error);
|
|
3316
|
+
}
|
|
3317
|
+
const response = result.value;
|
|
3318
|
+
if (response.errors && response.errors.length > 0) {
|
|
3319
|
+
return neverthrow.err(mapGraphQLErrors(response.errors));
|
|
3320
|
+
}
|
|
3321
|
+
if (!response.data) {
|
|
3322
|
+
return neverthrow.err(new errors.GraphQLError("No data in response", [{ message: "Response has no data" }]));
|
|
3323
|
+
}
|
|
3324
|
+
return neverthrow.ok(response.data);
|
|
3325
|
+
}
|
|
3326
|
+
};
|
|
2747
3327
|
}
|
|
2748
|
-
function
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
if (!encodedPayload) return null;
|
|
2753
|
-
const parsed = parseJWT(encodedPayload);
|
|
2754
|
-
if (!parsed) return null;
|
|
2755
|
-
const customerId = parsed.customerId;
|
|
2756
|
-
return typeof customerId === "string" && isUuid(customerId) ? customerId : null;
|
|
2757
|
-
}
|
|
2758
|
-
function decodeAnalyticsEntityId(value) {
|
|
2759
|
-
if (!value) return null;
|
|
2760
|
-
if (isUuid(value)) return value;
|
|
2761
|
-
if (value.startsWith("gid://")) {
|
|
2762
|
-
const parts = value.split("/");
|
|
2763
|
-
const candidate = parts[parts.length - 1];
|
|
2764
|
-
return candidate && isUuid(candidate) ? candidate : null;
|
|
3328
|
+
function extractUserErrors(data, fieldName) {
|
|
3329
|
+
const payload = data[fieldName];
|
|
3330
|
+
if (!payload || typeof payload !== "object") {
|
|
3331
|
+
return neverthrow.err(new errors.ValidationError("Unexpected response format", []));
|
|
2765
3332
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
const
|
|
2769
|
-
|
|
2770
|
-
return candidate && isUuid(candidate) ? candidate : null;
|
|
2771
|
-
} catch {
|
|
2772
|
-
return null;
|
|
3333
|
+
const typedPayload = payload;
|
|
3334
|
+
if (typedPayload.userErrors && typedPayload.userErrors.length > 0) {
|
|
3335
|
+
const messages = typedPayload.userErrors.map((e) => e.message).join("; ");
|
|
3336
|
+
return neverthrow.err(new errors.ValidationError(messages, typedPayload.userErrors));
|
|
2773
3337
|
}
|
|
3338
|
+
return neverthrow.ok(payload);
|
|
2774
3339
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
const deviceType = /ipad|tablet|playbook|silk/.test(ua) ? "tablet" : /mobi|android|iphone|ipod|windows phone/.test(ua) ? "mobile" : "desktop";
|
|
2782
|
-
let deviceOs = null;
|
|
2783
|
-
if (ua.includes("windows")) {
|
|
2784
|
-
deviceOs = "Windows";
|
|
2785
|
-
} else if (ua.includes("mac os") || ua.includes("macintosh")) {
|
|
2786
|
-
deviceOs = "macOS";
|
|
2787
|
-
} else if (ua.includes("android")) {
|
|
2788
|
-
deviceOs = "Android";
|
|
2789
|
-
} else if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ios")) {
|
|
2790
|
-
deviceOs = "iOS";
|
|
2791
|
-
} else if (ua.includes("linux")) {
|
|
2792
|
-
deviceOs = "Linux";
|
|
2793
|
-
}
|
|
2794
|
-
let deviceBrowser = null;
|
|
2795
|
-
if (ua.includes("edg/")) {
|
|
2796
|
-
deviceBrowser = "Edge";
|
|
2797
|
-
} else if (ua.includes("firefox/")) {
|
|
2798
|
-
deviceBrowser = "Firefox";
|
|
2799
|
-
} else if (ua.includes("chrome/") && !ua.includes("edg/")) {
|
|
2800
|
-
deviceBrowser = "Chrome";
|
|
2801
|
-
} else if (ua.includes("safari/") && !ua.includes("chrome/")) {
|
|
2802
|
-
deviceBrowser = "Safari";
|
|
3340
|
+
const AVAILABLE_PAYMENT_METHODS_QUERY = `
|
|
3341
|
+
query AvailablePaymentMethods {
|
|
3342
|
+
availablePaymentMethods {
|
|
3343
|
+
method
|
|
3344
|
+
displayName
|
|
3345
|
+
additionalFee
|
|
2803
3346
|
}
|
|
2804
|
-
return { deviceType, deviceOs, deviceBrowser };
|
|
2805
|
-
}
|
|
2806
|
-
function parseStoreSlugFromBase(basePath) {
|
|
2807
|
-
if (!basePath) return null;
|
|
2808
|
-
const segments = basePath.split("/").filter(Boolean);
|
|
2809
|
-
if (segments.length < 2) return null;
|
|
2810
|
-
const [mode, slug] = segments;
|
|
2811
|
-
if (mode !== "preview" && mode !== "live") return null;
|
|
2812
|
-
return slug ?? null;
|
|
2813
|
-
}
|
|
2814
|
-
function parseStoreSlugFromHostname(hostname) {
|
|
2815
|
-
if (hostname === "localhost") return null;
|
|
2816
|
-
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return null;
|
|
2817
|
-
const [candidate] = hostname.split(".");
|
|
2818
|
-
if (!candidate || candidate === "www") return null;
|
|
2819
|
-
return candidate;
|
|
2820
|
-
}
|
|
2821
|
-
function normalizePath(pathname) {
|
|
2822
|
-
return pathname.trim().length > 0 ? pathname : "/";
|
|
2823
3347
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
2827
|
-
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
2828
|
-
}
|
|
2829
|
-
function buildUtmPayload(context) {
|
|
2830
|
-
const persisted = getTrackingAttributionSnapshot();
|
|
3348
|
+
`;
|
|
3349
|
+
function mapPaymentMethod(data) {
|
|
2831
3350
|
return {
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
campaign: context.utmCampaign ?? persisted?.utm.campaign ?? null,
|
|
2836
|
-
term: context.utmTerm ?? persisted?.utm.term ?? null,
|
|
2837
|
-
content: context.utmContent ?? persisted?.utm.content ?? null
|
|
3351
|
+
method: data.method,
|
|
3352
|
+
displayName: data.displayName,
|
|
3353
|
+
additionalFee: data.additionalFee
|
|
2838
3354
|
};
|
|
2839
3355
|
}
|
|
2840
|
-
function
|
|
2841
|
-
const persisted = getTrackingAttributionSnapshot();
|
|
2842
|
-
const clickIds = context.clickIds;
|
|
3356
|
+
function createPaymentsOperations(client) {
|
|
2843
3357
|
return {
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3358
|
+
async getAvailableMethods() {
|
|
3359
|
+
const result = await client.query(
|
|
3360
|
+
{ query: AVAILABLE_PAYMENT_METHODS_QUERY },
|
|
3361
|
+
{ cache: true }
|
|
3362
|
+
);
|
|
3363
|
+
if (result.isErr()) {
|
|
3364
|
+
return neverthrow.err(result.error);
|
|
3365
|
+
}
|
|
3366
|
+
if (!result.value.availablePaymentMethods) {
|
|
3367
|
+
return neverthrow.err(new errors.NotFoundError("Payment methods not available"));
|
|
3368
|
+
}
|
|
3369
|
+
return neverthrow.ok(result.value.availablePaymentMethods.map(mapPaymentMethod));
|
|
3370
|
+
}
|
|
2851
3371
|
};
|
|
2852
3372
|
}
|
|
2853
|
-
function
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
context.utmMedium = params.get("utm_medium");
|
|
2864
|
-
context.utmCampaign = params.get("utm_campaign");
|
|
2865
|
-
context.utmTerm = params.get("utm_term");
|
|
2866
|
-
context.utmContent = params.get("utm_content");
|
|
2867
|
-
context.storeSlug = parseStoreSlugFromBase(pathname) ?? parseStoreSlugFromHostname(hostname) ?? void 0;
|
|
2868
|
-
context.deviceType = device.deviceType;
|
|
2869
|
-
context.deviceOs = device.deviceOs;
|
|
2870
|
-
context.deviceBrowser = device.deviceBrowser;
|
|
3373
|
+
function mapDisplayIntent(raw) {
|
|
3374
|
+
switch (raw) {
|
|
3375
|
+
case "ACCORDION":
|
|
3376
|
+
return "ACCORDION";
|
|
3377
|
+
case "SPECIFICATIONS":
|
|
3378
|
+
return "SPECIFICATIONS";
|
|
3379
|
+
case "BOTH":
|
|
3380
|
+
return "BOTH";
|
|
3381
|
+
default:
|
|
3382
|
+
return "ACCORDION";
|
|
2871
3383
|
}
|
|
2872
|
-
|
|
2873
|
-
|
|
3384
|
+
}
|
|
3385
|
+
function mapDetailSection(raw) {
|
|
3386
|
+
const base = {
|
|
3387
|
+
id: raw.id,
|
|
3388
|
+
title: raw.title,
|
|
3389
|
+
displayIntent: mapDisplayIntent(raw.displayIntent),
|
|
3390
|
+
position: raw.position
|
|
3391
|
+
};
|
|
3392
|
+
switch (raw.sectionType) {
|
|
3393
|
+
case "RICH_TEXT":
|
|
3394
|
+
return { ...base, sectionType: "RICH_TEXT", content: { html: raw.content.html } };
|
|
3395
|
+
case "BULLET_LIST":
|
|
3396
|
+
return { ...base, sectionType: "BULLET_LIST", content: { items: raw.content.items } };
|
|
3397
|
+
case "TABLE":
|
|
3398
|
+
return { ...base, sectionType: "TABLE", content: { rows: raw.content.rows } };
|
|
3399
|
+
default:
|
|
3400
|
+
return { ...base, sectionType: "RICH_TEXT", content: { html: "" } };
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
function mapProduct(raw) {
|
|
3404
|
+
return { ...raw, detailSections: raw.detailSections.map(mapDetailSection) };
|
|
3405
|
+
}
|
|
3406
|
+
const PRODUCTS_QUERY = `
|
|
3407
|
+
query Products($first: Int, $after: String, $filter: ProductFilter, $sort: ProductSortKey) {
|
|
3408
|
+
products(first: $first, after: $after, filter: $filter, sort: $sort) {
|
|
3409
|
+
edges {
|
|
3410
|
+
node {
|
|
3411
|
+
id
|
|
3412
|
+
handle
|
|
3413
|
+
title
|
|
3414
|
+
description
|
|
3415
|
+
vendor
|
|
3416
|
+
productType
|
|
3417
|
+
metaTitle
|
|
3418
|
+
metaDescription
|
|
3419
|
+
publishedAt
|
|
3420
|
+
createdAt
|
|
3421
|
+
availableForSale
|
|
3422
|
+
media {
|
|
3423
|
+
id
|
|
3424
|
+
url
|
|
3425
|
+
altText
|
|
3426
|
+
position
|
|
3427
|
+
}
|
|
3428
|
+
options {
|
|
3429
|
+
id
|
|
3430
|
+
name
|
|
3431
|
+
position
|
|
3432
|
+
values {
|
|
3433
|
+
id
|
|
3434
|
+
value
|
|
3435
|
+
position
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
variants {
|
|
3439
|
+
id
|
|
3440
|
+
title
|
|
3441
|
+
sku
|
|
3442
|
+
price
|
|
3443
|
+
compareAtPrice
|
|
3444
|
+
weight
|
|
3445
|
+
weightUnit
|
|
3446
|
+
requiresShipping
|
|
3447
|
+
availableForSale
|
|
3448
|
+
quantity
|
|
3449
|
+
selectedOptions {
|
|
3450
|
+
id
|
|
3451
|
+
value
|
|
3452
|
+
position
|
|
3453
|
+
}
|
|
3454
|
+
image {
|
|
3455
|
+
id
|
|
3456
|
+
url
|
|
3457
|
+
altText
|
|
3458
|
+
position
|
|
3459
|
+
}
|
|
3460
|
+
isOnSale
|
|
3461
|
+
quantityPricing {
|
|
3462
|
+
minQuantity
|
|
3463
|
+
price
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
categories {
|
|
3467
|
+
id
|
|
3468
|
+
name
|
|
3469
|
+
handle
|
|
3470
|
+
description
|
|
3471
|
+
}
|
|
3472
|
+
primaryCategory {
|
|
3473
|
+
id
|
|
3474
|
+
name
|
|
3475
|
+
handle
|
|
3476
|
+
}
|
|
3477
|
+
tags {
|
|
3478
|
+
id
|
|
3479
|
+
name
|
|
3480
|
+
}
|
|
3481
|
+
detailSections {
|
|
3482
|
+
id
|
|
3483
|
+
title
|
|
3484
|
+
sectionType
|
|
3485
|
+
content
|
|
3486
|
+
displayIntent
|
|
3487
|
+
position
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
cursor
|
|
3491
|
+
}
|
|
3492
|
+
pageInfo {
|
|
3493
|
+
hasNextPage
|
|
3494
|
+
hasPreviousPage
|
|
3495
|
+
startCursor
|
|
3496
|
+
endCursor
|
|
3497
|
+
}
|
|
2874
3498
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
3499
|
+
}
|
|
3500
|
+
`;
|
|
3501
|
+
const PRODUCT_BY_ID_QUERY = `
|
|
3502
|
+
query ProductById($id: GID!) {
|
|
3503
|
+
product(id: $id) {
|
|
3504
|
+
id
|
|
3505
|
+
handle
|
|
3506
|
+
title
|
|
3507
|
+
description
|
|
3508
|
+
vendor
|
|
3509
|
+
productType
|
|
3510
|
+
metaTitle
|
|
3511
|
+
metaDescription
|
|
3512
|
+
publishedAt
|
|
3513
|
+
createdAt
|
|
3514
|
+
availableForSale
|
|
3515
|
+
media {
|
|
3516
|
+
id
|
|
3517
|
+
url
|
|
3518
|
+
altText
|
|
3519
|
+
position
|
|
3520
|
+
}
|
|
3521
|
+
options {
|
|
3522
|
+
id
|
|
3523
|
+
name
|
|
3524
|
+
position
|
|
3525
|
+
values {
|
|
3526
|
+
id
|
|
3527
|
+
value
|
|
3528
|
+
position
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
variants {
|
|
3532
|
+
id
|
|
3533
|
+
title
|
|
3534
|
+
sku
|
|
3535
|
+
price
|
|
3536
|
+
compareAtPrice
|
|
3537
|
+
weight
|
|
3538
|
+
weightUnit
|
|
3539
|
+
requiresShipping
|
|
3540
|
+
availableForSale
|
|
3541
|
+
quantity
|
|
3542
|
+
selectedOptions {
|
|
3543
|
+
id
|
|
3544
|
+
value
|
|
3545
|
+
position
|
|
3546
|
+
}
|
|
3547
|
+
image {
|
|
3548
|
+
id
|
|
3549
|
+
url
|
|
3550
|
+
altText
|
|
3551
|
+
position
|
|
3552
|
+
}
|
|
3553
|
+
isOnSale
|
|
3554
|
+
quantityPricing {
|
|
3555
|
+
minQuantity
|
|
3556
|
+
price
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
categories {
|
|
3560
|
+
id
|
|
3561
|
+
name
|
|
3562
|
+
handle
|
|
3563
|
+
description
|
|
3564
|
+
}
|
|
3565
|
+
tags {
|
|
3566
|
+
id
|
|
3567
|
+
name
|
|
3568
|
+
}
|
|
3569
|
+
detailSections {
|
|
3570
|
+
id
|
|
3571
|
+
title
|
|
3572
|
+
sectionType
|
|
3573
|
+
content
|
|
3574
|
+
displayIntent
|
|
3575
|
+
position
|
|
3576
|
+
}
|
|
2877
3577
|
}
|
|
2878
|
-
context.deviceOs ??= null;
|
|
2879
|
-
context.deviceBrowser ??= null;
|
|
2880
|
-
return context;
|
|
2881
|
-
}
|
|
2882
|
-
function normalizeEventContext(context, storeFallback) {
|
|
2883
|
-
const defaults = buildDefaultContext(storeFallback);
|
|
2884
|
-
const merged = { ...defaults, ...context };
|
|
2885
|
-
return {
|
|
2886
|
-
context: merged,
|
|
2887
|
-
storeSlug: merged.storeSlug ?? null
|
|
2888
|
-
};
|
|
2889
|
-
}
|
|
2890
|
-
function isIngestResponse(value) {
|
|
2891
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2892
|
-
const response = value;
|
|
2893
|
-
return typeof response.acceptedCount === "number" && typeof response.duplicateCount === "number" && typeof response.rejectedCount === "number" && Array.isArray(response.errors);
|
|
2894
3578
|
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
3579
|
+
`;
|
|
3580
|
+
const PRODUCT_BY_HANDLE_QUERY = `
|
|
3581
|
+
query ProductByHandle($handle: String!) {
|
|
3582
|
+
product(handle: $handle) {
|
|
3583
|
+
id
|
|
3584
|
+
handle
|
|
3585
|
+
title
|
|
3586
|
+
description
|
|
3587
|
+
vendor
|
|
3588
|
+
productType
|
|
3589
|
+
metaTitle
|
|
3590
|
+
metaDescription
|
|
3591
|
+
publishedAt
|
|
3592
|
+
createdAt
|
|
3593
|
+
availableForSale
|
|
3594
|
+
media {
|
|
3595
|
+
id
|
|
3596
|
+
url
|
|
3597
|
+
altText
|
|
3598
|
+
position
|
|
2899
3599
|
}
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
3600
|
+
options {
|
|
3601
|
+
id
|
|
3602
|
+
name
|
|
3603
|
+
position
|
|
3604
|
+
values {
|
|
3605
|
+
id
|
|
3606
|
+
value
|
|
3607
|
+
position
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
variants {
|
|
3611
|
+
id
|
|
3612
|
+
title
|
|
3613
|
+
sku
|
|
3614
|
+
price
|
|
3615
|
+
compareAtPrice
|
|
3616
|
+
weight
|
|
3617
|
+
weightUnit
|
|
3618
|
+
requiresShipping
|
|
3619
|
+
availableForSale
|
|
3620
|
+
quantity
|
|
3621
|
+
selectedOptions {
|
|
3622
|
+
id
|
|
3623
|
+
value
|
|
3624
|
+
position
|
|
3625
|
+
}
|
|
3626
|
+
image {
|
|
3627
|
+
id
|
|
3628
|
+
url
|
|
3629
|
+
altText
|
|
3630
|
+
position
|
|
3631
|
+
}
|
|
3632
|
+
isOnSale
|
|
3633
|
+
quantityPricing {
|
|
3634
|
+
minQuantity
|
|
3635
|
+
price
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
categories {
|
|
3639
|
+
id
|
|
3640
|
+
name
|
|
3641
|
+
handle
|
|
3642
|
+
description
|
|
3643
|
+
}
|
|
3644
|
+
tags {
|
|
3645
|
+
id
|
|
3646
|
+
name
|
|
3647
|
+
}
|
|
3648
|
+
detailSections {
|
|
3649
|
+
id
|
|
3650
|
+
title
|
|
3651
|
+
sectionType
|
|
3652
|
+
content
|
|
3653
|
+
displayIntent
|
|
3654
|
+
position
|
|
2903
3655
|
}
|
|
2904
|
-
return `Analytics ingest failed with status ${response.status}`;
|
|
2905
|
-
}).catch(() => `Analytics ingest failed with status ${response.status}`);
|
|
2906
|
-
}
|
|
2907
|
-
function isPlainObject(value) {
|
|
2908
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2909
|
-
}
|
|
2910
|
-
function resolveTrackEvent(eventName) {
|
|
2911
|
-
const normalized = eventName.startsWith("analytics.") ? eventName.slice("analytics.".length) : eventName;
|
|
2912
|
-
if (normalized in ANALYTICS_PRESET_EVENT_MAP) {
|
|
2913
|
-
return { eventType: ANALYTICS_PRESET_EVENT_MAP[normalized] };
|
|
2914
3656
|
}
|
|
2915
|
-
return {
|
|
2916
|
-
eventType: "analytics.custom",
|
|
2917
|
-
customEventName: eventName
|
|
2918
|
-
};
|
|
2919
3657
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
3658
|
+
`;
|
|
3659
|
+
const PRODUCTS_BY_HANDLES_QUERY = `
|
|
3660
|
+
query ProductsByHandles($handles: [String!]!) {
|
|
3661
|
+
productsByHandles(handles: $handles) {
|
|
3662
|
+
id
|
|
3663
|
+
handle
|
|
3664
|
+
title
|
|
3665
|
+
description
|
|
3666
|
+
vendor
|
|
3667
|
+
productType
|
|
3668
|
+
metaTitle
|
|
3669
|
+
metaDescription
|
|
3670
|
+
publishedAt
|
|
3671
|
+
createdAt
|
|
3672
|
+
availableForSale
|
|
3673
|
+
media {
|
|
3674
|
+
id
|
|
3675
|
+
url
|
|
3676
|
+
altText
|
|
3677
|
+
position
|
|
2934
3678
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
3679
|
+
options {
|
|
3680
|
+
id
|
|
3681
|
+
name
|
|
3682
|
+
position
|
|
3683
|
+
values {
|
|
3684
|
+
id
|
|
3685
|
+
value
|
|
3686
|
+
position
|
|
3687
|
+
}
|
|
2940
3688
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
},
|
|
2957
|
-
referrer: eventContext.referrer ?? null,
|
|
2958
|
-
utm: buildUtmPayload(eventContext),
|
|
2959
|
-
clickIds: buildClickIdsPayload(eventContext),
|
|
2960
|
-
device: {
|
|
2961
|
-
deviceType: eventContext.deviceType ?? "unknown",
|
|
2962
|
-
deviceOs: eventContext.deviceOs ?? null,
|
|
2963
|
-
deviceBrowser: eventContext.deviceBrowser ?? null
|
|
2964
|
-
},
|
|
2965
|
-
properties
|
|
2966
|
-
};
|
|
2967
|
-
const payload = {
|
|
2968
|
-
storeSlug,
|
|
2969
|
-
events: [event]
|
|
2970
|
-
};
|
|
2971
|
-
try {
|
|
2972
|
-
const response = await fetch(endpoint, {
|
|
2973
|
-
method: "POST",
|
|
2974
|
-
headers: {
|
|
2975
|
-
"content-type": "application/json"
|
|
2976
|
-
},
|
|
2977
|
-
body: JSON.stringify(payload)
|
|
2978
|
-
});
|
|
2979
|
-
if (!response.ok) {
|
|
2980
|
-
const message = response.headers.get("content-type")?.includes("application/json") ? await extractErrorMessage(response) : `Analytics ingest failed with status ${response.status}`;
|
|
2981
|
-
return neverthrow.err(new errors.NetworkError(message));
|
|
3689
|
+
variants {
|
|
3690
|
+
id
|
|
3691
|
+
title
|
|
3692
|
+
sku
|
|
3693
|
+
price
|
|
3694
|
+
compareAtPrice
|
|
3695
|
+
weight
|
|
3696
|
+
weightUnit
|
|
3697
|
+
requiresShipping
|
|
3698
|
+
availableForSale
|
|
3699
|
+
quantity
|
|
3700
|
+
selectedOptions {
|
|
3701
|
+
id
|
|
3702
|
+
value
|
|
3703
|
+
position
|
|
2982
3704
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3705
|
+
image {
|
|
3706
|
+
id
|
|
3707
|
+
url
|
|
3708
|
+
altText
|
|
3709
|
+
position
|
|
2986
3710
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
3711
|
+
isOnSale
|
|
3712
|
+
quantityPricing {
|
|
3713
|
+
minQuantity
|
|
3714
|
+
price
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
categories {
|
|
3718
|
+
id
|
|
3719
|
+
name
|
|
3720
|
+
handle
|
|
3721
|
+
description
|
|
3722
|
+
}
|
|
3723
|
+
tags {
|
|
3724
|
+
id
|
|
3725
|
+
name
|
|
3726
|
+
}
|
|
3727
|
+
detailSections {
|
|
3728
|
+
id
|
|
3729
|
+
title
|
|
3730
|
+
sectionType
|
|
3731
|
+
content
|
|
3732
|
+
displayIntent
|
|
3733
|
+
position
|
|
2992
3734
|
}
|
|
2993
3735
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
const payload = eventPayload;
|
|
3007
|
-
const decodedProductId = decodeAnalyticsEntityId(payload.productId);
|
|
3008
|
-
if (!decodedProductId) {
|
|
3009
|
-
return neverthrow.err(new errors.NetworkError("Invalid productId"));
|
|
3010
|
-
}
|
|
3011
|
-
const decodedVariantId = decodeAnalyticsEntityId(payload.variantId);
|
|
3012
|
-
return sendEvent(
|
|
3013
|
-
"analytics.product_view",
|
|
3014
|
-
{ productId: decodedProductId, variantId: decodedVariantId },
|
|
3015
|
-
context
|
|
3016
|
-
);
|
|
3017
|
-
}
|
|
3018
|
-
case "analytics.collection_view": {
|
|
3019
|
-
if (!isPlainObject(eventPayload)) {
|
|
3020
|
-
return neverthrow.err(new errors.NetworkError("collectionId is required"));
|
|
3021
|
-
}
|
|
3022
|
-
const payload = eventPayload;
|
|
3023
|
-
const decodedCollectionId = decodeAnalyticsEntityId(payload.collectionId);
|
|
3024
|
-
if (!decodedCollectionId) {
|
|
3025
|
-
return neverthrow.err(new errors.NetworkError("Invalid collectionId"));
|
|
3026
|
-
}
|
|
3027
|
-
return sendEvent("analytics.collection_view", { collectionId: decodedCollectionId }, context);
|
|
3028
|
-
}
|
|
3029
|
-
case "analytics.search_performed": {
|
|
3030
|
-
if (!isPlainObject(eventPayload)) {
|
|
3031
|
-
return neverthrow.err(new errors.NetworkError("query is required"));
|
|
3032
|
-
}
|
|
3033
|
-
const payload = eventPayload;
|
|
3034
|
-
const trimmed = payload.query.trim();
|
|
3035
|
-
if (!trimmed) {
|
|
3036
|
-
return neverthrow.err(new errors.NetworkError("query is required"));
|
|
3037
|
-
}
|
|
3038
|
-
return sendEvent(
|
|
3039
|
-
"analytics.search_performed",
|
|
3040
|
-
{ query: trimmed, resultsCount: Math.max(0, Math.floor(payload.resultsCount)) },
|
|
3041
|
-
context
|
|
3042
|
-
);
|
|
3043
|
-
}
|
|
3044
|
-
case "analytics.add_to_cart": {
|
|
3045
|
-
if (!isPlainObject(eventPayload)) {
|
|
3046
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3047
|
-
}
|
|
3048
|
-
const payload = eventPayload;
|
|
3049
|
-
const cartId = decodeAnalyticsEntityId(payload.cartId);
|
|
3050
|
-
if (!cartId) {
|
|
3051
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3052
|
-
}
|
|
3053
|
-
return sendEvent(
|
|
3054
|
-
"analytics.add_to_cart",
|
|
3055
|
-
{
|
|
3056
|
-
cartId,
|
|
3057
|
-
quantity: Math.max(1, Math.floor(payload.quantity)),
|
|
3058
|
-
itemsCount: Math.max(0, Math.floor(payload.itemsCount)),
|
|
3059
|
-
cartValueCents: toCents(payload.cartValue)
|
|
3060
|
-
},
|
|
3061
|
-
context
|
|
3062
|
-
);
|
|
3063
|
-
}
|
|
3064
|
-
case "analytics.remove_from_cart": {
|
|
3065
|
-
if (!isPlainObject(eventPayload)) {
|
|
3066
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3067
|
-
}
|
|
3068
|
-
const payload = eventPayload;
|
|
3069
|
-
const cartId = decodeAnalyticsEntityId(payload.cartId);
|
|
3070
|
-
if (!cartId) {
|
|
3071
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3072
|
-
}
|
|
3073
|
-
return sendEvent(
|
|
3074
|
-
"analytics.remove_from_cart",
|
|
3075
|
-
{
|
|
3076
|
-
cartId,
|
|
3077
|
-
quantity: Math.max(1, Math.floor(payload.quantity)),
|
|
3078
|
-
itemsCount: Math.max(0, Math.floor(payload.itemsCount)),
|
|
3079
|
-
cartValueCents: toCents(payload.cartValue)
|
|
3080
|
-
},
|
|
3081
|
-
context
|
|
3082
|
-
);
|
|
3083
|
-
}
|
|
3084
|
-
case "analytics.checkout_started": {
|
|
3085
|
-
if (!isPlainObject(eventPayload)) {
|
|
3086
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3087
|
-
}
|
|
3088
|
-
const payload = eventPayload;
|
|
3089
|
-
const decodedCartId = decodeAnalyticsEntityId(payload.cartId);
|
|
3090
|
-
if (!decodedCartId) {
|
|
3091
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3092
|
-
}
|
|
3093
|
-
return sendEvent("analytics.checkout_started", { cartId: decodedCartId }, context);
|
|
3094
|
-
}
|
|
3095
|
-
case "analytics.checkout_step_completed": {
|
|
3096
|
-
if (!isPlainObject(eventPayload)) {
|
|
3097
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3098
|
-
}
|
|
3099
|
-
const payload = eventPayload;
|
|
3100
|
-
const decodedCartId = decodeAnalyticsEntityId(payload.cartId);
|
|
3101
|
-
if (!decodedCartId) {
|
|
3102
|
-
return neverthrow.err(new errors.NetworkError("Invalid cartId"));
|
|
3103
|
-
}
|
|
3104
|
-
return sendEvent("analytics.checkout_step_completed", { cartId: decodedCartId, step: payload.step }, context);
|
|
3736
|
+
}
|
|
3737
|
+
`;
|
|
3738
|
+
function createProductsOperations(client) {
|
|
3739
|
+
return {
|
|
3740
|
+
async list(options) {
|
|
3741
|
+
const result = await client.query({
|
|
3742
|
+
query: PRODUCTS_QUERY,
|
|
3743
|
+
variables: {
|
|
3744
|
+
first: options?.first,
|
|
3745
|
+
after: options?.after,
|
|
3746
|
+
filter: options?.filter,
|
|
3747
|
+
sort: options?.sort
|
|
3105
3748
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
{
|
|
3119
|
-
orderId,
|
|
3120
|
-
cartId,
|
|
3121
|
-
orderTotalCents: toCents(payload.orderTotal)
|
|
3122
|
-
},
|
|
3123
|
-
context
|
|
3124
|
-
);
|
|
3749
|
+
});
|
|
3750
|
+
if (result.isErr()) {
|
|
3751
|
+
return neverthrow.err(result.error);
|
|
3752
|
+
}
|
|
3753
|
+
const connection = result.value.products;
|
|
3754
|
+
return neverthrow.ok({
|
|
3755
|
+
items: connection.edges.map((edge) => normalizeProductAssetUrls(mapProduct(edge.node), client.config.endpoint)),
|
|
3756
|
+
pageInfo: {
|
|
3757
|
+
hasNextPage: connection.pageInfo.hasNextPage,
|
|
3758
|
+
hasPreviousPage: connection.pageInfo.hasPreviousPage,
|
|
3759
|
+
startCursor: connection.pageInfo.startCursor,
|
|
3760
|
+
endCursor: connection.pageInfo.endCursor
|
|
3125
3761
|
}
|
|
3762
|
+
});
|
|
3763
|
+
},
|
|
3764
|
+
async get(idOrHandle) {
|
|
3765
|
+
const useId = isGlobalId(idOrHandle);
|
|
3766
|
+
const result = await client.query({
|
|
3767
|
+
query: useId ? PRODUCT_BY_ID_QUERY : PRODUCT_BY_HANDLE_QUERY,
|
|
3768
|
+
variables: useId ? { id: idOrHandle } : { handle: idOrHandle }
|
|
3769
|
+
});
|
|
3770
|
+
if (result.isErr()) {
|
|
3771
|
+
return neverthrow.err(result.error);
|
|
3772
|
+
}
|
|
3773
|
+
if (!result.value.product) {
|
|
3774
|
+
return neverthrow.err(new errors.NotFoundError(`Product not found: ${idOrHandle}`));
|
|
3775
|
+
}
|
|
3776
|
+
return neverthrow.ok(normalizeProductAssetUrls(mapProduct(result.value.product), client.config.endpoint));
|
|
3777
|
+
},
|
|
3778
|
+
async getByHandles(handles) {
|
|
3779
|
+
if (handles.length === 0) {
|
|
3780
|
+
return neverthrow.ok([]);
|
|
3781
|
+
}
|
|
3782
|
+
const result = await client.query({
|
|
3783
|
+
query: PRODUCTS_BY_HANDLES_QUERY,
|
|
3784
|
+
variables: { handles }
|
|
3785
|
+
});
|
|
3786
|
+
if (result.isErr()) {
|
|
3787
|
+
return neverthrow.err(result.error);
|
|
3126
3788
|
}
|
|
3789
|
+
return neverthrow.ok(
|
|
3790
|
+
result.value.productsByHandles.map(
|
|
3791
|
+
(product) => product ? normalizeProductAssetUrls(mapProduct(product), client.config.endpoint) : null
|
|
3792
|
+
)
|
|
3793
|
+
);
|
|
3127
3794
|
}
|
|
3128
|
-
const properties = isPlainObject(eventPayload) ? eventPayload : {};
|
|
3129
|
-
return sendEvent(
|
|
3130
|
-
"analytics.custom",
|
|
3131
|
-
{ ...properties, eventName: normalized.customEventName ?? eventName },
|
|
3132
|
-
context
|
|
3133
|
-
);
|
|
3134
|
-
}
|
|
3135
|
-
return {
|
|
3136
|
-
track
|
|
3137
3795
|
};
|
|
3138
3796
|
}
|
|
3139
3797
|
const AVAILABLE_SHIPPING_RATES_QUERY = `
|
|
@@ -3177,6 +3835,53 @@ function createShippingOperations(client) {
|
|
|
3177
3835
|
}
|
|
3178
3836
|
};
|
|
3179
3837
|
}
|
|
3838
|
+
const STORE_QUERY = `
|
|
3839
|
+
query Store {
|
|
3840
|
+
store {
|
|
3841
|
+
id
|
|
3842
|
+
name
|
|
3843
|
+
slug
|
|
3844
|
+
description
|
|
3845
|
+
currency
|
|
3846
|
+
timezone
|
|
3847
|
+
logoUrl
|
|
3848
|
+
contactEmail
|
|
3849
|
+
contactPhone
|
|
3850
|
+
trackingDispatchOnUnknownConsent
|
|
3851
|
+
browserTrackingConfig {
|
|
3852
|
+
gtm {
|
|
3853
|
+
enabled
|
|
3854
|
+
containerId
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
analytics {
|
|
3858
|
+
enabled
|
|
3859
|
+
dispatchOnUnknownConsent
|
|
3860
|
+
gtm {
|
|
3861
|
+
enabled
|
|
3862
|
+
containerId
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
socialLinks
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
`;
|
|
3869
|
+
function createStoreOperations(client) {
|
|
3870
|
+
return {
|
|
3871
|
+
async get(options) {
|
|
3872
|
+
const result = await client.query(
|
|
3873
|
+
{
|
|
3874
|
+
query: STORE_QUERY
|
|
3875
|
+
},
|
|
3876
|
+
options
|
|
3877
|
+
);
|
|
3878
|
+
if (result.isErr()) {
|
|
3879
|
+
return neverthrow.err(result.error);
|
|
3880
|
+
}
|
|
3881
|
+
return neverthrow.ok(result.value.store);
|
|
3882
|
+
}
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3180
3885
|
const DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
|
|
3181
3886
|
function createStorefrontClient(config) {
|
|
3182
3887
|
const storage = config.storage ?? createDefaultAdapter();
|
|
@@ -3195,6 +3900,8 @@ function createStorefrontClient(config) {
|
|
|
3195
3900
|
_graphql: graphqlClient,
|
|
3196
3901
|
_storage: storage,
|
|
3197
3902
|
_queryCache: queryCache,
|
|
3903
|
+
_analyticsRuntimeConfig: null,
|
|
3904
|
+
_resolvedInitConfig: null,
|
|
3198
3905
|
cache: {
|
|
3199
3906
|
clear() {
|
|
3200
3907
|
queryCache.clear();
|
|
@@ -3202,6 +3909,7 @@ function createStorefrontClient(config) {
|
|
|
3202
3909
|
},
|
|
3203
3910
|
// Placeholder - will be assigned below
|
|
3204
3911
|
products: null,
|
|
3912
|
+
store: null,
|
|
3205
3913
|
collections: null,
|
|
3206
3914
|
categories: null,
|
|
3207
3915
|
cart: null,
|
|
@@ -3211,6 +3919,7 @@ function createStorefrontClient(config) {
|
|
|
3211
3919
|
account: null,
|
|
3212
3920
|
shipping: null,
|
|
3213
3921
|
analytics: null,
|
|
3922
|
+
init: null,
|
|
3214
3923
|
query(request, options) {
|
|
3215
3924
|
return graphqlClient.query(request, options);
|
|
3216
3925
|
},
|
|
@@ -3237,6 +3946,7 @@ function createStorefrontClient(config) {
|
|
|
3237
3946
|
}
|
|
3238
3947
|
};
|
|
3239
3948
|
client.products = createProductsOperations(client);
|
|
3949
|
+
client.store = createStoreOperations(client);
|
|
3240
3950
|
client.collections = createCollectionsOperations(client);
|
|
3241
3951
|
client.categories = createCategoriesOperations(client);
|
|
3242
3952
|
client.cart = createCartOperations(client, storage);
|
|
@@ -3245,9 +3955,325 @@ function createStorefrontClient(config) {
|
|
|
3245
3955
|
client.auth = createAuthOperations(client, storage);
|
|
3246
3956
|
client.account = createAccountOperations(client, storage);
|
|
3247
3957
|
client.shipping = createShippingOperations(client);
|
|
3248
|
-
|
|
3958
|
+
const analytics = createAnalyticsOperations(client, storage);
|
|
3959
|
+
client.analytics = analytics;
|
|
3960
|
+
client.init = async (options) => {
|
|
3961
|
+
client._analyticsRuntimeConfig = options?.analytics ?? null;
|
|
3962
|
+
const storeResult = await client.store.get({ cache: false });
|
|
3963
|
+
if (storeResult.isErr()) {
|
|
3964
|
+
return neverthrow.err(storeResult.error);
|
|
3965
|
+
}
|
|
3966
|
+
const resolvedConfig = {
|
|
3967
|
+
analytics: {
|
|
3968
|
+
enabled: storeResult.value.analytics.enabled,
|
|
3969
|
+
dispatchOnUnknownConsent: storeResult.value.analytics.dispatchOnUnknownConsent,
|
|
3970
|
+
gtm: storeResult.value.analytics.gtm
|
|
3971
|
+
}
|
|
3972
|
+
};
|
|
3973
|
+
client._analyticsRuntimeConfig = {
|
|
3974
|
+
...options?.analytics ?? {},
|
|
3975
|
+
dispatchOnUnknownConsent: resolvedConfig.analytics.dispatchOnUnknownConsent
|
|
3976
|
+
};
|
|
3977
|
+
client._resolvedInitConfig = resolvedConfig;
|
|
3978
|
+
analytics.init(resolvedConfig.analytics);
|
|
3979
|
+
return neverthrow.ok(resolvedConfig);
|
|
3980
|
+
};
|
|
3249
3981
|
return client;
|
|
3250
3982
|
}
|
|
3983
|
+
function getWindowObject(getWindow2) {
|
|
3984
|
+
if (getWindow2) {
|
|
3985
|
+
return getWindow2();
|
|
3986
|
+
}
|
|
3987
|
+
if (typeof window === "undefined") {
|
|
3988
|
+
return null;
|
|
3989
|
+
}
|
|
3990
|
+
return window;
|
|
3991
|
+
}
|
|
3992
|
+
function getNumberPropFromKeys(properties, keys) {
|
|
3993
|
+
for (const key of keys) {
|
|
3994
|
+
const value = properties[key];
|
|
3995
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3996
|
+
return value;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
return null;
|
|
4000
|
+
}
|
|
4001
|
+
const DEFAULT_CURRENCY = "RSD";
|
|
4002
|
+
const DEFAULT_DATA_LAYER_NAME = "dataLayer";
|
|
4003
|
+
const GTM_CONSENT_DEFAULT_STATE = {
|
|
4004
|
+
analytics_storage: "denied",
|
|
4005
|
+
ad_storage: "denied",
|
|
4006
|
+
ad_user_data: "denied",
|
|
4007
|
+
ad_personalization: "denied"
|
|
4008
|
+
};
|
|
4009
|
+
const GTM_TRIGGER_EVENT_MAP = {
|
|
4010
|
+
page_view: "ekomerc.page_view",
|
|
4011
|
+
view_item: "ekomerc.view_item",
|
|
4012
|
+
view_item_list: "ekomerc.view_item_list",
|
|
4013
|
+
search: "ekomerc.search",
|
|
4014
|
+
add_to_cart: "ekomerc.add_to_cart",
|
|
4015
|
+
remove_from_cart: "ekomerc.remove_from_cart",
|
|
4016
|
+
begin_checkout: "ekomerc.begin_checkout",
|
|
4017
|
+
purchase: "ekomerc.purchase"
|
|
4018
|
+
};
|
|
4019
|
+
function isObjectRecord(value) {
|
|
4020
|
+
return typeof value === "object" && value !== null;
|
|
4021
|
+
}
|
|
4022
|
+
function isGtmDataLayer(value) {
|
|
4023
|
+
return isObjectRecord(value) && typeof value.push === "function";
|
|
4024
|
+
}
|
|
4025
|
+
function getStringProp(properties, key) {
|
|
4026
|
+
const value = properties[key];
|
|
4027
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
4028
|
+
}
|
|
4029
|
+
function getNumberProp(properties, key) {
|
|
4030
|
+
const value = properties[key];
|
|
4031
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
4032
|
+
}
|
|
4033
|
+
function centsToCurrencyUnits(valueInCents) {
|
|
4034
|
+
if (valueInCents === null) {
|
|
4035
|
+
return null;
|
|
4036
|
+
}
|
|
4037
|
+
return valueInCents / 100;
|
|
4038
|
+
}
|
|
4039
|
+
function getDinarValue(properties, dinarKeys, centsKeys) {
|
|
4040
|
+
const valueInDinars = getNumberPropFromKeys(properties, dinarKeys);
|
|
4041
|
+
if (valueInDinars !== null) {
|
|
4042
|
+
return valueInDinars;
|
|
4043
|
+
}
|
|
4044
|
+
return centsToCurrencyUnits(getNumberPropFromKeys(properties, centsKeys));
|
|
4045
|
+
}
|
|
4046
|
+
function normalizeQuantity(value) {
|
|
4047
|
+
if (value === null) {
|
|
4048
|
+
return 1;
|
|
4049
|
+
}
|
|
4050
|
+
return Math.max(1, Math.floor(value));
|
|
4051
|
+
}
|
|
4052
|
+
function resolveProductId(properties) {
|
|
4053
|
+
return getStringProp(properties, "productId") ?? getStringProp(properties, "product_id") ?? getStringProp(properties, "contentId") ?? getStringProp(properties, "content_id") ?? getStringProp(properties, "id") ?? getStringProp(properties, "item_id");
|
|
4054
|
+
}
|
|
4055
|
+
function buildItem(properties, currency) {
|
|
4056
|
+
const productId = resolveProductId(properties);
|
|
4057
|
+
if (!productId) {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
const quantity = normalizeQuantity(getNumberProp(properties, "quantity"));
|
|
4061
|
+
const unitPrice = getDinarValue(
|
|
4062
|
+
properties,
|
|
4063
|
+
["unitPrice", "unit_price", "price"],
|
|
4064
|
+
["unitPriceCents", "unit_price_cents", "priceCents"]
|
|
4065
|
+
) ?? 0;
|
|
4066
|
+
const lineTotal = getDinarValue(
|
|
4067
|
+
properties,
|
|
4068
|
+
["lineTotal", "line_total", "total"],
|
|
4069
|
+
["lineTotalCents", "line_total_cents", "totalCents"]
|
|
4070
|
+
) ?? unitPrice * quantity;
|
|
4071
|
+
const item = {
|
|
4072
|
+
product_id: productId,
|
|
4073
|
+
quantity,
|
|
4074
|
+
unit_price: unitPrice,
|
|
4075
|
+
line_total: lineTotal,
|
|
4076
|
+
currency
|
|
4077
|
+
};
|
|
4078
|
+
const variantId = getStringProp(properties, "variantId") ?? getStringProp(properties, "variant_id");
|
|
4079
|
+
if (variantId) {
|
|
4080
|
+
item.variant_id = variantId;
|
|
4081
|
+
}
|
|
4082
|
+
return item;
|
|
4083
|
+
}
|
|
4084
|
+
function buildNormalizedItems(eventName, properties, currency) {
|
|
4085
|
+
const rawLineItems = properties.lineItems;
|
|
4086
|
+
if (Array.isArray(rawLineItems)) {
|
|
4087
|
+
const items = [];
|
|
4088
|
+
for (const lineItem of rawLineItems) {
|
|
4089
|
+
if (!isObjectRecord(lineItem)) {
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
const mappedItem = buildItem(lineItem, currency);
|
|
4093
|
+
if (mappedItem) {
|
|
4094
|
+
items.push(mappedItem);
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
if (items.length > 0) {
|
|
4098
|
+
return items;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
if (eventName === "view_item" || eventName === "add_to_cart" || eventName === "remove_from_cart" || eventName === "begin_checkout" || eventName === "purchase") {
|
|
4102
|
+
const singleItem = buildItem(properties, currency);
|
|
4103
|
+
return singleItem ? [singleItem] : [];
|
|
4104
|
+
}
|
|
4105
|
+
return [];
|
|
4106
|
+
}
|
|
4107
|
+
function buildAttributionPayload(input) {
|
|
4108
|
+
return {
|
|
4109
|
+
utm_source: input.event.utm.source,
|
|
4110
|
+
utm_medium: input.event.utm.medium,
|
|
4111
|
+
utm_campaign: input.event.utm.campaign,
|
|
4112
|
+
utm_term: input.event.utm.term,
|
|
4113
|
+
utm_content: input.event.utm.content,
|
|
4114
|
+
gclid: input.event.clickIds.gclid,
|
|
4115
|
+
gbraid: input.event.clickIds.gbraid,
|
|
4116
|
+
wbraid: input.event.clickIds.wbraid,
|
|
4117
|
+
fbclid: input.event.clickIds.fbclid,
|
|
4118
|
+
ttclid: input.event.clickIds.ttclid
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
function buildGtmDataLayerEvent(input) {
|
|
4122
|
+
const providerEventName = getTrackingProviderEventName("gtm", input.event.eventType);
|
|
4123
|
+
if (providerEventName === null) {
|
|
4124
|
+
return null;
|
|
4125
|
+
}
|
|
4126
|
+
const eventName = GTM_TRIGGER_EVENT_MAP[providerEventName];
|
|
4127
|
+
const dedupe = getTrackingProviderDedupeConvention("gtm");
|
|
4128
|
+
const ekomerc = {
|
|
4129
|
+
event_name: eventName,
|
|
4130
|
+
event_id: input.event[dedupe.canonicalEventIdField],
|
|
4131
|
+
consent_state: input.event.consentState,
|
|
4132
|
+
context: {
|
|
4133
|
+
path: input.event.context.path,
|
|
4134
|
+
referrer: input.event.referrer
|
|
4135
|
+
},
|
|
4136
|
+
attribution: buildAttributionPayload(input)
|
|
4137
|
+
};
|
|
4138
|
+
if (providerEventName === "search") {
|
|
4139
|
+
const query = getStringProp(input.event.properties, "query");
|
|
4140
|
+
if (query) {
|
|
4141
|
+
ekomerc.search_term = query;
|
|
4142
|
+
}
|
|
4143
|
+
const resultsCount = getNumberProp(input.event.properties, "resultsCount");
|
|
4144
|
+
if (resultsCount !== null) {
|
|
4145
|
+
ekomerc.results_count = resultsCount;
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
if (providerEventName !== "page_view" && providerEventName !== "search") {
|
|
4149
|
+
const currency = getStringProp(input.event.properties, "currency") ?? DEFAULT_CURRENCY;
|
|
4150
|
+
const value = getDinarValue(
|
|
4151
|
+
input.event.properties,
|
|
4152
|
+
["value", "total", "orderTotal", "cartValue"],
|
|
4153
|
+
["priceCents", "cartValueCents", "totalCents", "orderTotalCents"]
|
|
4154
|
+
);
|
|
4155
|
+
const quantity = getNumberProp(input.event.properties, "quantity");
|
|
4156
|
+
const itemsCount = getNumberPropFromKeys(input.event.properties, ["itemsCount", "numItems"]);
|
|
4157
|
+
const items = buildNormalizedItems(providerEventName, input.event.properties, currency);
|
|
4158
|
+
ekomerc.currency = currency;
|
|
4159
|
+
if (value !== null) {
|
|
4160
|
+
ekomerc.value = value;
|
|
4161
|
+
}
|
|
4162
|
+
const cartId = getStringProp(input.event.properties, "cartId");
|
|
4163
|
+
if (cartId) {
|
|
4164
|
+
ekomerc.cart_id = cartId;
|
|
4165
|
+
}
|
|
4166
|
+
const orderId = getStringProp(input.event.properties, "orderId");
|
|
4167
|
+
if (orderId) {
|
|
4168
|
+
ekomerc.order_id = orderId;
|
|
4169
|
+
}
|
|
4170
|
+
if (quantity !== null) {
|
|
4171
|
+
ekomerc.quantity = quantity;
|
|
4172
|
+
}
|
|
4173
|
+
if (itemsCount !== null) {
|
|
4174
|
+
ekomerc.items_count = itemsCount;
|
|
4175
|
+
} else if (items.length > 0) {
|
|
4176
|
+
ekomerc.items_count = items.length;
|
|
4177
|
+
}
|
|
4178
|
+
if (items.length > 0) {
|
|
4179
|
+
ekomerc.items = items;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
return {
|
|
4183
|
+
event: eventName,
|
|
4184
|
+
ekomerc
|
|
4185
|
+
};
|
|
4186
|
+
}
|
|
4187
|
+
function buildGtmConsentModeUpdate({ consentState }) {
|
|
4188
|
+
const consentValue = consentState === "granted" ? "granted" : "denied";
|
|
4189
|
+
return {
|
|
4190
|
+
analytics_storage: consentValue,
|
|
4191
|
+
ad_storage: consentValue,
|
|
4192
|
+
ad_user_data: consentValue,
|
|
4193
|
+
ad_personalization: consentValue
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
function getDataLayer(windowObject, dataLayerName) {
|
|
4197
|
+
const dataLayer = Reflect.get(windowObject, dataLayerName);
|
|
4198
|
+
if (isGtmDataLayer(dataLayer)) {
|
|
4199
|
+
return dataLayer;
|
|
4200
|
+
}
|
|
4201
|
+
if (dataLayer !== void 0 && dataLayer !== null) {
|
|
4202
|
+
return null;
|
|
4203
|
+
}
|
|
4204
|
+
const initializedDataLayer = [];
|
|
4205
|
+
if (!Reflect.set(windowObject, dataLayerName, initializedDataLayer)) {
|
|
4206
|
+
return null;
|
|
4207
|
+
}
|
|
4208
|
+
return initializedDataLayer;
|
|
4209
|
+
}
|
|
4210
|
+
function pushConsentCommand(windowObject, dataLayer, mode, consentUpdate) {
|
|
4211
|
+
const gtag = Reflect.get(windowObject, "gtag");
|
|
4212
|
+
if (typeof gtag === "function") {
|
|
4213
|
+
gtag("consent", mode, consentUpdate);
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
dataLayer.push(["consent", mode, consentUpdate]);
|
|
4217
|
+
}
|
|
4218
|
+
function runtimeUnavailable() {
|
|
4219
|
+
return {
|
|
4220
|
+
status: "skipped",
|
|
4221
|
+
reason: "runtime_unavailable"
|
|
4222
|
+
};
|
|
4223
|
+
}
|
|
4224
|
+
function createGtmBrowserAdapter(config, options = {}) {
|
|
4225
|
+
if (!config.enabled || !config.containerId) {
|
|
4226
|
+
return null;
|
|
4227
|
+
}
|
|
4228
|
+
const dataLayerName = options.dataLayerName ?? DEFAULT_DATA_LAYER_NAME;
|
|
4229
|
+
let consentDefaultsApplied = false;
|
|
4230
|
+
function applyConsentDefaults(windowObject, dataLayer) {
|
|
4231
|
+
if (consentDefaultsApplied) {
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
pushConsentCommand(windowObject, dataLayer, "default", GTM_CONSENT_DEFAULT_STATE);
|
|
4235
|
+
consentDefaultsApplied = true;
|
|
4236
|
+
}
|
|
4237
|
+
return {
|
|
4238
|
+
provider: "gtm",
|
|
4239
|
+
dispatch(input) {
|
|
4240
|
+
const payload = buildGtmDataLayerEvent(input);
|
|
4241
|
+
if (payload === null) {
|
|
4242
|
+
return {
|
|
4243
|
+
status: "success"
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
const windowObject = getWindowObject(options.getWindow);
|
|
4247
|
+
if (!windowObject) {
|
|
4248
|
+
return runtimeUnavailable();
|
|
4249
|
+
}
|
|
4250
|
+
const dataLayer = getDataLayer(windowObject, dataLayerName);
|
|
4251
|
+
if (!dataLayer) {
|
|
4252
|
+
return runtimeUnavailable();
|
|
4253
|
+
}
|
|
4254
|
+
applyConsentDefaults(windowObject, dataLayer);
|
|
4255
|
+
dataLayer.push(payload);
|
|
4256
|
+
return {
|
|
4257
|
+
status: "success"
|
|
4258
|
+
};
|
|
4259
|
+
},
|
|
4260
|
+
updateConsent(input) {
|
|
4261
|
+
const windowObject = getWindowObject(options.getWindow);
|
|
4262
|
+
if (!windowObject) {
|
|
4263
|
+
return runtimeUnavailable();
|
|
4264
|
+
}
|
|
4265
|
+
const dataLayer = getDataLayer(windowObject, dataLayerName);
|
|
4266
|
+
if (!dataLayer) {
|
|
4267
|
+
return runtimeUnavailable();
|
|
4268
|
+
}
|
|
4269
|
+
applyConsentDefaults(windowObject, dataLayer);
|
|
4270
|
+
pushConsentCommand(windowObject, dataLayer, "update", buildGtmConsentModeUpdate(input));
|
|
4271
|
+
return {
|
|
4272
|
+
status: "success"
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
};
|
|
4276
|
+
}
|
|
3251
4277
|
exports.AuthError = errors.AuthError;
|
|
3252
4278
|
exports.GraphQLError = errors.GraphQLError;
|
|
3253
4279
|
exports.NetworkError = errors.NetworkError;
|
|
@@ -3257,7 +4283,11 @@ exports.StorefrontError = errors.StorefrontError;
|
|
|
3257
4283
|
exports.ValidationError = errors.ValidationError;
|
|
3258
4284
|
exports.CART_TOKEN_KEY = CART_TOKEN_KEY;
|
|
3259
4285
|
exports.CUSTOMER_TOKEN_KEY = CUSTOMER_TOKEN_KEY;
|
|
4286
|
+
exports.TRACKING_ATTRIBUTION_QUERY_PARAM_KEYS = TRACKING_ATTRIBUTION_QUERY_PARAM_KEYS;
|
|
4287
|
+
exports.buildGtmConsentModeUpdate = buildGtmConsentModeUpdate;
|
|
4288
|
+
exports.buildGtmDataLayerEvent = buildGtmDataLayerEvent;
|
|
3260
4289
|
exports.createDefaultAdapter = createDefaultAdapter;
|
|
4290
|
+
exports.createGtmBrowserAdapter = createGtmBrowserAdapter;
|
|
3261
4291
|
exports.createLocalStorageAdapter = createLocalStorageAdapter;
|
|
3262
4292
|
exports.createMemoryAdapter = createMemoryAdapter;
|
|
3263
4293
|
exports.createQueryCache = createQueryCache;
|