@tiquo/dom-package 1.5.0 → 1.5.2

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,6 +641,333 @@ 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;
893
+ var TiquoCMS = class {
894
+ constructor(config) {
895
+ this.analytics = null;
896
+ if (!config.publicKey) {
897
+ throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
898
+ }
899
+ if (!config.publicKey.startsWith("pk_dom_")) {
900
+ throw new TiquoAuthError(
901
+ "Invalid public key format. Expected pk_dom_xxx",
902
+ "INVALID_PUBLIC_KEY"
903
+ );
904
+ }
905
+ this.config = {
906
+ publicKey: config.publicKey,
907
+ apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
908
+ debug: config.debug || false,
909
+ analytics: config.analytics !== false
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
+ }
918
+ }
919
+ /**
920
+ * Fetch a published CMS page for the website attached to this public key.
921
+ *
922
+ * This intentionally goes through the Tiquo edge API rather than exposing
923
+ * Convex connection details to the consuming website.
924
+ */
925
+ async getPage(request) {
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
+ );
942
+ if (response.status === 404) {
943
+ return null;
944
+ }
945
+ if (!response.ok) {
946
+ let message = "Failed to fetch CMS page";
947
+ try {
948
+ const error = await response.json();
949
+ if (typeof error?.error === "string") message = error.error;
950
+ } catch {
951
+ }
952
+ throw new TiquoAuthError(
953
+ message,
954
+ "CMS_PAGE_FETCH_FAILED",
955
+ response.status
956
+ );
957
+ }
958
+ const result = await response.json();
959
+ if (!result?.success || !result?.data) {
960
+ return null;
961
+ }
962
+ this.log("Fetched CMS page", request.siteSlug, request.pageSlug || "home");
963
+ return result.data;
964
+ }
965
+ log(...args) {
966
+ if (this.config.debug) {
967
+ console.log("[TiquoCMS]", ...args);
968
+ }
969
+ }
970
+ };
644
971
  var TiquoAuthError = class extends Error {
645
972
  constructor(message, code, statusCode) {
646
973
  super(message);
@@ -678,6 +1005,7 @@ var TiquoAuth = class {
678
1005
  this.visibilityHandler = null;
679
1006
  this.iframeObserver = null;
680
1007
  this.injectedIframes = /* @__PURE__ */ new WeakSet();
1008
+ this.analytics = null;
681
1009
  // Multi-tab sync
682
1010
  this.broadcastChannel = null;
683
1011
  this.isProcessingTabSync = false;
@@ -698,10 +1026,19 @@ var TiquoAuth = class {
698
1026
  debug: config.debug || false,
699
1027
  enableTabSync: config.enableTabSync !== false,
700
1028
  // Default true
1029
+ analytics: config.analytics !== false,
701
1030
  accessToken: config.accessToken,
702
1031
  refreshToken: config.refreshToken
703
1032
  };
704
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
+ }
705
1042
  if (this.config.enableTabSync) {
706
1043
  this.initTabSync();
707
1044
  }
@@ -727,7 +1064,11 @@ var TiquoAuth = class {
727
1064
  });
728
1065
  if (!response.ok) {
729
1066
  const error = await response.json().catch(() => ({ error: "Failed to send OTP" }));
730
- 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
+ );
731
1072
  }
732
1073
  const result = await response.json();
733
1074
  return {
@@ -750,7 +1091,11 @@ var TiquoAuth = class {
750
1091
  });
751
1092
  if (!response.ok) {
752
1093
  const error = await response.json().catch(() => ({ error: "Invalid OTP" }));
753
- 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
+ );
754
1099
  }
755
1100
  const result = await response.json();
756
1101
  this.accessToken = result.accessToken;
