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