@tiquo/dom-package 1.5.1 → 1.5.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 +29 -1
- package/dist/index.d.mts +93 -24
- package/dist/index.d.ts +93 -24
- package/dist/index.js +500 -80
- package/dist/index.mjs +499 -80
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -641,8 +641,258 @@ function printTiquoBranding() {
|
|
|
641
641
|
}
|
|
642
642
|
}
|
|
643
643
|
printTiquoBranding();
|
|
644
|
+
var ANALYTICS_LOCATION_EVENT = "tiquo:locationchange";
|
|
645
|
+
var analyticsHistoryPatched = false;
|
|
646
|
+
function randomId() {
|
|
647
|
+
try {
|
|
648
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
649
|
+
return crypto.randomUUID();
|
|
650
|
+
}
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
return `ev_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
654
|
+
}
|
|
655
|
+
function normalizeHostname(hostname) {
|
|
656
|
+
if (!hostname) return void 0;
|
|
657
|
+
const clean = hostname.trim().toLowerCase().split(":")[0];
|
|
658
|
+
return clean.startsWith("www.") ? clean.slice(4) : clean;
|
|
659
|
+
}
|
|
660
|
+
function getBrowser() {
|
|
661
|
+
if (typeof navigator === "undefined") return "Other";
|
|
662
|
+
const ua = navigator.userAgent;
|
|
663
|
+
if (/Edg(e|A|iOS)?\//i.test(ua)) return "Edge";
|
|
664
|
+
if (/Firefox\//i.test(ua)) return "Firefox";
|
|
665
|
+
if (/Chrome\/|CriOS\//i.test(ua)) return "Chrome";
|
|
666
|
+
if (/Safari\//i.test(ua) && !/Chrome/i.test(ua)) return "Safari";
|
|
667
|
+
return "Other";
|
|
668
|
+
}
|
|
669
|
+
function getDevice() {
|
|
670
|
+
if (typeof navigator === "undefined") return "Desktop";
|
|
671
|
+
const ua = navigator.userAgent;
|
|
672
|
+
if (/iPad|tablet|PlayBook|Silk/i.test(ua)) return "Tablet";
|
|
673
|
+
if (/Android(?!.*Mobile)/i.test(ua) && /Android/i.test(ua) && !/Mobile/i.test(ua))
|
|
674
|
+
return "Tablet";
|
|
675
|
+
if (/Mobile|iPhone|iPod|Android.*Mobile|webOS|BlackBerry|Opera Mini|IEMobile/i.test(
|
|
676
|
+
ua
|
|
677
|
+
))
|
|
678
|
+
return "Mobile";
|
|
679
|
+
return "Desktop";
|
|
680
|
+
}
|
|
681
|
+
function getOperatingSystem() {
|
|
682
|
+
if (typeof navigator === "undefined") return "Other";
|
|
683
|
+
const ua = navigator.userAgent;
|
|
684
|
+
if (/iPhone|iPad|iPod/i.test(ua)) return "iOS";
|
|
685
|
+
if (/Android/i.test(ua)) return "Android";
|
|
686
|
+
if (/CrOS/i.test(ua)) return "Chrome OS";
|
|
687
|
+
if (/Windows/i.test(ua)) return "Windows";
|
|
688
|
+
if (/Macintosh|Mac OS X/i.test(ua)) return "macOS";
|
|
689
|
+
if (/Linux/i.test(ua)) return "Linux";
|
|
690
|
+
return "Other";
|
|
691
|
+
}
|
|
692
|
+
function getUtmParams() {
|
|
693
|
+
if (typeof window === "undefined") return {};
|
|
694
|
+
const params = new URLSearchParams(window.location.search);
|
|
695
|
+
return {
|
|
696
|
+
source: params.get("utm_source") || void 0,
|
|
697
|
+
medium: params.get("utm_medium") || void 0,
|
|
698
|
+
campaign: params.get("utm_campaign") || void 0,
|
|
699
|
+
content: params.get("utm_content") || void 0,
|
|
700
|
+
term: params.get("utm_term") || void 0
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function patchHistoryForAnalytics() {
|
|
704
|
+
if (analyticsHistoryPatched || typeof window === "undefined") return;
|
|
705
|
+
analyticsHistoryPatched = true;
|
|
706
|
+
const notify = () => {
|
|
707
|
+
window.dispatchEvent(
|
|
708
|
+
new Event(ANALYTICS_LOCATION_EVENT)
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
const pushState = history.pushState;
|
|
712
|
+
history.pushState = function patchedPushState(...args) {
|
|
713
|
+
const result = pushState.apply(this, args);
|
|
714
|
+
notify();
|
|
715
|
+
return result;
|
|
716
|
+
};
|
|
717
|
+
const replaceState = history.replaceState;
|
|
718
|
+
history.replaceState = function patchedReplaceState(...args) {
|
|
719
|
+
const result = replaceState.apply(this, args);
|
|
720
|
+
notify();
|
|
721
|
+
return result;
|
|
722
|
+
};
|
|
723
|
+
window.addEventListener("popstate", notify);
|
|
724
|
+
}
|
|
725
|
+
var _TiquoAnalytics = class _TiquoAnalytics {
|
|
726
|
+
constructor(config) {
|
|
727
|
+
this.pageStartedAt = Date.now();
|
|
728
|
+
this.lastPath = null;
|
|
729
|
+
this.started = false;
|
|
730
|
+
if (!config.publicKey) {
|
|
731
|
+
throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
|
|
732
|
+
}
|
|
733
|
+
if (!config.publicKey.startsWith("pk_dom_")) {
|
|
734
|
+
throw new TiquoAuthError(
|
|
735
|
+
"Invalid public key format. Expected pk_dom_xxx",
|
|
736
|
+
"INVALID_PUBLIC_KEY"
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
this.config = {
|
|
740
|
+
publicKey: config.publicKey,
|
|
741
|
+
apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
|
|
742
|
+
debug: config.debug || false,
|
|
743
|
+
autoTrackPageviews: config.autoTrackPageviews !== false,
|
|
744
|
+
getAccessToken: config.getAccessToken,
|
|
745
|
+
siteSlug: config.siteSlug
|
|
746
|
+
};
|
|
747
|
+
this.clientSessionId = this.getClientSessionId();
|
|
748
|
+
if (this.config.autoTrackPageviews) {
|
|
749
|
+
this.start();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
start() {
|
|
753
|
+
if (this.started || typeof window === "undefined") return;
|
|
754
|
+
const activeKey = `${this.config.publicKey}:${this.config.siteSlug || ""}`;
|
|
755
|
+
const existing = _TiquoAnalytics.activeInstances.get(activeKey);
|
|
756
|
+
if (existing && existing !== this) {
|
|
757
|
+
existing.adoptConfig(this.config);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
_TiquoAnalytics.activeInstances.set(activeKey, this);
|
|
761
|
+
this.started = true;
|
|
762
|
+
patchHistoryForAnalytics();
|
|
763
|
+
this.trackPageview();
|
|
764
|
+
window.addEventListener(
|
|
765
|
+
ANALYTICS_LOCATION_EVENT,
|
|
766
|
+
() => this.trackPageview()
|
|
767
|
+
);
|
|
768
|
+
window.addEventListener("pagehide", () => this.trackEngagement());
|
|
769
|
+
document.addEventListener("visibilitychange", () => {
|
|
770
|
+
if (document.visibilityState === "hidden") this.trackEngagement();
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
async trackPageview(options = {}) {
|
|
774
|
+
if (typeof window === "undefined") return;
|
|
775
|
+
const path = options.path || `${window.location.pathname}${window.location.search}`;
|
|
776
|
+
if (path === this.lastPath) return;
|
|
777
|
+
if (this.lastPath) {
|
|
778
|
+
this.trackEngagement().catch(() => void 0);
|
|
779
|
+
}
|
|
780
|
+
this.lastPath = path;
|
|
781
|
+
this.pageStartedAt = Date.now();
|
|
782
|
+
await this.track("pageview", {
|
|
783
|
+
...options,
|
|
784
|
+
path,
|
|
785
|
+
url: options.url || window.location.href,
|
|
786
|
+
title: options.title || document.title,
|
|
787
|
+
referrer: options.referrer || document.referrer || void 0
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
async track(eventType, options = {}) {
|
|
791
|
+
if (typeof window === "undefined") return;
|
|
792
|
+
const utm = getUtmParams();
|
|
793
|
+
const payload = {
|
|
794
|
+
publicKey: this.config.publicKey,
|
|
795
|
+
eventId: randomId(),
|
|
796
|
+
eventType,
|
|
797
|
+
eventName: options.eventName,
|
|
798
|
+
eventProperties: options.eventProperties,
|
|
799
|
+
clientSessionId: this.clientSessionId,
|
|
800
|
+
siteSlug: options.siteSlug || this.config.siteSlug,
|
|
801
|
+
pageSlug: options.pageSlug,
|
|
802
|
+
hostname: normalizeHostname(window.location.hostname),
|
|
803
|
+
path: options.path || `${window.location.pathname}${window.location.search}`,
|
|
804
|
+
url: options.url || window.location.href,
|
|
805
|
+
title: options.title || document.title,
|
|
806
|
+
referrer: options.referrer || document.referrer || void 0,
|
|
807
|
+
timestamp: Date.now(),
|
|
808
|
+
browser: getBrowser(),
|
|
809
|
+
device: getDevice(),
|
|
810
|
+
operatingSystem: getOperatingSystem(),
|
|
811
|
+
language: navigator.language,
|
|
812
|
+
screenWidth: window.screen?.width,
|
|
813
|
+
screenHeight: window.screen?.height,
|
|
814
|
+
viewportWidth: window.innerWidth,
|
|
815
|
+
viewportHeight: window.innerHeight,
|
|
816
|
+
durationMs: options.durationMs,
|
|
817
|
+
scrollDepth: options.scrollDepth,
|
|
818
|
+
identityLinkType: options.identityLinkType,
|
|
819
|
+
...utm
|
|
820
|
+
};
|
|
821
|
+
await this.send(payload);
|
|
822
|
+
}
|
|
823
|
+
async identify(options = {}) {
|
|
824
|
+
await this.track("identify", {
|
|
825
|
+
...options,
|
|
826
|
+
identityLinkType: options.identityLinkType || "auth_dom_login"
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
async trackEngagement() {
|
|
830
|
+
if (typeof window === "undefined" || !this.lastPath) return;
|
|
831
|
+
const durationMs = Date.now() - this.pageStartedAt;
|
|
832
|
+
const scrollHeight = Math.max(
|
|
833
|
+
document.documentElement.scrollHeight,
|
|
834
|
+
document.body?.scrollHeight || 0,
|
|
835
|
+
window.innerHeight
|
|
836
|
+
);
|
|
837
|
+
const scrollDepth = scrollHeight > 0 ? Math.min(1, (window.scrollY + window.innerHeight) / scrollHeight) : void 0;
|
|
838
|
+
await this.track("engagement", {
|
|
839
|
+
path: this.lastPath,
|
|
840
|
+
durationMs,
|
|
841
|
+
scrollDepth
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
async send(payload) {
|
|
845
|
+
const headers = {
|
|
846
|
+
"Content-Type": "text/plain;charset=UTF-8",
|
|
847
|
+
"X-Public-Key": this.config.publicKey
|
|
848
|
+
};
|
|
849
|
+
const accessToken = this.config.getAccessToken?.();
|
|
850
|
+
if (accessToken) {
|
|
851
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
852
|
+
}
|
|
853
|
+
try {
|
|
854
|
+
await fetch(`${this.config.apiEndpoint}/api/dom-analytics/event`, {
|
|
855
|
+
method: "POST",
|
|
856
|
+
headers,
|
|
857
|
+
credentials: "include",
|
|
858
|
+
keepalive: true,
|
|
859
|
+
body: JSON.stringify(payload)
|
|
860
|
+
});
|
|
861
|
+
} catch (error) {
|
|
862
|
+
this.log("Analytics event failed:", error);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
getClientSessionId() {
|
|
866
|
+
const key = `tiquo_analytics_session_${this.config.publicKey}`;
|
|
867
|
+
try {
|
|
868
|
+
const existing = sessionStorage.getItem(key);
|
|
869
|
+
if (existing) return existing;
|
|
870
|
+
const next = randomId();
|
|
871
|
+
sessionStorage.setItem(key, next);
|
|
872
|
+
return next;
|
|
873
|
+
} catch {
|
|
874
|
+
return randomId();
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
log(...args) {
|
|
878
|
+
if (this.config.debug) {
|
|
879
|
+
console.log("[TiquoAnalytics]", ...args);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
adoptConfig(nextConfig) {
|
|
883
|
+
if (nextConfig.getAccessToken) {
|
|
884
|
+
this.config.getAccessToken = nextConfig.getAccessToken;
|
|
885
|
+
}
|
|
886
|
+
if (nextConfig.siteSlug && !this.config.siteSlug) {
|
|
887
|
+
this.config.siteSlug = nextConfig.siteSlug;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
_TiquoAnalytics.activeInstances = /* @__PURE__ */ new Map();
|
|
892
|
+
var TiquoAnalytics = _TiquoAnalytics;
|
|
644
893
|
var TiquoCMS = class {
|
|
645
894
|
constructor(config) {
|
|
895
|
+
this.analytics = null;
|
|
646
896
|
if (!config.publicKey) {
|
|
647
897
|
throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
|
|
648
898
|
}
|
|
@@ -655,8 +905,16 @@ var TiquoCMS = class {
|
|
|
655
905
|
this.config = {
|
|
656
906
|
publicKey: config.publicKey,
|
|
657
907
|
apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
|
|
658
|
-
debug: config.debug || false
|
|
908
|
+
debug: config.debug || false,
|
|
909
|
+
analytics: config.analytics !== false
|
|
659
910
|
};
|
|
911
|
+
if (this.config.analytics) {
|
|
912
|
+
this.analytics = new TiquoAnalytics({
|
|
913
|
+
publicKey: this.config.publicKey,
|
|
914
|
+
apiEndpoint: this.config.apiEndpoint,
|
|
915
|
+
debug: this.config.debug
|
|
916
|
+
});
|
|
917
|
+
}
|
|
660
918
|
}
|
|
661
919
|
/**
|
|
662
920
|
* Fetch a published CMS page for the website attached to this public key.
|
|
@@ -665,19 +923,22 @@ var TiquoCMS = class {
|
|
|
665
923
|
* Convex connection details to the consuming website.
|
|
666
924
|
*/
|
|
667
925
|
async getPage(request) {
|
|
668
|
-
const response = await fetch(
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
926
|
+
const response = await fetch(
|
|
927
|
+
`${this.config.apiEndpoint}/api/dom-cms/page`,
|
|
928
|
+
{
|
|
929
|
+
method: "POST",
|
|
930
|
+
headers: {
|
|
931
|
+
"Content-Type": "text/plain;charset=UTF-8"
|
|
932
|
+
},
|
|
933
|
+
credentials: "include",
|
|
934
|
+
body: JSON.stringify({
|
|
935
|
+
publicKey: this.config.publicKey,
|
|
936
|
+
siteSlug: request.siteSlug,
|
|
937
|
+
pageSlug: request.pageSlug || "home",
|
|
938
|
+
templateId: request.templateId
|
|
939
|
+
})
|
|
940
|
+
}
|
|
941
|
+
);
|
|
681
942
|
if (response.status === 404) {
|
|
682
943
|
return null;
|
|
683
944
|
}
|
|
@@ -688,7 +949,11 @@ var TiquoCMS = class {
|
|
|
688
949
|
if (typeof error?.error === "string") message = error.error;
|
|
689
950
|
} catch {
|
|
690
951
|
}
|
|
691
|
-
throw new TiquoAuthError(
|
|
952
|
+
throw new TiquoAuthError(
|
|
953
|
+
message,
|
|
954
|
+
"CMS_PAGE_FETCH_FAILED",
|
|
955
|
+
response.status
|
|
956
|
+
);
|
|
692
957
|
}
|
|
693
958
|
const result = await response.json();
|
|
694
959
|
if (!result?.success || !result?.data) {
|
|
@@ -740,6 +1005,7 @@ var TiquoAuth = class {
|
|
|
740
1005
|
this.visibilityHandler = null;
|
|
741
1006
|
this.iframeObserver = null;
|
|
742
1007
|
this.injectedIframes = /* @__PURE__ */ new WeakSet();
|
|
1008
|
+
this.analytics = null;
|
|
743
1009
|
// Multi-tab sync
|
|
744
1010
|
this.broadcastChannel = null;
|
|
745
1011
|
this.isProcessingTabSync = false;
|
|
@@ -760,10 +1026,19 @@ var TiquoAuth = class {
|
|
|
760
1026
|
debug: config.debug || false,
|
|
761
1027
|
enableTabSync: config.enableTabSync !== false,
|
|
762
1028
|
// Default true
|
|
1029
|
+
analytics: config.analytics !== false,
|
|
763
1030
|
accessToken: config.accessToken,
|
|
764
1031
|
refreshToken: config.refreshToken
|
|
765
1032
|
};
|
|
766
1033
|
this.tabId = this.generateTabId();
|
|
1034
|
+
if (this.config.analytics) {
|
|
1035
|
+
this.analytics = new TiquoAnalytics({
|
|
1036
|
+
publicKey: this.config.publicKey,
|
|
1037
|
+
apiEndpoint: this.config.apiEndpoint,
|
|
1038
|
+
debug: this.config.debug,
|
|
1039
|
+
getAccessToken: () => this.accessToken
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
767
1042
|
if (this.config.enableTabSync) {
|
|
768
1043
|
this.initTabSync();
|
|
769
1044
|
}
|
|
@@ -789,7 +1064,11 @@ var TiquoAuth = class {
|
|
|
789
1064
|
});
|
|
790
1065
|
if (!response.ok) {
|
|
791
1066
|
const error = await response.json().catch(() => ({ error: "Failed to send OTP" }));
|
|
792
|
-
throw new TiquoAuthError(
|
|
1067
|
+
throw new TiquoAuthError(
|
|
1068
|
+
error.error || "Failed to send OTP",
|
|
1069
|
+
"OTP_SEND_FAILED",
|
|
1070
|
+
response.status
|
|
1071
|
+
);
|
|
793
1072
|
}
|
|
794
1073
|
const result = await response.json();
|
|
795
1074
|
return {
|
|
@@ -812,7 +1091,11 @@ var TiquoAuth = class {
|
|
|
812
1091
|
});
|
|
813
1092
|
if (!response.ok) {
|
|
814
1093
|
const error = await response.json().catch(() => ({ error: "Invalid OTP" }));
|
|
815
|
-
throw new TiquoAuthError(
|
|
1094
|
+
throw new TiquoAuthError(
|
|
1095
|
+
error.error || "Invalid OTP",
|
|
1096
|
+
"OTP_VERIFY_FAILED",
|
|
1097
|
+
response.status
|
|
1098
|
+
);
|
|
816
1099
|
}
|
|
817
1100
|
const result = await response.json();
|
|
818
1101
|
this.accessToken = result.accessToken;
|
|
@@ -822,6 +1105,7 @@ var TiquoAuth = class {
|
|
|
822
1105
|
if (this.session?.user?.id) {
|
|
823
1106
|
addCustomerUserId(this.session.user.id);
|
|
824
1107
|
}
|
|
1108
|
+
this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
|
|
825
1109
|
this.broadcastTabSync("LOGIN");
|
|
826
1110
|
return {
|
|
827
1111
|
success: true,
|
|
@@ -869,7 +1153,9 @@ var TiquoAuth = class {
|
|
|
869
1153
|
async updateProfile(updates) {
|
|
870
1154
|
await this.ensureValidToken();
|
|
871
1155
|
const normalizedUpdates = { ...updates };
|
|
872
|
-
const profilePhotoUpload = this.extractUploadableProfilePhoto(
|
|
1156
|
+
const profilePhotoUpload = this.extractUploadableProfilePhoto(
|
|
1157
|
+
normalizedUpdates.profilePhoto
|
|
1158
|
+
);
|
|
873
1159
|
if (profilePhotoUpload) {
|
|
874
1160
|
delete normalizedUpdates.profilePhoto;
|
|
875
1161
|
}
|
|
@@ -877,7 +1163,12 @@ var TiquoAuth = class {
|
|
|
877
1163
|
const normalized = normalizePhone(normalizedUpdates.phone);
|
|
878
1164
|
const validation = validatePhone(normalizedUpdates.phone);
|
|
879
1165
|
if (!validation.valid) {
|
|
880
|
-
this.log(
|
|
1166
|
+
this.log(
|
|
1167
|
+
"\u26A0\uFE0F Phone validation warning:",
|
|
1168
|
+
validation.reason,
|
|
1169
|
+
"- Original:",
|
|
1170
|
+
normalizedUpdates.phone
|
|
1171
|
+
);
|
|
881
1172
|
} else {
|
|
882
1173
|
this.log("Phone normalized:", normalizedUpdates.phone, "\u2192", normalized);
|
|
883
1174
|
}
|
|
@@ -889,7 +1180,12 @@ var TiquoAuth = class {
|
|
|
889
1180
|
const normalized = normalizePhone(p.number);
|
|
890
1181
|
const validation = validatePhone(p.number);
|
|
891
1182
|
if (!validation.valid) {
|
|
892
|
-
this.log(
|
|
1183
|
+
this.log(
|
|
1184
|
+
"\u26A0\uFE0F Phone validation warning:",
|
|
1185
|
+
validation.reason,
|
|
1186
|
+
"- Original:",
|
|
1187
|
+
p.number
|
|
1188
|
+
);
|
|
893
1189
|
}
|
|
894
1190
|
return { ...p, number: normalized || p.number };
|
|
895
1191
|
});
|
|
@@ -903,7 +1199,11 @@ var TiquoAuth = class {
|
|
|
903
1199
|
});
|
|
904
1200
|
if (!response.ok) {
|
|
905
1201
|
const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
|
|
906
|
-
throw new TiquoAuthError(
|
|
1202
|
+
throw new TiquoAuthError(
|
|
1203
|
+
error.error || "Failed to update profile",
|
|
1204
|
+
"PROFILE_UPDATE_FAILED",
|
|
1205
|
+
response.status
|
|
1206
|
+
);
|
|
907
1207
|
}
|
|
908
1208
|
const result = await response.json();
|
|
909
1209
|
if (this.session && result.data?.customer) {
|
|
@@ -935,12 +1235,18 @@ var TiquoAuth = class {
|
|
|
935
1235
|
await this.ensureValidToken();
|
|
936
1236
|
const blob = await this.profilePhotoToBlob(photo);
|
|
937
1237
|
if (!blob.type.startsWith("image/")) {
|
|
938
|
-
throw new TiquoAuthError(
|
|
1238
|
+
throw new TiquoAuthError(
|
|
1239
|
+
"Profile photo must be an image",
|
|
1240
|
+
"INVALID_PROFILE_PHOTO"
|
|
1241
|
+
);
|
|
939
1242
|
}
|
|
940
|
-
const uploadUrlResponse = await this.request(
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1243
|
+
const uploadUrlResponse = await this.request(
|
|
1244
|
+
"/api/client/v1/profile/photo-upload-url",
|
|
1245
|
+
{
|
|
1246
|
+
method: "POST",
|
|
1247
|
+
body: JSON.stringify({})
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
944
1250
|
if (!uploadUrlResponse.ok) {
|
|
945
1251
|
const error = await uploadUrlResponse.json().catch(() => ({ error: "Failed to create profile photo upload URL" }));
|
|
946
1252
|
throw new TiquoAuthError(
|
|
@@ -952,7 +1258,10 @@ var TiquoAuth = class {
|
|
|
952
1258
|
const uploadUrlResult = await uploadUrlResponse.json();
|
|
953
1259
|
const uploadUrl = uploadUrlResult.data?.uploadUrl;
|
|
954
1260
|
if (!uploadUrl) {
|
|
955
|
-
throw new TiquoAuthError(
|
|
1261
|
+
throw new TiquoAuthError(
|
|
1262
|
+
"Profile photo upload URL was missing",
|
|
1263
|
+
"PROFILE_PHOTO_UPLOAD_URL_FAILED"
|
|
1264
|
+
);
|
|
956
1265
|
}
|
|
957
1266
|
const uploadResponse = await fetch(uploadUrl, {
|
|
958
1267
|
method: "POST",
|
|
@@ -960,17 +1269,27 @@ var TiquoAuth = class {
|
|
|
960
1269
|
body: blob
|
|
961
1270
|
});
|
|
962
1271
|
if (!uploadResponse.ok) {
|
|
963
|
-
throw new TiquoAuthError(
|
|
1272
|
+
throw new TiquoAuthError(
|
|
1273
|
+
"Failed to upload profile photo",
|
|
1274
|
+
"PROFILE_PHOTO_UPLOAD_FAILED",
|
|
1275
|
+
uploadResponse.status
|
|
1276
|
+
);
|
|
964
1277
|
}
|
|
965
1278
|
const uploadResult = await uploadResponse.json();
|
|
966
1279
|
const storageId = uploadResult.storageId;
|
|
967
1280
|
if (!storageId) {
|
|
968
|
-
throw new TiquoAuthError(
|
|
1281
|
+
throw new TiquoAuthError(
|
|
1282
|
+
"Profile photo storage ID was missing",
|
|
1283
|
+
"PROFILE_PHOTO_UPLOAD_FAILED"
|
|
1284
|
+
);
|
|
969
1285
|
}
|
|
970
|
-
const finalizeResponse = await this.request(
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1286
|
+
const finalizeResponse = await this.request(
|
|
1287
|
+
"/api/client/v1/profile/photo",
|
|
1288
|
+
{
|
|
1289
|
+
method: "POST",
|
|
1290
|
+
body: JSON.stringify({ storageId })
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
974
1293
|
if (!finalizeResponse.ok) {
|
|
975
1294
|
const error = await finalizeResponse.json().catch(() => ({ error: "Failed to update profile photo" }));
|
|
976
1295
|
throw new TiquoAuthError(
|
|
@@ -983,7 +1302,10 @@ var TiquoAuth = class {
|
|
|
983
1302
|
const customer = finalizeResult.data?.customer;
|
|
984
1303
|
const profilePhoto = finalizeResult.data?.profilePhoto;
|
|
985
1304
|
if (!customer || !profilePhoto) {
|
|
986
|
-
throw new TiquoAuthError(
|
|
1305
|
+
throw new TiquoAuthError(
|
|
1306
|
+
"Profile photo update response was incomplete",
|
|
1307
|
+
"PROFILE_PHOTO_UPDATE_FAILED"
|
|
1308
|
+
);
|
|
987
1309
|
}
|
|
988
1310
|
if (this.session && customer) {
|
|
989
1311
|
this.session = {
|
|
@@ -1058,13 +1380,17 @@ var TiquoAuth = class {
|
|
|
1058
1380
|
const response = await fetch(url.toString(), {
|
|
1059
1381
|
method: "GET",
|
|
1060
1382
|
headers: {
|
|
1061
|
-
|
|
1383
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1062
1384
|
},
|
|
1063
1385
|
credentials: "include"
|
|
1064
1386
|
});
|
|
1065
1387
|
if (!response.ok) {
|
|
1066
1388
|
const error = await response.json().catch(() => ({ error: "Failed to get orders" }));
|
|
1067
|
-
throw new TiquoAuthError(
|
|
1389
|
+
throw new TiquoAuthError(
|
|
1390
|
+
error.error || "Failed to get orders",
|
|
1391
|
+
"GET_ORDERS_FAILED",
|
|
1392
|
+
response.status
|
|
1393
|
+
);
|
|
1068
1394
|
}
|
|
1069
1395
|
const result = await response.json();
|
|
1070
1396
|
return result.data || { orders: [], hasMore: false };
|
|
@@ -1096,17 +1422,51 @@ var TiquoAuth = class {
|
|
|
1096
1422
|
const response = await fetch(url.toString(), {
|
|
1097
1423
|
method: "GET",
|
|
1098
1424
|
headers: {
|
|
1099
|
-
|
|
1425
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1100
1426
|
},
|
|
1101
1427
|
credentials: "include"
|
|
1102
1428
|
});
|
|
1103
1429
|
if (!response.ok) {
|
|
1104
1430
|
const error = await response.json().catch(() => ({ error: "Failed to get bookings" }));
|
|
1105
|
-
throw new TiquoAuthError(
|
|
1431
|
+
throw new TiquoAuthError(
|
|
1432
|
+
error.error || "Failed to get bookings",
|
|
1433
|
+
"GET_BOOKINGS_FAILED",
|
|
1434
|
+
response.status
|
|
1435
|
+
);
|
|
1106
1436
|
}
|
|
1107
1437
|
const result = await response.json();
|
|
1108
1438
|
return result.data || { bookings: [], hasMore: false };
|
|
1109
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Download a QR-code ticket PDF for one of the authenticated customer's bookings.
|
|
1442
|
+
*
|
|
1443
|
+
* This is only available when `booking.ticketing.qrCodeTicketsEnabled` is
|
|
1444
|
+
* true in the booking list response.
|
|
1445
|
+
*/
|
|
1446
|
+
async downloadBookingTicket(bookingId) {
|
|
1447
|
+
await this.ensureValidToken();
|
|
1448
|
+
this.log("Downloading booking ticket:", bookingId);
|
|
1449
|
+
const url = new URL(
|
|
1450
|
+
`${this.config.apiEndpoint}/api/client/v1/booking-ticket`
|
|
1451
|
+
);
|
|
1452
|
+
url.searchParams.set("bookingId", bookingId);
|
|
1453
|
+
const response = await fetch(url.toString(), {
|
|
1454
|
+
method: "GET",
|
|
1455
|
+
headers: {
|
|
1456
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1457
|
+
},
|
|
1458
|
+
credentials: "include"
|
|
1459
|
+
});
|
|
1460
|
+
if (!response.ok) {
|
|
1461
|
+
const error = await response.json().catch(() => ({ error: "Failed to download booking ticket" }));
|
|
1462
|
+
throw new TiquoAuthError(
|
|
1463
|
+
error.error || "Failed to download booking ticket",
|
|
1464
|
+
"DOWNLOAD_BOOKING_TICKET_FAILED",
|
|
1465
|
+
response.status
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
return response.blob();
|
|
1469
|
+
}
|
|
1110
1470
|
/**
|
|
1111
1471
|
* Convenience wrapper around `getBookings({ upcoming: true })` for the
|
|
1112
1472
|
* common "show me my upcoming bookings" case. Results are sorted
|
|
@@ -1144,14 +1504,18 @@ var TiquoAuth = class {
|
|
|
1144
1504
|
const response = await fetch(url.toString(), {
|
|
1145
1505
|
method: "GET",
|
|
1146
1506
|
headers: {
|
|
1147
|
-
|
|
1507
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1148
1508
|
},
|
|
1149
1509
|
credentials: "include"
|
|
1150
1510
|
});
|
|
1151
1511
|
if (!response.ok) {
|
|
1152
1512
|
const error = await response.json().catch(() => ({ error: "Failed to get receipt" }));
|
|
1153
1513
|
const code = response.status === 404 ? "RECEIPT_NOT_AVAILABLE" : "GET_RECEIPT_FAILED";
|
|
1154
|
-
throw new TiquoAuthError(
|
|
1514
|
+
throw new TiquoAuthError(
|
|
1515
|
+
error.error || "Failed to get receipt",
|
|
1516
|
+
code,
|
|
1517
|
+
response.status
|
|
1518
|
+
);
|
|
1155
1519
|
}
|
|
1156
1520
|
const result = await response.json();
|
|
1157
1521
|
return result.data;
|
|
@@ -1176,13 +1540,17 @@ var TiquoAuth = class {
|
|
|
1176
1540
|
const response = await fetch(url.toString(), {
|
|
1177
1541
|
method: "GET",
|
|
1178
1542
|
headers: {
|
|
1179
|
-
|
|
1543
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1180
1544
|
},
|
|
1181
1545
|
credentials: "include"
|
|
1182
1546
|
});
|
|
1183
1547
|
if (!response.ok) {
|
|
1184
1548
|
const error = await response.json().catch(() => ({ error: "Failed to get enquiries" }));
|
|
1185
|
-
throw new TiquoAuthError(
|
|
1549
|
+
throw new TiquoAuthError(
|
|
1550
|
+
error.error || "Failed to get enquiries",
|
|
1551
|
+
"GET_ENQUIRIES_FAILED",
|
|
1552
|
+
response.status
|
|
1553
|
+
);
|
|
1186
1554
|
}
|
|
1187
1555
|
const result = await response.json();
|
|
1188
1556
|
return result.data || { enquiries: [], hasMore: false };
|
|
@@ -1200,16 +1568,23 @@ var TiquoAuth = class {
|
|
|
1200
1568
|
async getCompanies() {
|
|
1201
1569
|
await this.ensureValidToken();
|
|
1202
1570
|
this.log("Fetching customer companies");
|
|
1203
|
-
const response = await fetch(
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
"
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1571
|
+
const response = await fetch(
|
|
1572
|
+
`${this.config.apiEndpoint}/api/client/v1/companies`,
|
|
1573
|
+
{
|
|
1574
|
+
method: "GET",
|
|
1575
|
+
headers: {
|
|
1576
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1577
|
+
},
|
|
1578
|
+
credentials: "include"
|
|
1579
|
+
}
|
|
1580
|
+
);
|
|
1210
1581
|
if (!response.ok) {
|
|
1211
1582
|
const error = await response.json().catch(() => ({ error: "Failed to get companies" }));
|
|
1212
|
-
throw new TiquoAuthError(
|
|
1583
|
+
throw new TiquoAuthError(
|
|
1584
|
+
error.error || "Failed to get companies",
|
|
1585
|
+
"GET_COMPANIES_FAILED",
|
|
1586
|
+
response.status
|
|
1587
|
+
);
|
|
1213
1588
|
}
|
|
1214
1589
|
const result = await response.json();
|
|
1215
1590
|
return result.data || { companies: [] };
|
|
@@ -1231,19 +1606,25 @@ var TiquoAuth = class {
|
|
|
1231
1606
|
async getCompanyColleagues(companyId) {
|
|
1232
1607
|
await this.ensureValidToken();
|
|
1233
1608
|
this.log("Fetching company colleagues for:", companyId);
|
|
1234
|
-
const url = new URL(
|
|
1609
|
+
const url = new URL(
|
|
1610
|
+
`${this.config.apiEndpoint}/api/client/v1/companies/colleagues`
|
|
1611
|
+
);
|
|
1235
1612
|
url.searchParams.set("companyId", companyId);
|
|
1236
1613
|
const response = await fetch(url.toString(), {
|
|
1237
1614
|
method: "GET",
|
|
1238
1615
|
headers: {
|
|
1239
|
-
|
|
1616
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1240
1617
|
},
|
|
1241
1618
|
credentials: "include"
|
|
1242
1619
|
});
|
|
1243
1620
|
if (!response.ok) {
|
|
1244
1621
|
const error = await response.json().catch(() => ({ error: "Failed to get colleagues" }));
|
|
1245
1622
|
const code = response.status === 403 ? "NOT_COMPANY_ADMIN" : "GET_COLLEAGUES_FAILED";
|
|
1246
|
-
throw new TiquoAuthError(
|
|
1623
|
+
throw new TiquoAuthError(
|
|
1624
|
+
error.error || "Failed to get colleagues",
|
|
1625
|
+
code,
|
|
1626
|
+
response.status
|
|
1627
|
+
);
|
|
1247
1628
|
}
|
|
1248
1629
|
const result = await response.json();
|
|
1249
1630
|
return result.data;
|
|
@@ -1264,13 +1645,17 @@ var TiquoAuth = class {
|
|
|
1264
1645
|
});
|
|
1265
1646
|
if (!response.ok) {
|
|
1266
1647
|
const error = await response.json().catch(() => ({ error: "Failed to generate token" }));
|
|
1267
|
-
throw new TiquoAuthError(
|
|
1648
|
+
throw new TiquoAuthError(
|
|
1649
|
+
error.error || "Failed to generate token",
|
|
1650
|
+
"IFRAME_TOKEN_FAILED",
|
|
1651
|
+
response.status
|
|
1652
|
+
);
|
|
1268
1653
|
}
|
|
1269
1654
|
return response.json();
|
|
1270
1655
|
}
|
|
1271
1656
|
/**
|
|
1272
1657
|
* Embed a customer flow with automatic authentication
|
|
1273
|
-
*
|
|
1658
|
+
*
|
|
1274
1659
|
* @param flowUrl - The URL of the customer flow (book.tiquo.app/...)
|
|
1275
1660
|
* @param container - Container element or selector
|
|
1276
1661
|
* @param options - Optional iframe configuration
|
|
@@ -1278,7 +1663,10 @@ var TiquoAuth = class {
|
|
|
1278
1663
|
async embedCustomerFlow(flowUrl, container, options) {
|
|
1279
1664
|
const containerEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
1280
1665
|
if (!containerEl) {
|
|
1281
|
-
throw new TiquoAuthError(
|
|
1666
|
+
throw new TiquoAuthError(
|
|
1667
|
+
"Container element not found",
|
|
1668
|
+
"CONTAINER_NOT_FOUND"
|
|
1669
|
+
);
|
|
1282
1670
|
}
|
|
1283
1671
|
let authToken;
|
|
1284
1672
|
if (this.accessToken) {
|
|
@@ -1304,7 +1692,10 @@ var TiquoAuth = class {
|
|
|
1304
1692
|
iframe.addEventListener("load", options.onLoad);
|
|
1305
1693
|
}
|
|
1306
1694
|
if (options?.onError) {
|
|
1307
|
-
iframe.addEventListener(
|
|
1695
|
+
iframe.addEventListener(
|
|
1696
|
+
"error",
|
|
1697
|
+
() => options.onError(new Error("Failed to load iframe"))
|
|
1698
|
+
);
|
|
1308
1699
|
}
|
|
1309
1700
|
containerEl.innerHTML = "";
|
|
1310
1701
|
containerEl.appendChild(iframe);
|
|
@@ -1433,7 +1824,11 @@ var TiquoAuth = class {
|
|
|
1433
1824
|
this.accessToken = accessToken;
|
|
1434
1825
|
this.refreshToken = params.get("refresh_token");
|
|
1435
1826
|
if (window.history?.replaceState) {
|
|
1436
|
-
window.history.replaceState(
|
|
1827
|
+
window.history.replaceState(
|
|
1828
|
+
null,
|
|
1829
|
+
"",
|
|
1830
|
+
window.location.pathname + window.location.search
|
|
1831
|
+
);
|
|
1437
1832
|
}
|
|
1438
1833
|
return;
|
|
1439
1834
|
}
|
|
@@ -1461,7 +1856,10 @@ var TiquoAuth = class {
|
|
|
1461
1856
|
if (typeof photo === "string" && this.isProfilePhotoBlobUrl(photo)) {
|
|
1462
1857
|
const response = await fetch(photo);
|
|
1463
1858
|
if (!response.ok) {
|
|
1464
|
-
throw new TiquoAuthError(
|
|
1859
|
+
throw new TiquoAuthError(
|
|
1860
|
+
"Failed to read profile photo URI",
|
|
1861
|
+
"INVALID_PROFILE_PHOTO"
|
|
1862
|
+
);
|
|
1465
1863
|
}
|
|
1466
1864
|
return response.blob();
|
|
1467
1865
|
}
|
|
@@ -1529,16 +1927,19 @@ var TiquoAuth = class {
|
|
|
1529
1927
|
this.isRefreshing = true;
|
|
1530
1928
|
this.log("Refreshing access token");
|
|
1531
1929
|
try {
|
|
1532
|
-
const response = await fetch(
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1930
|
+
const response = await fetch(
|
|
1931
|
+
`${this.config.apiEndpoint}/api/client/v1/refresh`,
|
|
1932
|
+
{
|
|
1933
|
+
method: "POST",
|
|
1934
|
+
headers: {
|
|
1935
|
+
"Content-Type": "application/json"
|
|
1936
|
+
},
|
|
1937
|
+
body: JSON.stringify({
|
|
1938
|
+
refresh_token: this.refreshToken
|
|
1939
|
+
}),
|
|
1940
|
+
credentials: "include"
|
|
1941
|
+
}
|
|
1942
|
+
);
|
|
1542
1943
|
if (!response.ok) {
|
|
1543
1944
|
this.log("Token refresh failed, clearing session");
|
|
1544
1945
|
this.clearTokens();
|
|
@@ -1575,13 +1976,16 @@ var TiquoAuth = class {
|
|
|
1575
1976
|
}
|
|
1576
1977
|
try {
|
|
1577
1978
|
await this.refreshTokenIfNeeded();
|
|
1578
|
-
const response = await fetch(
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
"
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1979
|
+
const response = await fetch(
|
|
1980
|
+
`${this.config.apiEndpoint}/api/client/v1/profile`,
|
|
1981
|
+
{
|
|
1982
|
+
method: "GET",
|
|
1983
|
+
headers: {
|
|
1984
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1985
|
+
},
|
|
1986
|
+
credentials: "include"
|
|
1987
|
+
}
|
|
1988
|
+
);
|
|
1585
1989
|
if (!response.ok) {
|
|
1586
1990
|
this.log("Session invalid, clearing");
|
|
1587
1991
|
this.clearTokens();
|
|
@@ -1599,6 +2003,9 @@ var TiquoAuth = class {
|
|
|
1599
2003
|
if (this.session.user?.id) {
|
|
1600
2004
|
addCustomerUserId(this.session.user.id);
|
|
1601
2005
|
}
|
|
2006
|
+
if (this.session.customer) {
|
|
2007
|
+
this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
|
|
2008
|
+
}
|
|
1602
2009
|
this.notifyListeners();
|
|
1603
2010
|
this.scheduleRefresh();
|
|
1604
2011
|
return this.session;
|
|
@@ -1633,8 +2040,12 @@ var TiquoAuth = class {
|
|
|
1633
2040
|
return;
|
|
1634
2041
|
}
|
|
1635
2042
|
try {
|
|
1636
|
-
const accessToken = localStorage.getItem(
|
|
1637
|
-
|
|
2043
|
+
const accessToken = localStorage.getItem(
|
|
2044
|
+
`${this.config.storagePrefix}access_token`
|
|
2045
|
+
);
|
|
2046
|
+
const refreshToken = localStorage.getItem(
|
|
2047
|
+
`${this.config.storagePrefix}refresh_token`
|
|
2048
|
+
);
|
|
1638
2049
|
if (accessToken) {
|
|
1639
2050
|
this.accessToken = accessToken;
|
|
1640
2051
|
this.refreshToken = refreshToken;
|
|
@@ -1746,7 +2157,12 @@ var TiquoAuth = class {
|
|
|
1746
2157
|
if (message.tabId === this.tabId) return;
|
|
1747
2158
|
if (this.isProcessingTabSync) return;
|
|
1748
2159
|
this.isProcessingTabSync = true;
|
|
1749
|
-
this.log(
|
|
2160
|
+
this.log(
|
|
2161
|
+
"Received tab sync message:",
|
|
2162
|
+
message.type,
|
|
2163
|
+
"from tab:",
|
|
2164
|
+
message.tabId
|
|
2165
|
+
);
|
|
1750
2166
|
try {
|
|
1751
2167
|
switch (message.type) {
|
|
1752
2168
|
case "LOGIN":
|
|
@@ -1773,7 +2189,9 @@ var TiquoAuth = class {
|
|
|
1773
2189
|
}
|
|
1774
2190
|
try {
|
|
1775
2191
|
localStorage.removeItem(`${this.config.storagePrefix}access_token`);
|
|
1776
|
-
localStorage.removeItem(
|
|
2192
|
+
localStorage.removeItem(
|
|
2193
|
+
`${this.config.storagePrefix}refresh_token`
|
|
2194
|
+
);
|
|
1777
2195
|
} catch {
|
|
1778
2196
|
}
|
|
1779
2197
|
this.notifyListeners();
|
|
@@ -1876,6 +2294,7 @@ TiquoPhone.getDialCode = getDialCode;
|
|
|
1876
2294
|
TiquoPhone.buildPhone = buildPhone;
|
|
1877
2295
|
var index_default = TiquoAuth;
|
|
1878
2296
|
export {
|
|
2297
|
+
TiquoAnalytics,
|
|
1879
2298
|
TiquoAuth,
|
|
1880
2299
|
TiquoAuthError,
|
|
1881
2300
|
TiquoCMS,
|