@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/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(`${this.config.apiEndpoint}/api/dom-cms/page`, {
669
- method: "POST",
670
- headers: {
671
- "Content-Type": "text/plain;charset=UTF-8"
672
- },
673
- credentials: "include",
674
- body: JSON.stringify({
675
- publicKey: this.config.publicKey,
676
- siteSlug: request.siteSlug,
677
- pageSlug: request.pageSlug || "home",
678
- templateId: request.templateId
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(message, "CMS_PAGE_FETCH_FAILED", response.status);
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(error.error || "Failed to send OTP", "OTP_SEND_FAILED", response.status);
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(error.error || "Invalid OTP", "OTP_VERIFY_FAILED", response.status);
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(normalizedUpdates.profilePhoto);
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("\u26A0\uFE0F Phone validation warning:", validation.reason, "- Original:", normalizedUpdates.phone);
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("\u26A0\uFE0F Phone validation warning:", validation.reason, "- Original:", p.number);
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(error.error || "Failed to update profile", "PROFILE_UPDATE_FAILED", response.status);
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("Profile photo must be an image", "INVALID_PROFILE_PHOTO");
1238
+ throw new TiquoAuthError(
1239
+ "Profile photo must be an image",
1240
+ "INVALID_PROFILE_PHOTO"
1241
+ );
939
1242
  }
940
- const uploadUrlResponse = await this.request("/api/client/v1/profile/photo-upload-url", {
941
- method: "POST",
942
- body: JSON.stringify({})
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("Profile photo upload URL was missing", "PROFILE_PHOTO_UPLOAD_URL_FAILED");
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("Failed to upload profile photo", "PROFILE_PHOTO_UPLOAD_FAILED", uploadResponse.status);
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("Profile photo storage ID was missing", "PROFILE_PHOTO_UPLOAD_FAILED");
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("/api/client/v1/profile/photo", {
971
- method: "POST",
972
- body: JSON.stringify({ storageId })
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("Profile photo update response was incomplete", "PROFILE_PHOTO_UPDATE_FAILED");
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
- "Authorization": `Bearer ${this.accessToken}`
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(error.error || "Failed to get orders", "GET_ORDERS_FAILED", response.status);
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
- "Authorization": `Bearer ${this.accessToken}`
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(error.error || "Failed to get bookings", "GET_BOOKINGS_FAILED", response.status);
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
- "Authorization": `Bearer ${this.accessToken}`
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(error.error || "Failed to get receipt", code, response.status);
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
- "Authorization": `Bearer ${this.accessToken}`
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(error.error || "Failed to get enquiries", "GET_ENQUIRIES_FAILED", response.status);
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(`${this.config.apiEndpoint}/api/client/v1/companies`, {
1204
- method: "GET",
1205
- headers: {
1206
- "Authorization": `Bearer ${this.accessToken}`
1207
- },
1208
- credentials: "include"
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(error.error || "Failed to get companies", "GET_COMPANIES_FAILED", response.status);
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(`${this.config.apiEndpoint}/api/client/v1/companies/colleagues`);
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
- "Authorization": `Bearer ${this.accessToken}`
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(error.error || "Failed to get colleagues", code, response.status);
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(error.error || "Failed to generate token", "IFRAME_TOKEN_FAILED", response.status);
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("Container element not found", "CONTAINER_NOT_FOUND");
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("error", () => options.onError(new Error("Failed to load iframe")));
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(null, "", window.location.pathname + window.location.search);
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("Failed to read profile photo URI", "INVALID_PROFILE_PHOTO");
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(`${this.config.apiEndpoint}/api/client/v1/refresh`, {
1533
- method: "POST",
1534
- headers: {
1535
- "Content-Type": "application/json"
1536
- },
1537
- body: JSON.stringify({
1538
- refresh_token: this.refreshToken
1539
- }),
1540
- credentials: "include"
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(`${this.config.apiEndpoint}/api/client/v1/profile`, {
1579
- method: "GET",
1580
- headers: {
1581
- "Authorization": `Bearer ${this.accessToken}`
1582
- },
1583
- credentials: "include"
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(`${this.config.storagePrefix}access_token`);
1637
- const refreshToken = localStorage.getItem(`${this.config.storagePrefix}refresh_token`);
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("Received tab sync message:", message.type, "from tab:", message.tabId);
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(`${this.config.storagePrefix}refresh_token`);
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,