@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.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
TiquoAnalytics: () => TiquoAnalytics,
|
|
23
24
|
TiquoAuth: () => TiquoAuth,
|
|
24
25
|
TiquoAuthError: () => TiquoAuthError,
|
|
25
26
|
TiquoCMS: () => TiquoCMS,
|
|
@@ -678,8 +679,258 @@ function printTiquoBranding() {
|
|
|
678
679
|
}
|
|
679
680
|
}
|
|
680
681
|
printTiquoBranding();
|
|
682
|
+
var ANALYTICS_LOCATION_EVENT = "tiquo:locationchange";
|
|
683
|
+
var analyticsHistoryPatched = false;
|
|
684
|
+
function randomId() {
|
|
685
|
+
try {
|
|
686
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
687
|
+
return crypto.randomUUID();
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
return `ev_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
|
|
692
|
+
}
|
|
693
|
+
function normalizeHostname(hostname) {
|
|
694
|
+
if (!hostname) return void 0;
|
|
695
|
+
const clean = hostname.trim().toLowerCase().split(":")[0];
|
|
696
|
+
return clean.startsWith("www.") ? clean.slice(4) : clean;
|
|
697
|
+
}
|
|
698
|
+
function getBrowser() {
|
|
699
|
+
if (typeof navigator === "undefined") return "Other";
|
|
700
|
+
const ua = navigator.userAgent;
|
|
701
|
+
if (/Edg(e|A|iOS)?\//i.test(ua)) return "Edge";
|
|
702
|
+
if (/Firefox\//i.test(ua)) return "Firefox";
|
|
703
|
+
if (/Chrome\/|CriOS\//i.test(ua)) return "Chrome";
|
|
704
|
+
if (/Safari\//i.test(ua) && !/Chrome/i.test(ua)) return "Safari";
|
|
705
|
+
return "Other";
|
|
706
|
+
}
|
|
707
|
+
function getDevice() {
|
|
708
|
+
if (typeof navigator === "undefined") return "Desktop";
|
|
709
|
+
const ua = navigator.userAgent;
|
|
710
|
+
if (/iPad|tablet|PlayBook|Silk/i.test(ua)) return "Tablet";
|
|
711
|
+
if (/Android(?!.*Mobile)/i.test(ua) && /Android/i.test(ua) && !/Mobile/i.test(ua))
|
|
712
|
+
return "Tablet";
|
|
713
|
+
if (/Mobile|iPhone|iPod|Android.*Mobile|webOS|BlackBerry|Opera Mini|IEMobile/i.test(
|
|
714
|
+
ua
|
|
715
|
+
))
|
|
716
|
+
return "Mobile";
|
|
717
|
+
return "Desktop";
|
|
718
|
+
}
|
|
719
|
+
function getOperatingSystem() {
|
|
720
|
+
if (typeof navigator === "undefined") return "Other";
|
|
721
|
+
const ua = navigator.userAgent;
|
|
722
|
+
if (/iPhone|iPad|iPod/i.test(ua)) return "iOS";
|
|
723
|
+
if (/Android/i.test(ua)) return "Android";
|
|
724
|
+
if (/CrOS/i.test(ua)) return "Chrome OS";
|
|
725
|
+
if (/Windows/i.test(ua)) return "Windows";
|
|
726
|
+
if (/Macintosh|Mac OS X/i.test(ua)) return "macOS";
|
|
727
|
+
if (/Linux/i.test(ua)) return "Linux";
|
|
728
|
+
return "Other";
|
|
729
|
+
}
|
|
730
|
+
function getUtmParams() {
|
|
731
|
+
if (typeof window === "undefined") return {};
|
|
732
|
+
const params = new URLSearchParams(window.location.search);
|
|
733
|
+
return {
|
|
734
|
+
source: params.get("utm_source") || void 0,
|
|
735
|
+
medium: params.get("utm_medium") || void 0,
|
|
736
|
+
campaign: params.get("utm_campaign") || void 0,
|
|
737
|
+
content: params.get("utm_content") || void 0,
|
|
738
|
+
term: params.get("utm_term") || void 0
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function patchHistoryForAnalytics() {
|
|
742
|
+
if (analyticsHistoryPatched || typeof window === "undefined") return;
|
|
743
|
+
analyticsHistoryPatched = true;
|
|
744
|
+
const notify = () => {
|
|
745
|
+
window.dispatchEvent(
|
|
746
|
+
new Event(ANALYTICS_LOCATION_EVENT)
|
|
747
|
+
);
|
|
748
|
+
};
|
|
749
|
+
const pushState = history.pushState;
|
|
750
|
+
history.pushState = function patchedPushState(...args) {
|
|
751
|
+
const result = pushState.apply(this, args);
|
|
752
|
+
notify();
|
|
753
|
+
return result;
|
|
754
|
+
};
|
|
755
|
+
const replaceState = history.replaceState;
|
|
756
|
+
history.replaceState = function patchedReplaceState(...args) {
|
|
757
|
+
const result = replaceState.apply(this, args);
|
|
758
|
+
notify();
|
|
759
|
+
return result;
|
|
760
|
+
};
|
|
761
|
+
window.addEventListener("popstate", notify);
|
|
762
|
+
}
|
|
763
|
+
var _TiquoAnalytics = class _TiquoAnalytics {
|
|
764
|
+
constructor(config) {
|
|
765
|
+
this.pageStartedAt = Date.now();
|
|
766
|
+
this.lastPath = null;
|
|
767
|
+
this.started = false;
|
|
768
|
+
if (!config.publicKey) {
|
|
769
|
+
throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
|
|
770
|
+
}
|
|
771
|
+
if (!config.publicKey.startsWith("pk_dom_")) {
|
|
772
|
+
throw new TiquoAuthError(
|
|
773
|
+
"Invalid public key format. Expected pk_dom_xxx",
|
|
774
|
+
"INVALID_PUBLIC_KEY"
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
this.config = {
|
|
778
|
+
publicKey: config.publicKey,
|
|
779
|
+
apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
|
|
780
|
+
debug: config.debug || false,
|
|
781
|
+
autoTrackPageviews: config.autoTrackPageviews !== false,
|
|
782
|
+
getAccessToken: config.getAccessToken,
|
|
783
|
+
siteSlug: config.siteSlug
|
|
784
|
+
};
|
|
785
|
+
this.clientSessionId = this.getClientSessionId();
|
|
786
|
+
if (this.config.autoTrackPageviews) {
|
|
787
|
+
this.start();
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
start() {
|
|
791
|
+
if (this.started || typeof window === "undefined") return;
|
|
792
|
+
const activeKey = `${this.config.publicKey}:${this.config.siteSlug || ""}`;
|
|
793
|
+
const existing = _TiquoAnalytics.activeInstances.get(activeKey);
|
|
794
|
+
if (existing && existing !== this) {
|
|
795
|
+
existing.adoptConfig(this.config);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
_TiquoAnalytics.activeInstances.set(activeKey, this);
|
|
799
|
+
this.started = true;
|
|
800
|
+
patchHistoryForAnalytics();
|
|
801
|
+
this.trackPageview();
|
|
802
|
+
window.addEventListener(
|
|
803
|
+
ANALYTICS_LOCATION_EVENT,
|
|
804
|
+
() => this.trackPageview()
|
|
805
|
+
);
|
|
806
|
+
window.addEventListener("pagehide", () => this.trackEngagement());
|
|
807
|
+
document.addEventListener("visibilitychange", () => {
|
|
808
|
+
if (document.visibilityState === "hidden") this.trackEngagement();
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
async trackPageview(options = {}) {
|
|
812
|
+
if (typeof window === "undefined") return;
|
|
813
|
+
const path = options.path || `${window.location.pathname}${window.location.search}`;
|
|
814
|
+
if (path === this.lastPath) return;
|
|
815
|
+
if (this.lastPath) {
|
|
816
|
+
this.trackEngagement().catch(() => void 0);
|
|
817
|
+
}
|
|
818
|
+
this.lastPath = path;
|
|
819
|
+
this.pageStartedAt = Date.now();
|
|
820
|
+
await this.track("pageview", {
|
|
821
|
+
...options,
|
|
822
|
+
path,
|
|
823
|
+
url: options.url || window.location.href,
|
|
824
|
+
title: options.title || document.title,
|
|
825
|
+
referrer: options.referrer || document.referrer || void 0
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
async track(eventType, options = {}) {
|
|
829
|
+
if (typeof window === "undefined") return;
|
|
830
|
+
const utm = getUtmParams();
|
|
831
|
+
const payload = {
|
|
832
|
+
publicKey: this.config.publicKey,
|
|
833
|
+
eventId: randomId(),
|
|
834
|
+
eventType,
|
|
835
|
+
eventName: options.eventName,
|
|
836
|
+
eventProperties: options.eventProperties,
|
|
837
|
+
clientSessionId: this.clientSessionId,
|
|
838
|
+
siteSlug: options.siteSlug || this.config.siteSlug,
|
|
839
|
+
pageSlug: options.pageSlug,
|
|
840
|
+
hostname: normalizeHostname(window.location.hostname),
|
|
841
|
+
path: options.path || `${window.location.pathname}${window.location.search}`,
|
|
842
|
+
url: options.url || window.location.href,
|
|
843
|
+
title: options.title || document.title,
|
|
844
|
+
referrer: options.referrer || document.referrer || void 0,
|
|
845
|
+
timestamp: Date.now(),
|
|
846
|
+
browser: getBrowser(),
|
|
847
|
+
device: getDevice(),
|
|
848
|
+
operatingSystem: getOperatingSystem(),
|
|
849
|
+
language: navigator.language,
|
|
850
|
+
screenWidth: window.screen?.width,
|
|
851
|
+
screenHeight: window.screen?.height,
|
|
852
|
+
viewportWidth: window.innerWidth,
|
|
853
|
+
viewportHeight: window.innerHeight,
|
|
854
|
+
durationMs: options.durationMs,
|
|
855
|
+
scrollDepth: options.scrollDepth,
|
|
856
|
+
identityLinkType: options.identityLinkType,
|
|
857
|
+
...utm
|
|
858
|
+
};
|
|
859
|
+
await this.send(payload);
|
|
860
|
+
}
|
|
861
|
+
async identify(options = {}) {
|
|
862
|
+
await this.track("identify", {
|
|
863
|
+
...options,
|
|
864
|
+
identityLinkType: options.identityLinkType || "auth_dom_login"
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
async trackEngagement() {
|
|
868
|
+
if (typeof window === "undefined" || !this.lastPath) return;
|
|
869
|
+
const durationMs = Date.now() - this.pageStartedAt;
|
|
870
|
+
const scrollHeight = Math.max(
|
|
871
|
+
document.documentElement.scrollHeight,
|
|
872
|
+
document.body?.scrollHeight || 0,
|
|
873
|
+
window.innerHeight
|
|
874
|
+
);
|
|
875
|
+
const scrollDepth = scrollHeight > 0 ? Math.min(1, (window.scrollY + window.innerHeight) / scrollHeight) : void 0;
|
|
876
|
+
await this.track("engagement", {
|
|
877
|
+
path: this.lastPath,
|
|
878
|
+
durationMs,
|
|
879
|
+
scrollDepth
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
async send(payload) {
|
|
883
|
+
const headers = {
|
|
884
|
+
"Content-Type": "text/plain;charset=UTF-8",
|
|
885
|
+
"X-Public-Key": this.config.publicKey
|
|
886
|
+
};
|
|
887
|
+
const accessToken = this.config.getAccessToken?.();
|
|
888
|
+
if (accessToken) {
|
|
889
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
await fetch(`${this.config.apiEndpoint}/api/dom-analytics/event`, {
|
|
893
|
+
method: "POST",
|
|
894
|
+
headers,
|
|
895
|
+
credentials: "include",
|
|
896
|
+
keepalive: true,
|
|
897
|
+
body: JSON.stringify(payload)
|
|
898
|
+
});
|
|
899
|
+
} catch (error) {
|
|
900
|
+
this.log("Analytics event failed:", error);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
getClientSessionId() {
|
|
904
|
+
const key = `tiquo_analytics_session_${this.config.publicKey}`;
|
|
905
|
+
try {
|
|
906
|
+
const existing = sessionStorage.getItem(key);
|
|
907
|
+
if (existing) return existing;
|
|
908
|
+
const next = randomId();
|
|
909
|
+
sessionStorage.setItem(key, next);
|
|
910
|
+
return next;
|
|
911
|
+
} catch {
|
|
912
|
+
return randomId();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
log(...args) {
|
|
916
|
+
if (this.config.debug) {
|
|
917
|
+
console.log("[TiquoAnalytics]", ...args);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
adoptConfig(nextConfig) {
|
|
921
|
+
if (nextConfig.getAccessToken) {
|
|
922
|
+
this.config.getAccessToken = nextConfig.getAccessToken;
|
|
923
|
+
}
|
|
924
|
+
if (nextConfig.siteSlug && !this.config.siteSlug) {
|
|
925
|
+
this.config.siteSlug = nextConfig.siteSlug;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
_TiquoAnalytics.activeInstances = /* @__PURE__ */ new Map();
|
|
930
|
+
var TiquoAnalytics = _TiquoAnalytics;
|
|
681
931
|
var TiquoCMS = class {
|
|
682
932
|
constructor(config) {
|
|
933
|
+
this.analytics = null;
|
|
683
934
|
if (!config.publicKey) {
|
|
684
935
|
throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
|
|
685
936
|
}
|
|
@@ -692,8 +943,16 @@ var TiquoCMS = class {
|
|
|
692
943
|
this.config = {
|
|
693
944
|
publicKey: config.publicKey,
|
|
694
945
|
apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
|
|
695
|
-
debug: config.debug || false
|
|
946
|
+
debug: config.debug || false,
|
|
947
|
+
analytics: config.analytics !== false
|
|
696
948
|
};
|
|
949
|
+
if (this.config.analytics) {
|
|
950
|
+
this.analytics = new TiquoAnalytics({
|
|
951
|
+
publicKey: this.config.publicKey,
|
|
952
|
+
apiEndpoint: this.config.apiEndpoint,
|
|
953
|
+
debug: this.config.debug
|
|
954
|
+
});
|
|
955
|
+
}
|
|
697
956
|
}
|
|
698
957
|
/**
|
|
699
958
|
* Fetch a published CMS page for the website attached to this public key.
|
|
@@ -702,19 +961,22 @@ var TiquoCMS = class {
|
|
|
702
961
|
* Convex connection details to the consuming website.
|
|
703
962
|
*/
|
|
704
963
|
async getPage(request) {
|
|
705
|
-
const response = await fetch(
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
964
|
+
const response = await fetch(
|
|
965
|
+
`${this.config.apiEndpoint}/api/dom-cms/page`,
|
|
966
|
+
{
|
|
967
|
+
method: "POST",
|
|
968
|
+
headers: {
|
|
969
|
+
"Content-Type": "text/plain;charset=UTF-8"
|
|
970
|
+
},
|
|
971
|
+
credentials: "include",
|
|
972
|
+
body: JSON.stringify({
|
|
973
|
+
publicKey: this.config.publicKey,
|
|
974
|
+
siteSlug: request.siteSlug,
|
|
975
|
+
pageSlug: request.pageSlug || "home",
|
|
976
|
+
templateId: request.templateId
|
|
977
|
+
})
|
|
978
|
+
}
|
|
979
|
+
);
|
|
718
980
|
if (response.status === 404) {
|
|
719
981
|
return null;
|
|
720
982
|
}
|
|
@@ -725,7 +987,11 @@ var TiquoCMS = class {
|
|
|
725
987
|
if (typeof error?.error === "string") message = error.error;
|
|
726
988
|
} catch {
|
|
727
989
|
}
|
|
728
|
-
throw new TiquoAuthError(
|
|
990
|
+
throw new TiquoAuthError(
|
|
991
|
+
message,
|
|
992
|
+
"CMS_PAGE_FETCH_FAILED",
|
|
993
|
+
response.status
|
|
994
|
+
);
|
|
729
995
|
}
|
|
730
996
|
const result = await response.json();
|
|
731
997
|
if (!result?.success || !result?.data) {
|
|
@@ -777,6 +1043,7 @@ var TiquoAuth = class {
|
|
|
777
1043
|
this.visibilityHandler = null;
|
|
778
1044
|
this.iframeObserver = null;
|
|
779
1045
|
this.injectedIframes = /* @__PURE__ */ new WeakSet();
|
|
1046
|
+
this.analytics = null;
|
|
780
1047
|
// Multi-tab sync
|
|
781
1048
|
this.broadcastChannel = null;
|
|
782
1049
|
this.isProcessingTabSync = false;
|
|
@@ -797,10 +1064,19 @@ var TiquoAuth = class {
|
|
|
797
1064
|
debug: config.debug || false,
|
|
798
1065
|
enableTabSync: config.enableTabSync !== false,
|
|
799
1066
|
// Default true
|
|
1067
|
+
analytics: config.analytics !== false,
|
|
800
1068
|
accessToken: config.accessToken,
|
|
801
1069
|
refreshToken: config.refreshToken
|
|
802
1070
|
};
|
|
803
1071
|
this.tabId = this.generateTabId();
|
|
1072
|
+
if (this.config.analytics) {
|
|
1073
|
+
this.analytics = new TiquoAnalytics({
|
|
1074
|
+
publicKey: this.config.publicKey,
|
|
1075
|
+
apiEndpoint: this.config.apiEndpoint,
|
|
1076
|
+
debug: this.config.debug,
|
|
1077
|
+
getAccessToken: () => this.accessToken
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
804
1080
|
if (this.config.enableTabSync) {
|
|
805
1081
|
this.initTabSync();
|
|
806
1082
|
}
|
|
@@ -826,7 +1102,11 @@ var TiquoAuth = class {
|
|
|
826
1102
|
});
|
|
827
1103
|
if (!response.ok) {
|
|
828
1104
|
const error = await response.json().catch(() => ({ error: "Failed to send OTP" }));
|
|
829
|
-
throw new TiquoAuthError(
|
|
1105
|
+
throw new TiquoAuthError(
|
|
1106
|
+
error.error || "Failed to send OTP",
|
|
1107
|
+
"OTP_SEND_FAILED",
|
|
1108
|
+
response.status
|
|
1109
|
+
);
|
|
830
1110
|
}
|
|
831
1111
|
const result = await response.json();
|
|
832
1112
|
return {
|
|
@@ -849,7 +1129,11 @@ var TiquoAuth = class {
|
|
|
849
1129
|
});
|
|
850
1130
|
if (!response.ok) {
|
|
851
1131
|
const error = await response.json().catch(() => ({ error: "Invalid OTP" }));
|
|
852
|
-
throw new TiquoAuthError(
|
|
1132
|
+
throw new TiquoAuthError(
|
|
1133
|
+
error.error || "Invalid OTP",
|
|
1134
|
+
"OTP_VERIFY_FAILED",
|
|
1135
|
+
response.status
|
|
1136
|
+
);
|
|
853
1137
|
}
|
|
854
1138
|
const result = await response.json();
|
|
855
1139
|
this.accessToken = result.accessToken;
|
|
@@ -859,6 +1143,7 @@ var TiquoAuth = class {
|
|
|
859
1143
|
if (this.session?.user?.id) {
|
|
860
1144
|
addCustomerUserId(this.session.user.id);
|
|
861
1145
|
}
|
|
1146
|
+
this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
|
|
862
1147
|
this.broadcastTabSync("LOGIN");
|
|
863
1148
|
return {
|
|
864
1149
|
success: true,
|
|
@@ -906,7 +1191,9 @@ var TiquoAuth = class {
|
|
|
906
1191
|
async updateProfile(updates) {
|
|
907
1192
|
await this.ensureValidToken();
|
|
908
1193
|
const normalizedUpdates = { ...updates };
|
|
909
|
-
const profilePhotoUpload = this.extractUploadableProfilePhoto(
|
|
1194
|
+
const profilePhotoUpload = this.extractUploadableProfilePhoto(
|
|
1195
|
+
normalizedUpdates.profilePhoto
|
|
1196
|
+
);
|
|
910
1197
|
if (profilePhotoUpload) {
|
|
911
1198
|
delete normalizedUpdates.profilePhoto;
|
|
912
1199
|
}
|
|
@@ -914,7 +1201,12 @@ var TiquoAuth = class {
|
|
|
914
1201
|
const normalized = normalizePhone(normalizedUpdates.phone);
|
|
915
1202
|
const validation = validatePhone(normalizedUpdates.phone);
|
|
916
1203
|
if (!validation.valid) {
|
|
917
|
-
this.log(
|
|
1204
|
+
this.log(
|
|
1205
|
+
"\u26A0\uFE0F Phone validation warning:",
|
|
1206
|
+
validation.reason,
|
|
1207
|
+
"- Original:",
|
|
1208
|
+
normalizedUpdates.phone
|
|
1209
|
+
);
|
|
918
1210
|
} else {
|
|
919
1211
|
this.log("Phone normalized:", normalizedUpdates.phone, "\u2192", normalized);
|
|
920
1212
|
}
|
|
@@ -926,7 +1218,12 @@ var TiquoAuth = class {
|
|
|
926
1218
|
const normalized = normalizePhone(p.number);
|
|
927
1219
|
const validation = validatePhone(p.number);
|
|
928
1220
|
if (!validation.valid) {
|
|
929
|
-
this.log(
|
|
1221
|
+
this.log(
|
|
1222
|
+
"\u26A0\uFE0F Phone validation warning:",
|
|
1223
|
+
validation.reason,
|
|
1224
|
+
"- Original:",
|
|
1225
|
+
p.number
|
|
1226
|
+
);
|
|
930
1227
|
}
|
|
931
1228
|
return { ...p, number: normalized || p.number };
|
|
932
1229
|
});
|
|
@@ -940,7 +1237,11 @@ var TiquoAuth = class {
|
|
|
940
1237
|
});
|
|
941
1238
|
if (!response.ok) {
|
|
942
1239
|
const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
|
|
943
|
-
throw new TiquoAuthError(
|
|
1240
|
+
throw new TiquoAuthError(
|
|
1241
|
+
error.error || "Failed to update profile",
|
|
1242
|
+
"PROFILE_UPDATE_FAILED",
|
|
1243
|
+
response.status
|
|
1244
|
+
);
|
|
944
1245
|
}
|
|
945
1246
|
const result = await response.json();
|
|
946
1247
|
if (this.session && result.data?.customer) {
|
|
@@ -972,12 +1273,18 @@ var TiquoAuth = class {
|
|
|
972
1273
|
await this.ensureValidToken();
|
|
973
1274
|
const blob = await this.profilePhotoToBlob(photo);
|
|
974
1275
|
if (!blob.type.startsWith("image/")) {
|
|
975
|
-
throw new TiquoAuthError(
|
|
1276
|
+
throw new TiquoAuthError(
|
|
1277
|
+
"Profile photo must be an image",
|
|
1278
|
+
"INVALID_PROFILE_PHOTO"
|
|
1279
|
+
);
|
|
976
1280
|
}
|
|
977
|
-
const uploadUrlResponse = await this.request(
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1281
|
+
const uploadUrlResponse = await this.request(
|
|
1282
|
+
"/api/client/v1/profile/photo-upload-url",
|
|
1283
|
+
{
|
|
1284
|
+
method: "POST",
|
|
1285
|
+
body: JSON.stringify({})
|
|
1286
|
+
}
|
|
1287
|
+
);
|
|
981
1288
|
if (!uploadUrlResponse.ok) {
|
|
982
1289
|
const error = await uploadUrlResponse.json().catch(() => ({ error: "Failed to create profile photo upload URL" }));
|
|
983
1290
|
throw new TiquoAuthError(
|
|
@@ -989,7 +1296,10 @@ var TiquoAuth = class {
|
|
|
989
1296
|
const uploadUrlResult = await uploadUrlResponse.json();
|
|
990
1297
|
const uploadUrl = uploadUrlResult.data?.uploadUrl;
|
|
991
1298
|
if (!uploadUrl) {
|
|
992
|
-
throw new TiquoAuthError(
|
|
1299
|
+
throw new TiquoAuthError(
|
|
1300
|
+
"Profile photo upload URL was missing",
|
|
1301
|
+
"PROFILE_PHOTO_UPLOAD_URL_FAILED"
|
|
1302
|
+
);
|
|
993
1303
|
}
|
|
994
1304
|
const uploadResponse = await fetch(uploadUrl, {
|
|
995
1305
|
method: "POST",
|
|
@@ -997,17 +1307,27 @@ var TiquoAuth = class {
|
|
|
997
1307
|
body: blob
|
|
998
1308
|
});
|
|
999
1309
|
if (!uploadResponse.ok) {
|
|
1000
|
-
throw new TiquoAuthError(
|
|
1310
|
+
throw new TiquoAuthError(
|
|
1311
|
+
"Failed to upload profile photo",
|
|
1312
|
+
"PROFILE_PHOTO_UPLOAD_FAILED",
|
|
1313
|
+
uploadResponse.status
|
|
1314
|
+
);
|
|
1001
1315
|
}
|
|
1002
1316
|
const uploadResult = await uploadResponse.json();
|
|
1003
1317
|
const storageId = uploadResult.storageId;
|
|
1004
1318
|
if (!storageId) {
|
|
1005
|
-
throw new TiquoAuthError(
|
|
1319
|
+
throw new TiquoAuthError(
|
|
1320
|
+
"Profile photo storage ID was missing",
|
|
1321
|
+
"PROFILE_PHOTO_UPLOAD_FAILED"
|
|
1322
|
+
);
|
|
1006
1323
|
}
|
|
1007
|
-
const finalizeResponse = await this.request(
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1324
|
+
const finalizeResponse = await this.request(
|
|
1325
|
+
"/api/client/v1/profile/photo",
|
|
1326
|
+
{
|
|
1327
|
+
method: "POST",
|
|
1328
|
+
body: JSON.stringify({ storageId })
|
|
1329
|
+
}
|
|
1330
|
+
);
|
|
1011
1331
|
if (!finalizeResponse.ok) {
|
|
1012
1332
|
const error = await finalizeResponse.json().catch(() => ({ error: "Failed to update profile photo" }));
|
|
1013
1333
|
throw new TiquoAuthError(
|
|
@@ -1020,7 +1340,10 @@ var TiquoAuth = class {
|
|
|
1020
1340
|
const customer = finalizeResult.data?.customer;
|
|
1021
1341
|
const profilePhoto = finalizeResult.data?.profilePhoto;
|
|
1022
1342
|
if (!customer || !profilePhoto) {
|
|
1023
|
-
throw new TiquoAuthError(
|
|
1343
|
+
throw new TiquoAuthError(
|
|
1344
|
+
"Profile photo update response was incomplete",
|
|
1345
|
+
"PROFILE_PHOTO_UPDATE_FAILED"
|
|
1346
|
+
);
|
|
1024
1347
|
}
|
|
1025
1348
|
if (this.session && customer) {
|
|
1026
1349
|
this.session = {
|
|
@@ -1095,13 +1418,17 @@ var TiquoAuth = class {
|
|
|
1095
1418
|
const response = await fetch(url.toString(), {
|
|
1096
1419
|
method: "GET",
|
|
1097
1420
|
headers: {
|
|
1098
|
-
|
|
1421
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1099
1422
|
},
|
|
1100
1423
|
credentials: "include"
|
|
1101
1424
|
});
|
|
1102
1425
|
if (!response.ok) {
|
|
1103
1426
|
const error = await response.json().catch(() => ({ error: "Failed to get orders" }));
|
|
1104
|
-
throw new TiquoAuthError(
|
|
1427
|
+
throw new TiquoAuthError(
|
|
1428
|
+
error.error || "Failed to get orders",
|
|
1429
|
+
"GET_ORDERS_FAILED",
|
|
1430
|
+
response.status
|
|
1431
|
+
);
|
|
1105
1432
|
}
|
|
1106
1433
|
const result = await response.json();
|
|
1107
1434
|
return result.data || { orders: [], hasMore: false };
|
|
@@ -1133,17 +1460,51 @@ var TiquoAuth = class {
|
|
|
1133
1460
|
const response = await fetch(url.toString(), {
|
|
1134
1461
|
method: "GET",
|
|
1135
1462
|
headers: {
|
|
1136
|
-
|
|
1463
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1137
1464
|
},
|
|
1138
1465
|
credentials: "include"
|
|
1139
1466
|
});
|
|
1140
1467
|
if (!response.ok) {
|
|
1141
1468
|
const error = await response.json().catch(() => ({ error: "Failed to get bookings" }));
|
|
1142
|
-
throw new TiquoAuthError(
|
|
1469
|
+
throw new TiquoAuthError(
|
|
1470
|
+
error.error || "Failed to get bookings",
|
|
1471
|
+
"GET_BOOKINGS_FAILED",
|
|
1472
|
+
response.status
|
|
1473
|
+
);
|
|
1143
1474
|
}
|
|
1144
1475
|
const result = await response.json();
|
|
1145
1476
|
return result.data || { bookings: [], hasMore: false };
|
|
1146
1477
|
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Download a QR-code ticket PDF for one of the authenticated customer's bookings.
|
|
1480
|
+
*
|
|
1481
|
+
* This is only available when `booking.ticketing.qrCodeTicketsEnabled` is
|
|
1482
|
+
* true in the booking list response.
|
|
1483
|
+
*/
|
|
1484
|
+
async downloadBookingTicket(bookingId) {
|
|
1485
|
+
await this.ensureValidToken();
|
|
1486
|
+
this.log("Downloading booking ticket:", bookingId);
|
|
1487
|
+
const url = new URL(
|
|
1488
|
+
`${this.config.apiEndpoint}/api/client/v1/booking-ticket`
|
|
1489
|
+
);
|
|
1490
|
+
url.searchParams.set("bookingId", bookingId);
|
|
1491
|
+
const response = await fetch(url.toString(), {
|
|
1492
|
+
method: "GET",
|
|
1493
|
+
headers: {
|
|
1494
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1495
|
+
},
|
|
1496
|
+
credentials: "include"
|
|
1497
|
+
});
|
|
1498
|
+
if (!response.ok) {
|
|
1499
|
+
const error = await response.json().catch(() => ({ error: "Failed to download booking ticket" }));
|
|
1500
|
+
throw new TiquoAuthError(
|
|
1501
|
+
error.error || "Failed to download booking ticket",
|
|
1502
|
+
"DOWNLOAD_BOOKING_TICKET_FAILED",
|
|
1503
|
+
response.status
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
return response.blob();
|
|
1507
|
+
}
|
|
1147
1508
|
/**
|
|
1148
1509
|
* Convenience wrapper around `getBookings({ upcoming: true })` for the
|
|
1149
1510
|
* common "show me my upcoming bookings" case. Results are sorted
|
|
@@ -1181,14 +1542,18 @@ var TiquoAuth = class {
|
|
|
1181
1542
|
const response = await fetch(url.toString(), {
|
|
1182
1543
|
method: "GET",
|
|
1183
1544
|
headers: {
|
|
1184
|
-
|
|
1545
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1185
1546
|
},
|
|
1186
1547
|
credentials: "include"
|
|
1187
1548
|
});
|
|
1188
1549
|
if (!response.ok) {
|
|
1189
1550
|
const error = await response.json().catch(() => ({ error: "Failed to get receipt" }));
|
|
1190
1551
|
const code = response.status === 404 ? "RECEIPT_NOT_AVAILABLE" : "GET_RECEIPT_FAILED";
|
|
1191
|
-
throw new TiquoAuthError(
|
|
1552
|
+
throw new TiquoAuthError(
|
|
1553
|
+
error.error || "Failed to get receipt",
|
|
1554
|
+
code,
|
|
1555
|
+
response.status
|
|
1556
|
+
);
|
|
1192
1557
|
}
|
|
1193
1558
|
const result = await response.json();
|
|
1194
1559
|
return result.data;
|
|
@@ -1213,13 +1578,17 @@ var TiquoAuth = class {
|
|
|
1213
1578
|
const response = await fetch(url.toString(), {
|
|
1214
1579
|
method: "GET",
|
|
1215
1580
|
headers: {
|
|
1216
|
-
|
|
1581
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1217
1582
|
},
|
|
1218
1583
|
credentials: "include"
|
|
1219
1584
|
});
|
|
1220
1585
|
if (!response.ok) {
|
|
1221
1586
|
const error = await response.json().catch(() => ({ error: "Failed to get enquiries" }));
|
|
1222
|
-
throw new TiquoAuthError(
|
|
1587
|
+
throw new TiquoAuthError(
|
|
1588
|
+
error.error || "Failed to get enquiries",
|
|
1589
|
+
"GET_ENQUIRIES_FAILED",
|
|
1590
|
+
response.status
|
|
1591
|
+
);
|
|
1223
1592
|
}
|
|
1224
1593
|
const result = await response.json();
|
|
1225
1594
|
return result.data || { enquiries: [], hasMore: false };
|
|
@@ -1237,16 +1606,23 @@ var TiquoAuth = class {
|
|
|
1237
1606
|
async getCompanies() {
|
|
1238
1607
|
await this.ensureValidToken();
|
|
1239
1608
|
this.log("Fetching customer companies");
|
|
1240
|
-
const response = await fetch(
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
"
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1609
|
+
const response = await fetch(
|
|
1610
|
+
`${this.config.apiEndpoint}/api/client/v1/companies`,
|
|
1611
|
+
{
|
|
1612
|
+
method: "GET",
|
|
1613
|
+
headers: {
|
|
1614
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1615
|
+
},
|
|
1616
|
+
credentials: "include"
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1247
1619
|
if (!response.ok) {
|
|
1248
1620
|
const error = await response.json().catch(() => ({ error: "Failed to get companies" }));
|
|
1249
|
-
throw new TiquoAuthError(
|
|
1621
|
+
throw new TiquoAuthError(
|
|
1622
|
+
error.error || "Failed to get companies",
|
|
1623
|
+
"GET_COMPANIES_FAILED",
|
|
1624
|
+
response.status
|
|
1625
|
+
);
|
|
1250
1626
|
}
|
|
1251
1627
|
const result = await response.json();
|
|
1252
1628
|
return result.data || { companies: [] };
|
|
@@ -1268,19 +1644,25 @@ var TiquoAuth = class {
|
|
|
1268
1644
|
async getCompanyColleagues(companyId) {
|
|
1269
1645
|
await this.ensureValidToken();
|
|
1270
1646
|
this.log("Fetching company colleagues for:", companyId);
|
|
1271
|
-
const url = new URL(
|
|
1647
|
+
const url = new URL(
|
|
1648
|
+
`${this.config.apiEndpoint}/api/client/v1/companies/colleagues`
|
|
1649
|
+
);
|
|
1272
1650
|
url.searchParams.set("companyId", companyId);
|
|
1273
1651
|
const response = await fetch(url.toString(), {
|
|
1274
1652
|
method: "GET",
|
|
1275
1653
|
headers: {
|
|
1276
|
-
|
|
1654
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
1277
1655
|
},
|
|
1278
1656
|
credentials: "include"
|
|
1279
1657
|
});
|
|
1280
1658
|
if (!response.ok) {
|
|
1281
1659
|
const error = await response.json().catch(() => ({ error: "Failed to get colleagues" }));
|
|
1282
1660
|
const code = response.status === 403 ? "NOT_COMPANY_ADMIN" : "GET_COLLEAGUES_FAILED";
|
|
1283
|
-
throw new TiquoAuthError(
|
|
1661
|
+
throw new TiquoAuthError(
|
|
1662
|
+
error.error || "Failed to get colleagues",
|
|
1663
|
+
code,
|
|
1664
|
+
response.status
|
|
1665
|
+
);
|
|
1284
1666
|
}
|
|
1285
1667
|
const result = await response.json();
|
|
1286
1668
|
return result.data;
|
|
@@ -1301,13 +1683,17 @@ var TiquoAuth = class {
|
|
|
1301
1683
|
});
|
|
1302
1684
|
if (!response.ok) {
|
|
1303
1685
|
const error = await response.json().catch(() => ({ error: "Failed to generate token" }));
|
|
1304
|
-
throw new TiquoAuthError(
|
|
1686
|
+
throw new TiquoAuthError(
|
|
1687
|
+
error.error || "Failed to generate token",
|
|
1688
|
+
"IFRAME_TOKEN_FAILED",
|
|
1689
|
+
response.status
|
|
1690
|
+
);
|
|
1305
1691
|
}
|
|
1306
1692
|
return response.json();
|
|
1307
1693
|
}
|
|
1308
1694
|
/**
|
|
1309
1695
|
* Embed a customer flow with automatic authentication
|
|
1310
|
-
*
|
|
1696
|
+
*
|
|
1311
1697
|
* @param flowUrl - The URL of the customer flow (book.tiquo.app/...)
|
|
1312
1698
|
* @param container - Container element or selector
|
|
1313
1699
|
* @param options - Optional iframe configuration
|
|
@@ -1315,7 +1701,10 @@ var TiquoAuth = class {
|
|
|
1315
1701
|
async embedCustomerFlow(flowUrl, container, options) {
|
|
1316
1702
|
const containerEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
1317
1703
|
if (!containerEl) {
|
|
1318
|
-
throw new TiquoAuthError(
|
|
1704
|
+
throw new TiquoAuthError(
|
|
1705
|
+
"Container element not found",
|
|
1706
|
+
"CONTAINER_NOT_FOUND"
|
|
1707
|
+
);
|
|
1319
1708
|
}
|
|
1320
1709
|
let authToken;
|
|
1321
1710
|
if (this.accessToken) {
|
|
@@ -1341,7 +1730,10 @@ var TiquoAuth = class {
|
|
|
1341
1730
|
iframe.addEventListener("load", options.onLoad);
|
|
1342
1731
|
}
|
|
1343
1732
|
if (options?.onError) {
|
|
1344
|
-
iframe.addEventListener(
|
|
1733
|
+
iframe.addEventListener(
|
|
1734
|
+
"error",
|
|
1735
|
+
() => options.onError(new Error("Failed to load iframe"))
|
|
1736
|
+
);
|
|
1345
1737
|
}
|
|
1346
1738
|
containerEl.innerHTML = "";
|
|
1347
1739
|
containerEl.appendChild(iframe);
|
|
@@ -1470,7 +1862,11 @@ var TiquoAuth = class {
|
|
|
1470
1862
|
this.accessToken = accessToken;
|
|
1471
1863
|
this.refreshToken = params.get("refresh_token");
|
|
1472
1864
|
if (window.history?.replaceState) {
|
|
1473
|
-
window.history.replaceState(
|
|
1865
|
+
window.history.replaceState(
|
|
1866
|
+
null,
|
|
1867
|
+
"",
|
|
1868
|
+
window.location.pathname + window.location.search
|
|
1869
|
+
);
|
|
1474
1870
|
}
|
|
1475
1871
|
return;
|
|
1476
1872
|
}
|
|
@@ -1498,7 +1894,10 @@ var TiquoAuth = class {
|
|
|
1498
1894
|
if (typeof photo === "string" && this.isProfilePhotoBlobUrl(photo)) {
|
|
1499
1895
|
const response = await fetch(photo);
|
|
1500
1896
|
if (!response.ok) {
|
|
1501
|
-
throw new TiquoAuthError(
|
|
1897
|
+
throw new TiquoAuthError(
|
|
1898
|
+
"Failed to read profile photo URI",
|
|
1899
|
+
"INVALID_PROFILE_PHOTO"
|
|
1900
|
+
);
|
|
1502
1901
|
}
|
|
1503
1902
|
return response.blob();
|
|
1504
1903
|
}
|
|
@@ -1566,16 +1965,19 @@ var TiquoAuth = class {
|
|
|
1566
1965
|
this.isRefreshing = true;
|
|
1567
1966
|
this.log("Refreshing access token");
|
|
1568
1967
|
try {
|
|
1569
|
-
const response = await fetch(
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1968
|
+
const response = await fetch(
|
|
1969
|
+
`${this.config.apiEndpoint}/api/client/v1/refresh`,
|
|
1970
|
+
{
|
|
1971
|
+
method: "POST",
|
|
1972
|
+
headers: {
|
|
1973
|
+
"Content-Type": "application/json"
|
|
1974
|
+
},
|
|
1975
|
+
body: JSON.stringify({
|
|
1976
|
+
refresh_token: this.refreshToken
|
|
1977
|
+
}),
|
|
1978
|
+
credentials: "include"
|
|
1979
|
+
}
|
|
1980
|
+
);
|
|
1579
1981
|
if (!response.ok) {
|
|
1580
1982
|
this.log("Token refresh failed, clearing session");
|
|
1581
1983
|
this.clearTokens();
|
|
@@ -1612,13 +2014,16 @@ var TiquoAuth = class {
|
|
|
1612
2014
|
}
|
|
1613
2015
|
try {
|
|
1614
2016
|
await this.refreshTokenIfNeeded();
|
|
1615
|
-
const response = await fetch(
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
"
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2017
|
+
const response = await fetch(
|
|
2018
|
+
`${this.config.apiEndpoint}/api/client/v1/profile`,
|
|
2019
|
+
{
|
|
2020
|
+
method: "GET",
|
|
2021
|
+
headers: {
|
|
2022
|
+
Authorization: `Bearer ${this.accessToken}`
|
|
2023
|
+
},
|
|
2024
|
+
credentials: "include"
|
|
2025
|
+
}
|
|
2026
|
+
);
|
|
1622
2027
|
if (!response.ok) {
|
|
1623
2028
|
this.log("Session invalid, clearing");
|
|
1624
2029
|
this.clearTokens();
|
|
@@ -1636,6 +2041,9 @@ var TiquoAuth = class {
|
|
|
1636
2041
|
if (this.session.user?.id) {
|
|
1637
2042
|
addCustomerUserId(this.session.user.id);
|
|
1638
2043
|
}
|
|
2044
|
+
if (this.session.customer) {
|
|
2045
|
+
this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
|
|
2046
|
+
}
|
|
1639
2047
|
this.notifyListeners();
|
|
1640
2048
|
this.scheduleRefresh();
|
|
1641
2049
|
return this.session;
|
|
@@ -1670,8 +2078,12 @@ var TiquoAuth = class {
|
|
|
1670
2078
|
return;
|
|
1671
2079
|
}
|
|
1672
2080
|
try {
|
|
1673
|
-
const accessToken = localStorage.getItem(
|
|
1674
|
-
|
|
2081
|
+
const accessToken = localStorage.getItem(
|
|
2082
|
+
`${this.config.storagePrefix}access_token`
|
|
2083
|
+
);
|
|
2084
|
+
const refreshToken = localStorage.getItem(
|
|
2085
|
+
`${this.config.storagePrefix}refresh_token`
|
|
2086
|
+
);
|
|
1675
2087
|
if (accessToken) {
|
|
1676
2088
|
this.accessToken = accessToken;
|
|
1677
2089
|
this.refreshToken = refreshToken;
|
|
@@ -1783,7 +2195,12 @@ var TiquoAuth = class {
|
|
|
1783
2195
|
if (message.tabId === this.tabId) return;
|
|
1784
2196
|
if (this.isProcessingTabSync) return;
|
|
1785
2197
|
this.isProcessingTabSync = true;
|
|
1786
|
-
this.log(
|
|
2198
|
+
this.log(
|
|
2199
|
+
"Received tab sync message:",
|
|
2200
|
+
message.type,
|
|
2201
|
+
"from tab:",
|
|
2202
|
+
message.tabId
|
|
2203
|
+
);
|
|
1787
2204
|
try {
|
|
1788
2205
|
switch (message.type) {
|
|
1789
2206
|
case "LOGIN":
|
|
@@ -1810,7 +2227,9 @@ var TiquoAuth = class {
|
|
|
1810
2227
|
}
|
|
1811
2228
|
try {
|
|
1812
2229
|
localStorage.removeItem(`${this.config.storagePrefix}access_token`);
|
|
1813
|
-
localStorage.removeItem(
|
|
2230
|
+
localStorage.removeItem(
|
|
2231
|
+
`${this.config.storagePrefix}refresh_token`
|
|
2232
|
+
);
|
|
1814
2233
|
} catch {
|
|
1815
2234
|
}
|
|
1816
2235
|
this.notifyListeners();
|
|
@@ -1914,6 +2333,7 @@ TiquoPhone.buildPhone = buildPhone;
|
|
|
1914
2333
|
var index_default = TiquoAuth;
|
|
1915
2334
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1916
2335
|
0 && (module.exports = {
|
|
2336
|
+
TiquoAnalytics,
|
|
1917
2337
|
TiquoAuth,
|
|
1918
2338
|
TiquoAuthError,
|
|
1919
2339
|
TiquoCMS,
|