@@ -760,6 +1105,7 @@ var TiquoAuth = class {
760
1105
  if (this.session?.user?.id) {
761
1106
  addCustomerUserId(this.session.user.id);
762
1107
  }
1108
+ this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
763
1109
  this.broadcastTabSync("LOGIN");
764
1110
  return {
765
1111
  success: true,
@@ -807,11 +1153,22 @@ var TiquoAuth = class {
807
1153
  async updateProfile(updates) {
808
1154
  await this.ensureValidToken();
809
1155
  const normalizedUpdates = { ...updates };
1156
+ const profilePhotoUpload = this.extractUploadableProfilePhoto(
1157
+ normalizedUpdates.profilePhoto
1158
+ );
1159
+ if (profilePhotoUpload) {
1160
+ delete normalizedUpdates.profilePhoto;
1161
+ }
810
1162
  if (normalizedUpdates.phone !== void 0 && normalizedUpdates.phone !== "") {
811
1163
  const normalized = normalizePhone(normalizedUpdates.phone);
812
1164
  const validation = validatePhone(normalizedUpdates.phone);
813
1165
  if (!validation.valid) {
814
- 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
+ );
815
1172
  } else {
816
1173
  this.log("Phone normalized:", normalizedUpdates.phone, "\u2192", normalized);
817
1174
  }
@@ -823,32 +1180,146 @@ var TiquoAuth = class {
823
1180
  const normalized = normalizePhone(p.number);
824
1181
  const validation = validatePhone(p.number);
825
1182
  if (!validation.valid) {
826
- 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
+ );
827
1189
  }
828
1190
  return { ...p, number: normalized || p.number };
829
1191
  });
830
1192
  }
831
1193
  this.log("Updating customer profile:", normalizedUpdates);
832
- const response = await this.request("/api/client/v1/profile", {
833
- method: "PATCH",
834
- body: JSON.stringify(normalizedUpdates)
1194
+ let customer = this.session?.customer || void 0;
1195
+ if (Object.keys(normalizedUpdates).length > 0) {
1196
+ const response = await this.request("/api/client/v1/profile", {
1197
+ method: "PATCH",
1198
+ body: JSON.stringify(normalizedUpdates)
1199
+ });
1200
+ if (!response.ok) {
1201
+ const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
1202
+ throw new TiquoAuthError(
1203
+ error.error || "Failed to update profile",
1204
+ "PROFILE_UPDATE_FAILED",
1205
+ response.status
1206
+ );
1207
+ }
1208
+ const result = await response.json();
1209
+ if (this.session && result.data?.customer) {
1210
+ this.session = {
1211
+ ...this.session,
1212
+ customer: result.data.customer
1213
+ };
1214
+ this.notifyListeners();
1215
+ this.broadcastTabSync("SESSION_UPDATE");
1216
+ }
1217
+ customer = result.data?.customer;
1218
+ }
1219
+ if (profilePhotoUpload) {
1220
+ return this.uploadProfilePhoto(profilePhotoUpload);
1221
+ }
1222
+ return {
1223
+ success: true,
1224
+ customer
1225
+ };
1226
+ }
1227
+ /**
1228
+ * Upload and save the authenticated customer's profile photo.
1229
+ *
1230
+ * Accepts a browser File/Blob, `data:image/...` URI, or `blob:...` URI. The
1231
+ * image is uploaded to Tiquo storage first, and the customer profile is
1232
+ * updated with the URL.
1233
+ */
1234
+ async uploadProfilePhoto(photo) {
1235
+ await this.ensureValidToken();
1236
+ const blob = await this.profilePhotoToBlob(photo);
1237
+ if (!blob.type.startsWith("image/")) {
1238
+ throw new TiquoAuthError(
1239
+ "Profile photo must be an image",
1240
+ "INVALID_PROFILE_PHOTO"
1241
+ );
1242
+ }
1243
+ const uploadUrlResponse = await this.request(
1244
+ "/api/client/v1/profile/photo-upload-url",
1245
+ {
1246
+ method: "POST",
1247
+ body: JSON.stringify({})
1248
+ }
1249
+ );
1250
+ if (!uploadUrlResponse.ok) {
1251
+ const error = await uploadUrlResponse.json().catch(() => ({ error: "Failed to create profile photo upload URL" }));
1252
+ throw new TiquoAuthError(
1253
+ error.error || "Failed to create profile photo upload URL",
1254
+ "PROFILE_PHOTO_UPLOAD_URL_FAILED",
1255
+ uploadUrlResponse.status
1256
+ );
1257
+ }
1258
+ const uploadUrlResult = await uploadUrlResponse.json();
1259
+ const uploadUrl = uploadUrlResult.data?.uploadUrl;
1260
+ if (!uploadUrl) {
1261
+ throw new TiquoAuthError(
1262
+ "Profile photo upload URL was missing",
1263
+ "PROFILE_PHOTO_UPLOAD_URL_FAILED"
1264
+ );
1265
+ }
1266
+ const uploadResponse = await fetch(uploadUrl, {
1267
+ method: "POST",
1268
+ headers: { "Content-Type": blob.type },
1269
+ body: blob
835
1270
  });
836
- if (!response.ok) {
837
- const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
838
- throw new TiquoAuthError(error.error || "Failed to update profile", "PROFILE_UPDATE_FAILED", response.status);
1271
+ if (!uploadResponse.ok) {
1272
+ throw new TiquoAuthError(
1273
+ "Failed to upload profile photo",
1274
+ "PROFILE_PHOTO_UPLOAD_FAILED",
1275
+ uploadResponse.status
1276
+ );
839
1277
  }
840
- const result = await response.json();
841
- if (this.session && result.data?.customer) {
1278
+ const uploadResult = await uploadResponse.json();
1279
+ const storageId = uploadResult.storageId;
1280
+ if (!storageId) {
1281
+ throw new TiquoAuthError(
1282
+ "Profile photo storage ID was missing",
1283
+ "PROFILE_PHOTO_UPLOAD_FAILED"
1284
+ );
1285
+ }
1286
+ const finalizeResponse = await this.request(
1287
+ "/api/client/v1/profile/photo",
1288
+ {
1289
+ method: "POST",
1290
+ body: JSON.stringify({ storageId })
1291
+ }
1292
+ );
1293
+ if (!finalizeResponse.ok) {
1294
+ const error = await finalizeResponse.json().catch(() => ({ error: "Failed to update profile photo" }));
1295
+ throw new TiquoAuthError(
1296
+ error.error || "Failed to update profile photo",
1297
+ "PROFILE_PHOTO_UPDATE_FAILED",
1298
+ finalizeResponse.status
1299
+ );
1300
+ }
1301
+ const finalizeResult = await finalizeResponse.json();
1302
+ const customer = finalizeResult.data?.customer;
1303
+ const profilePhoto = finalizeResult.data?.profilePhoto;
1304
+ if (!customer || !profilePhoto) {
1305
+ throw new TiquoAuthError(
1306
+ "Profile photo update response was incomplete",
1307
+ "PROFILE_PHOTO_UPDATE_FAILED"
1308
+ );
1309
+ }
1310
+ if (this.session && customer) {
842
1311
  this.session = {
843
1312
  ...this.session,
844
- customer: result.data.customer
1313
+ customer
845
1314
  };
846
1315
  this.notifyListeners();
847
1316
  this.broadcastTabSync("SESSION_UPDATE");
848
1317
  }
849
1318
  return {
850
1319
  success: true,
851
- customer: result.data?.customer
1320
+ customer,
1321
+ profilePhoto,
1322
+ storageId
852
1323
  };
853
1324
  }
854
1325
  /**
@@ -909,13 +1380,17 @@ var TiquoAuth = class {
909
1380
  const response = await fetch(url.toString(), {
910
1381
  method: "GET",
911
1382
  headers: {
912
- "Authorization": `Bearer ${this.accessToken}`
1383
+ Authorization: `Bearer ${this.accessToken}`
913
1384
  },
914
1385
  credentials: "include"
915
1386
  });
916
1387
  if (!response.ok) {
917
1388
  const error = await response.json().catch(() => ({ error: "Failed to get orders" }));
918
- 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
+ );
919
1394
  }
920
1395
  const result = await response.json();
921
1396
  return result.data || { orders: [], hasMore: false };
@@ -947,13 +1422,17 @@ var TiquoAuth = class {
947
1422
  const response = await fetch(url.toString(), {
948
1423
  method: "GET",
949
1424
  headers: {
950
- "Authorization": `Bearer ${this.accessToken}`
1425
+ Authorization: `Bearer ${this.accessToken}`
951
1426
  },
952
1427
  credentials: "include"
953
1428
  });
954
1429
  if (!response.ok) {
955
1430
  const error = await response.json().catch(() => ({ error: "Failed to get bookings" }));
956
- 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
+ );
957
1436
  }
958
1437
  const result = await response.json();
959
1438
  return result.data || { bookings: [], hasMore: false };
@@ -995,14 +1474,18 @@ var TiquoAuth = class {
995
1474
  const response = await fetch(url.toString(), {
996
1475
  method: "GET",
997
1476
  headers: {
998
- "Authorization": `Bearer ${this.accessToken}`
1477
+ Authorization: `Bearer ${this.accessToken}`
999
1478
  },
1000
1479
  credentials: "include"
1001
1480
  });
1002
1481
  if (!response.ok) {
1003
1482
  const error = await response.json().catch(() => ({ error: "Failed to get receipt" }));
1004
1483
  const code = response.status === 404 ? "RECEIPT_NOT_AVAILABLE" : "GET_RECEIPT_FAILED";
1005
- throw new TiquoAuthError(error.error || "Failed to get receipt", code, response.status);
1484
+ throw new TiquoAuthError(
1485
+ error.error || "Failed to get receipt",
1486
+ code,
1487
+ response.status
1488
+ );
1006
1489
  }
1007
1490
  const result = await response.json();
1008
1491
  return result.data;
@@ -1027,13 +1510,17 @@ var TiquoAuth = class {
1027
1510
  const response = await fetch(url.toString(), {
1028
1511
  method: "GET",
1029
1512
  headers: {
1030
- "Authorization": `Bearer ${this.accessToken}`
1513
+ Authorization: `Bearer ${this.accessToken}`
1031
1514
  },
1032
1515
  credentials: "include"
1033
1516
  });
1034
1517
  if (!response.ok) {
1035
1518
  const error = await response.json().catch(() => ({ error: "Failed to get enquiries" }));
1036
- throw new TiquoAuthError(error.error || "Failed to get enquiries", "GET_ENQUIRIES_FAILED", response.status);
1519
+ throw new TiquoAuthError(
1520
+ error.error || "Failed to get enquiries",
1521
+ "GET_ENQUIRIES_FAILED",
1522
+ response.status
1523
+ );
1037
1524
  }
1038
1525
  const result = await response.json();
1039
1526
  return result.data || { enquiries: [], hasMore: false };
@@ -1051,16 +1538,23 @@ var TiquoAuth = class {
1051
1538
  async getCompanies() {
1052
1539
  await this.ensureValidToken();
1053
1540
  this.log("Fetching customer companies");
1054
- const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/companies`, {
1055
- method: "GET",
1056
- headers: {
1057
- "Authorization": `Bearer ${this.accessToken}`
1058
- },
1059
- credentials: "include"
1060
- });
1541
+ const response = await fetch(
1542
+ `${this.config.apiEndpoint}/api/client/v1/companies`,
1543
+ {
1544
+ method: "GET",
1545
+ headers: {
1546
+ Authorization: `Bearer ${this.accessToken}`
1547
+ },
1548
+ credentials: "include"
1549
+ }
1550
+ );
1061
1551
  if (!response.ok) {
1062
1552
  const error = await response.json().catch(() => ({ error: "Failed to get companies" }));
1063
- throw new TiquoAuthError(error.error || "Failed to get companies", "GET_COMPANIES_FAILED", response.status);
1553
+ throw new TiquoAuthError(
1554
+ error.error || "Failed to get companies",
1555
+ "GET_COMPANIES_FAILED",
1556
+ response.status
1557
+ );
1064
1558
  }
1065
1559
  const result = await response.json();
1066
1560
  return result.data || { companies: [] };
@@ -1082,19 +1576,25 @@ var TiquoAuth = class {
1082
1576
  async getCompanyColleagues(companyId) {
1083
1577
  await this.ensureValidToken();
1084
1578
  this.log("Fetching company colleagues for:", companyId);
1085
- const url = new URL(`${this.config.apiEndpoint}/api/client/v1/companies/colleagues`);
1579
+ const url = new URL(
1580
+ `${this.config.apiEndpoint}/api/client/v1/companies/colleagues`
1581
+ );
1086
1582
  url.searchParams.set("companyId", companyId);
1087
1583
  const response = await fetch(url.toString(), {
1088
1584
  method: "GET",
1089
1585
  headers: {
1090
- "Authorization": `Bearer ${this.accessToken}`
1586
+ Authorization: `Bearer ${this.accessToken}`
1091
1587
  },
1092
1588
  credentials: "include"
1093
1589
  });
1094
1590
  if (!response.ok) {
1095
1591
  const error = await response.json().catch(() => ({ error: "Failed to get colleagues" }));
1096
1592
  const code = response.status === 403 ? "NOT_COMPANY_ADMIN" : "GET_COLLEAGUES_FAILED";
1097
- throw new TiquoAuthError(error.error || "Failed to get colleagues", code, response.status);
1593
+ throw new TiquoAuthError(
1594
+ error.error || "Failed to get colleagues",
1595
+ code,
1596
+ response.status
1597
+ );
1098
1598
  }
1099
1599
  const result = await response.json();
1100
1600
  return result.data;
@@ -1115,13 +1615,17 @@ var TiquoAuth = class {
1115
1615
  });
1116
1616
  if (!response.ok) {
1117
1617
  const error = await response.json().catch(() => ({ error: "Failed to generate token" }));
1118
- throw new TiquoAuthError(error.error || "Failed to generate token", "IFRAME_TOKEN_FAILED", response.status);
1618
+ throw new TiquoAuthError(
1619
+ error.error || "Failed to generate token",
1620
+ "IFRAME_TOKEN_FAILED",
1621
+ response.status
1622
+ );
1119
1623
  }
1120
1624
  return response.json();
1121
1625
  }
1122
1626
  /**
1123
1627
  * Embed a customer flow with automatic authentication
1124
- *
1628
+ *
1125
1629
  * @param flowUrl - The URL of the customer flow (book.tiquo.app/...)
1126
1630
  * @param container - Container element or selector
1127
1631
  * @param options - Optional iframe configuration
@@ -1129,7 +1633,10 @@ var TiquoAuth = class {
1129
1633
  async embedCustomerFlow(flowUrl, container, options) {
1130
1634
  const containerEl = typeof container === "string" ? document.querySelector(container) : container;
1131
1635
  if (!containerEl) {
1132
- throw new TiquoAuthError("Container element not found", "CONTAINER_NOT_FOUND");
1636
+ throw new TiquoAuthError(
1637
+ "Container element not found",
1638
+ "CONTAINER_NOT_FOUND"
1639
+ );
1133
1640
  }
1134
1641
  let authToken;
1135
1642
  if (this.accessToken) {
@@ -1155,7 +1662,10 @@ var TiquoAuth = class {
1155
1662
  iframe.addEventListener("load", options.onLoad);
1156
1663
  }
1157
1664
  if (options?.onError) {
1158
- iframe.addEventListener("error", () => options.onError(new Error("Failed to load iframe")));
1665
+ iframe.addEventListener(
1666
+ "error",
1667
+ () => options.onError(new Error("Failed to load iframe"))
1668
+ );
1159
1669
  }
1160
1670
  containerEl.innerHTML = "";
1161
1671
  containerEl.appendChild(iframe);
@@ -1284,12 +1794,50 @@ var TiquoAuth = class {
1284
1794
  this.accessToken = accessToken;
1285
1795
  this.refreshToken = params.get("refresh_token");
1286
1796
  if (window.history?.replaceState) {
1287
- window.history.replaceState(null, "", window.location.pathname + window.location.search);
1797
+ window.history.replaceState(
1798
+ null,
1799
+ "",
1800
+ window.location.pathname + window.location.search
1801
+ );
1288
1802
  }
1289
1803
  return;
1290
1804
  }
1291
1805
  }
1292
1806
  }
1807
+ extractUploadableProfilePhoto(profilePhoto) {
1808
+ if (typeof Blob !== "undefined" && profilePhoto instanceof Blob) {
1809
+ return profilePhoto;
1810
+ }
1811
+ if (typeof profilePhoto === "string") {
1812
+ const trimmed = profilePhoto.trim();
1813
+ if (trimmed.startsWith("data:") || trimmed.startsWith("blob:")) {
1814
+ return trimmed;
1815
+ }
1816
+ }
1817
+ return null;
1818
+ }
1819
+ isProfilePhotoBlobUrl(photo) {
1820
+ return photo.trim().startsWith("data:") || photo.trim().startsWith("blob:");
1821
+ }
1822
+ async profilePhotoToBlob(photo) {
1823
+ if (typeof Blob !== "undefined" && photo instanceof Blob) {
1824
+ return photo;
1825
+ }
1826
+ if (typeof photo === "string" && this.isProfilePhotoBlobUrl(photo)) {
1827
+ const response = await fetch(photo);
1828
+ if (!response.ok) {
1829
+ throw new TiquoAuthError(
1830
+ "Failed to read profile photo URI",
1831
+ "INVALID_PROFILE_PHOTO"
1832
+ );
1833
+ }
1834
+ return response.blob();
1835
+ }
1836
+ throw new TiquoAuthError(
1837
+ "uploadProfilePhoto expects a File, Blob, data URI, or blob URI. Use updateProfile({ profilePhoto: url }) for remote URLs.",
1838
+ "INVALID_PROFILE_PHOTO"
1839
+ );
1840
+ }
1293
1841
  async request(path, options) {
1294
1842
  const url = `${this.config.apiEndpoint}${path}`;
1295
1843
  const headers = {
@@ -1349,16 +1897,19 @@ var TiquoAuth = class {
1349
1897
  this.isRefreshing = true;
1350
1898
  this.log("Refreshing access token");
1351
1899
  try {
1352
- const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/refresh`, {
1353
- method: "POST",
1354
- headers: {
1355
- "Content-Type": "application/json"
1356
- },
1357
- body: JSON.stringify({
1358
- refresh_token: this.refreshToken
1359
- }),
1360
- credentials: "include"
1361
- });
1900
+ const response = await fetch(
1901
+ `${this.config.apiEndpoint}/api/client/v1/refresh`,
1902
+ {
1903
+ method: "POST",
1904
+ headers: {
1905
+ "Content-Type": "application/json"
1906
+ },
1907
+ body: JSON.stringify({
1908
+ refresh_token: this.refreshToken
1909
+ }),
1910
+ credentials: "include"
1911
+ }
1912
+ );
1362
1913
  if (!response.ok) {
1363
1914
  this.log("Token refresh failed, clearing session");
1364
1915
  this.clearTokens();
@@ -1395,13 +1946,16 @@ var TiquoAuth = class {
1395
1946
  }
1396
1947
  try {
1397
1948
  await this.refreshTokenIfNeeded();
1398
- const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/profile`, {
1399
- method: "GET",
1400
- headers: {
1401
- "Authorization": `Bearer ${this.accessToken}`
1402
- },
1403
- credentials: "include"
1404
- });
1949
+ const response = await fetch(
1950
+ `${this.config.apiEndpoint}/api/client/v1/profile`,
1951
+ {
1952
+ method: "GET",
1953
+ headers: {
1954
+ Authorization: `Bearer ${this.accessToken}`
1955
+ },
1956
+ credentials: "include"
1957
+ }
1958
+ );
1405
1959
  if (!response.ok) {
1406
1960
  this.log("Session invalid, clearing");
1407
1961
  this.clearTokens();
@@ -1419,6 +1973,9 @@ var TiquoAuth = class {
1419
1973
  if (this.session.user?.id) {
1420
1974
  addCustomerUserId(this.session.user.id);
1421
1975
  }
1976
+ if (this.session.customer) {
1977
+ this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
1978
+ }
1422
1979
  this.notifyListeners();
1423
1980
  this.scheduleRefresh();
1424
1981
  return this.session;
@@ -1453,8 +2010,12 @@ var TiquoAuth = class {
1453
2010
  return;
1454
2011
  }
1455
2012
  try {
1456
- const accessToken = localStorage.getItem(`${this.config.storagePrefix}access_token`);
1457
- const refreshToken = localStorage.getItem(`${this.config.storagePrefix}refresh_token`);
2013
+ const accessToken = localStorage.getItem(
2014
+ `${this.config.storagePrefix}access_token`
2015
+ );
2016
+ const refreshToken = localStorage.getItem(
2017
+ `${this.config.storagePrefix}refresh_token`
2018
+ );
1458
2019
  if (accessToken) {
1459
2020
  this.accessToken = accessToken;
1460
2021
  this.refreshToken = refreshToken;
@@ -1566,7 +2127,12 @@ var TiquoAuth = class {
1566
2127
  if (message.tabId === this.tabId) return;
1567
2128
  if (this.isProcessingTabSync) return;
1568
2129
  this.isProcessingTabSync = true;
1569
- this.log("Received tab sync message:", message.type, "from tab:", message.tabId);
2130
+ this.log(
2131
+ "Received tab sync message:",
2132
+ message.type,
2133
+ "from tab:",
2134
+ message.tabId
2135
+ );
1570
2136
  try {
1571
2137
  switch (message.type) {
1572
2138
  case "LOGIN":
@@ -1593,7 +2159,9 @@ var TiquoAuth = class {
1593
2159
  }
1594
2160
  try {
1595
2161
  localStorage.removeItem(`${this.config.storagePrefix}access_token`);
1596
- localStorage.removeItem(`${this.config.storagePrefix}refresh_token`);
2162
+ localStorage.removeItem(
2163
+ `${this.config.storagePrefix}refresh_token`
2164
+ );
1597
2165
  } catch {
1598
2166
  }
1599
2167
  this.notifyListeners();
@@ -1665,6 +2233,7 @@ function useTiquoAuth(auth) {
1665
2233
  verifyOTP: (email, otp) => auth.verifyOTP(email, otp),
1666
2234
  logout: () => auth.logout(),
1667
2235
  updateProfile: (updates) => auth.updateProfile(updates),
2236
+ uploadProfilePhoto: (photo) => auth.uploadProfilePhoto(photo),
1668
2237
  getOrders: (options) => auth.getOrders(options),
1669
2238
  getBookings: (options) => auth.getBookings(options),
1670
2239
  getUpcomingBookings: (options) => auth.getUpcomingBookings(options),
@@ -1695,8 +2264,10 @@ TiquoPhone.getDialCode = getDialCode;
1695
2264
  TiquoPhone.buildPhone = buildPhone;
1696
2265
  var index_default = TiquoAuth;
1697
2266
  export {
2267
+ TiquoAnalytics,
1698
2268
  TiquoAuth,
1699
2269
  TiquoAuthError,
2270
+ TiquoCMS,
1700
2271
  TiquoPhone,
1701
2272
  addCustomerUserId,
1702
2273
  clearCachedEmail,