@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.js CHANGED
@@ -20,8 +20,10 @@ 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,
26
+ TiquoCMS: () => TiquoCMS,
25
27
  TiquoPhone: () => TiquoPhone,
26
28
  addCustomerUserId: () => addCustomerUserId,
27
29
  clearCachedEmail: () => clearCachedEmail,
@@ -677,6 +679,333 @@ function printTiquoBranding() {
677
679
  }
678
680
  }
679
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;
931
+ var TiquoCMS = class {
932
+ constructor(config) {
933
+ this.analytics = null;
934
+ if (!config.publicKey) {
935
+ throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
936
+ }
937
+ if (!config.publicKey.startsWith("pk_dom_")) {
938
+ throw new TiquoAuthError(
939
+ "Invalid public key format. Expected pk_dom_xxx",
940
+ "INVALID_PUBLIC_KEY"
941
+ );
942
+ }
943
+ this.config = {
944
+ publicKey: config.publicKey,
945
+ apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
946
+ debug: config.debug || false,
947
+ analytics: config.analytics !== false
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
+ }
956
+ }
957
+ /**
958
+ * Fetch a published CMS page for the website attached to this public key.
959
+ *
960
+ * This intentionally goes through the Tiquo edge API rather than exposing
961
+ * Convex connection details to the consuming website.
962
+ */
963
+ async getPage(request) {
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
+ );
980
+ if (response.status === 404) {
981
+ return null;
982
+ }
983
+ if (!response.ok) {
984
+ let message = "Failed to fetch CMS page";
985
+ try {
986
+ const error = await response.json();
987
+ if (typeof error?.error === "string") message = error.error;
988
+ } catch {
989
+ }
990
+ throw new TiquoAuthError(
991
+ message,
992
+ "CMS_PAGE_FETCH_FAILED",
993
+ response.status
994
+ );
995
+ }
996
+ const result = await response.json();
997
+ if (!result?.success || !result?.data) {
998
+ return null;
999
+ }
1000
+ this.log("Fetched CMS page", request.siteSlug, request.pageSlug || "home");
1001
+ return result.data;
1002
+ }
1003
+ log(...args) {
1004
+ if (this.config.debug) {
1005
+ console.log("[TiquoCMS]", ...args);
1006
+ }
1007
+ }
1008
+ };
680
1009
  var TiquoAuthError = class extends Error {
681
1010
  constructor(message, code, statusCode) {
682
1011
  super(message);
@@ -714,6 +1043,7 @@ var TiquoAuth = class {
714
1043
  this.visibilityHandler = null;
715
1044
  this.iframeObserver = null;
716
1045
  this.injectedIframes = /* @__PURE__ */ new WeakSet();
1046
+ this.analytics = null;
717
1047
  // Multi-tab sync
718
1048
  this.broadcastChannel = null;
719
1049
  this.isProcessingTabSync = false;
@@ -734,10 +1064,19 @@ var TiquoAuth = class {
734
1064
  debug: config.debug || false,
735
1065
  enableTabSync: config.enableTabSync !== false,
736
1066
  // Default true
1067
+ analytics: config.analytics !== false,
737
1068
  accessToken: config.accessToken,
738
1069
  refreshToken: config.refreshToken
739
1070
  };
740
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
+ }
741
1080
  if (this.config.enableTabSync) {
742
1081
  this.initTabSync();
743
1082
  }
@@ -763,7 +1102,11 @@ var TiquoAuth = class {
763
1102
  });
764
1103
  if (!response.ok) {
765
1104
  const error = await response.json().catch(() => ({ error: "Failed to send OTP" }));
766
- throw new TiquoAuthError(error.error || "Failed to send OTP", "OTP_SEND_FAILED", response.status);
1105
+ throw new TiquoAuthError(
1106
+ error.error || "Failed to send OTP",
1107
+ "OTP_SEND_FAILED",
1108
+ response.status
1109
+ );
767
1110
  }
768
1111
  const result = await response.json();
769
1112
  return {
@@ -786,7 +1129,11 @@ var TiquoAuth = class {
786
1129
  });
787
1130
  if (!response.ok) {
788
1131
  const error = await response.json().catch(() => ({ error: "Invalid OTP" }));
789
- throw new TiquoAuthError(error.error || "Invalid OTP", "OTP_VERIFY_FAILED", response.status);
1132
+ throw new TiquoAuthError(
1133
+ error.error || "Invalid OTP",
1134
+ "OTP_VERIFY_FAILED",
1135
+ response.status
1136
+ );
790
1137
  }
791
1138
  const result = await response.json();
792
1139
  this.accessToken = result.accessToken;
@@ -796,6 +1143,7 @@ var TiquoAuth = class {
796
1143
  if (this.session?.user?.id) {
797
1144
  addCustomerUserId(this.session.user.id);
798
1145
  }
1146
+ this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
799
1147
  this.broadcastTabSync("LOGIN");
800
1148
  return {
801
1149
  success: true,
@@ -843,11 +1191,22 @@ var TiquoAuth = class {
843
1191
  async updateProfile(updates) {
844
1192
  await this.ensureValidToken();
845
1193
  const normalizedUpdates = { ...updates };
1194
+ const profilePhotoUpload = this.extractUploadableProfilePhoto(
1195
+ normalizedUpdates.profilePhoto
1196
+ );
1197
+ if (profilePhotoUpload) {
1198
+ delete normalizedUpdates.profilePhoto;
1199
+ }
846
1200
  if (normalizedUpdates.phone !== void 0 && normalizedUpdates.phone !== "") {
847
1201
  const normalized = normalizePhone(normalizedUpdates.phone);
848
1202
  const validation = validatePhone(normalizedUpdates.phone);
849
1203
  if (!validation.valid) {
850
- this.log("\u26A0\uFE0F Phone validation warning:", validation.reason, "- Original:", normalizedUpdates.phone);
1204
+ this.log(
1205
+ "\u26A0\uFE0F Phone validation warning:",
1206
+ validation.reason,
1207
+ "- Original:",
1208
+ normalizedUpdates.phone
1209
+ );
851
1210
  } else {
852
1211
  this.log("Phone normalized:", normalizedUpdates.phone, "\u2192", normalized);
853
1212
  }
@@ -859,32 +1218,146 @@ var TiquoAuth = class {
859
1218
  const normalized = normalizePhone(p.number);
860
1219
  const validation = validatePhone(p.number);
861
1220
  if (!validation.valid) {
862
- this.log("\u26A0\uFE0F Phone validation warning:", validation.reason, "- Original:", p.number);
1221
+ this.log(
1222
+ "\u26A0\uFE0F Phone validation warning:",
1223
+ validation.reason,
1224
+ "- Original:",
1225
+ p.number
1226
+ );
863
1227
  }
864
1228
  return { ...p, number: normalized || p.number };
865
1229
  });
866
1230
  }
867
1231
  this.log("Updating customer profile:", normalizedUpdates);
868
- const response = await this.request("/api/client/v1/profile", {
869
- method: "PATCH",
870
- body: JSON.stringify(normalizedUpdates)
1232
+ let customer = this.session?.customer || void 0;
1233
+ if (Object.keys(normalizedUpdates).length > 0) {
1234
+ const response = await this.request("/api/client/v1/profile", {
1235
+ method: "PATCH",
1236
+ body: JSON.stringify(normalizedUpdates)
1237
+ });
1238
+ if (!response.ok) {
1239
+ const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
1240
+ throw new TiquoAuthError(
1241
+ error.error || "Failed to update profile",
1242
+ "PROFILE_UPDATE_FAILED",
1243
+ response.status
1244
+ );
1245
+ }
1246
+ const result = await response.json();
1247
+ if (this.session && result.data?.customer) {
1248
+ this.session = {
1249
+ ...this.session,
1250
+ customer: result.data.customer
1251
+ };
1252
+ this.notifyListeners();
1253
+ this.broadcastTabSync("SESSION_UPDATE");
1254
+ }
1255
+ customer = result.data?.customer;
1256
+ }
1257
+ if (profilePhotoUpload) {
1258
+ return this.uploadProfilePhoto(profilePhotoUpload);
1259
+ }
1260
+ return {
1261
+ success: true,
1262
+ customer
1263
+ };
1264
+ }
1265
+ /**
1266
+ * Upload and save the authenticated customer's profile photo.
1267
+ *
1268
+ * Accepts a browser File/Blob, `data:image/...` URI, or `blob:...` URI. The
1269
+ * image is uploaded to Tiquo storage first, and the customer profile is
1270
+ * updated with the URL.
1271
+ */
1272
+ async uploadProfilePhoto(photo) {
1273
+ await this.ensureValidToken();
1274
+ const blob = await this.profilePhotoToBlob(photo);
1275
+ if (!blob.type.startsWith("image/")) {
1276
+ throw new TiquoAuthError(
1277
+ "Profile photo must be an image",
1278
+ "INVALID_PROFILE_PHOTO"
1279
+ );
1280
+ }
1281
+ const uploadUrlResponse = await this.request(
1282
+ "/api/client/v1/profile/photo-upload-url",
1283
+ {
1284
+ method: "POST",
1285
+ body: JSON.stringify({})
1286
+ }
1287
+ );
1288
+ if (!uploadUrlResponse.ok) {
1289
+ const error = await uploadUrlResponse.json().catch(() => ({ error: "Failed to create profile photo upload URL" }));
1290
+ throw new TiquoAuthError(
1291
+ error.error || "Failed to create profile photo upload URL",
1292
+ "PROFILE_PHOTO_UPLOAD_URL_FAILED",
1293
+ uploadUrlResponse.status
1294
+ );
1295
+ }
1296
+ const uploadUrlResult = await uploadUrlResponse.json();
1297
+ const uploadUrl = uploadUrlResult.data?.uploadUrl;
1298
+ if (!uploadUrl) {
1299
+ throw new TiquoAuthError(
1300
+ "Profile photo upload URL was missing",
1301
+ "PROFILE_PHOTO_UPLOAD_URL_FAILED"
1302
+ );
1303
+ }
1304
+ const uploadResponse = await fetch(uploadUrl, {
1305
+ method: "POST",
1306
+ headers: { "Content-Type": blob.type },
1307
+ body: blob
871
1308
  });
872
- if (!response.ok) {
873
- const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
874
- throw new TiquoAuthError(error.error || "Failed to update profile", "PROFILE_UPDATE_FAILED", response.status);
1309
+ if (!uploadResponse.ok) {
1310
+ throw new TiquoAuthError(
1311
+ "Failed to upload profile photo",
1312
+ "PROFILE_PHOTO_UPLOAD_FAILED",
1313
+ uploadResponse.status
1314
+ );
875
1315
  }
876
- const result = await response.json();
877
- if (this.session && result.data?.customer) {
1316
+ const uploadResult = await uploadResponse.json();
1317
+ const storageId = uploadResult.storageId;
1318
+ if (!storageId) {
1319
+ throw new TiquoAuthError(
1320
+ "Profile photo storage ID was missing",
1321
+ "PROFILE_PHOTO_UPLOAD_FAILED"
1322
+ );
1323
+ }
1324
+ const finalizeResponse = await this.request(
1325
+ "/api/client/v1/profile/photo",
1326
+ {
1327
+ method: "POST",
1328
+ body: JSON.stringify({ storageId })
1329
+ }
1330
+ );
1331
+ if (!finalizeResponse.ok) {
1332
+ const error = await finalizeResponse.json().catch(() => ({ error: "Failed to update profile photo" }));
1333
+ throw new TiquoAuthError(
1334
+ error.error || "Failed to update profile photo",
1335
+ "PROFILE_PHOTO_UPDATE_FAILED",
1336
+ finalizeResponse.status
1337
+ );
1338
+ }
1339
+ const finalizeResult = await finalizeResponse.json();
1340
+ const customer = finalizeResult.data?.customer;
1341
+ const profilePhoto = finalizeResult.data?.profilePhoto;
1342
+ if (!customer || !profilePhoto) {
1343
+ throw new TiquoAuthError(
1344
+ "Profile photo update response was incomplete",
1345
+ "PROFILE_PHOTO_UPDATE_FAILED"
1346
+ );
1347
+ }
1348
+ if (this.session && customer) {
878
1349
  this.session = {
879
1350
  ...this.session,
880
- customer: result.data.customer
1351
+ customer
881
1352
  };
882
1353
  this.notifyListeners();
883
1354
  this.broadcastTabSync("SESSION_UPDATE");
884
1355
  }
885
1356
  return {
886
1357
  success: true,
887
- customer: result.data?.customer
1358
+ customer,
1359
+ profilePhoto,
1360
+ storageId
888
1361
  };
889
1362
  }
890
1363
  /**
@@ -945,13 +1418,17 @@ var TiquoAuth = class {
945
1418
  const response = await fetch(url.toString(), {
946
1419
  method: "GET",
947
1420
  headers: {
948
- "Authorization": `Bearer ${this.accessToken}`
1421
+ Authorization: `Bearer ${this.accessToken}`
949
1422
  },
950
1423
  credentials: "include"
951
1424
  });
952
1425
  if (!response.ok) {
953
1426
  const error = await response.json().catch(() => ({ error: "Failed to get orders" }));
954
- throw new TiquoAuthError(error.error || "Failed to get orders", "GET_ORDERS_FAILED", response.status);
1427
+ throw new TiquoAuthError(
1428
+ error.error || "Failed to get orders",
1429
+ "GET_ORDERS_FAILED",
1430
+ response.status
1431
+ );
955
1432
  }
956
1433
  const result = await response.json();
957
1434
  return result.data || { orders: [], hasMore: false };
@@ -983,13 +1460,17 @@ var TiquoAuth = class {
983
1460
  const response = await fetch(url.toString(), {
984
1461
  method: "GET",
985
1462
  headers: {
986
- "Authorization": `Bearer ${this.accessToken}`
1463
+ Authorization: `Bearer ${this.accessToken}`
987
1464
  },
988
1465
  credentials: "include"
989
1466
  });
990
1467
  if (!response.ok) {
991
1468
  const error = await response.json().catch(() => ({ error: "Failed to get bookings" }));
992
- throw new TiquoAuthError(error.error || "Failed to get bookings", "GET_BOOKINGS_FAILED", response.status);
1469
+ throw new TiquoAuthError(
1470
+ error.error || "Failed to get bookings",
1471
+ "GET_BOOKINGS_FAILED",
1472
+ response.status
1473
+ );
993
1474
  }
994
1475
  const result = await response.json();
995
1476
  return result.data || { bookings: [], hasMore: false };
@@ -1031,14 +1512,18 @@ var TiquoAuth = class {
1031
1512
  const response = await fetch(url.toString(), {
1032
1513
  method: "GET",
1033
1514
  headers: {
1034
- "Authorization": `Bearer ${this.accessToken}`
1515
+ Authorization: `Bearer ${this.accessToken}`
1035
1516
  },
1036
1517
  credentials: "include"
1037
1518
  });
1038
1519
  if (!response.ok) {
1039
1520
  const error = await response.json().catch(() => ({ error: "Failed to get receipt" }));
1040
1521
  const code = response.status === 404 ? "RECEIPT_NOT_AVAILABLE" : "GET_RECEIPT_FAILED";
1041
- throw new TiquoAuthError(error.error || "Failed to get receipt", code, response.status);
1522
+ throw new TiquoAuthError(
1523
+ error.error || "Failed to get receipt",
1524
+ code,
1525
+ response.status
1526
+ );
1042
1527
  }
1043
1528
  const result = await response.json();
1044
1529
  return result.data;
@@ -1063,13 +1548,17 @@ var TiquoAuth = class {
1063
1548
  const response = await fetch(url.toString(), {
1064
1549
  method: "GET",
1065
1550
  headers: {
1066
- "Authorization": `Bearer ${this.accessToken}`
1551
+ Authorization: `Bearer ${this.accessToken}`
1067
1552
  },
1068
1553
  credentials: "include"
1069
1554
  });
1070
1555
  if (!response.ok) {
1071
1556
  const error = await response.json().catch(() => ({ error: "Failed to get enquiries" }));
1072
- throw new TiquoAuthError(error.error || "Failed to get enquiries", "GET_ENQUIRIES_FAILED", response.status);
1557
+ throw new TiquoAuthError(
1558
+ error.error || "Failed to get enquiries",
1559
+ "GET_ENQUIRIES_FAILED",
1560
+ response.status
1561
+ );
1073
1562
  }
1074
1563
  const result = await response.json();
1075
1564
  return result.data || { enquiries: [], hasMore: false };
@@ -1087,16 +1576,23 @@ var TiquoAuth = class {
1087
1576
  async getCompanies() {
1088
1577
  await this.ensureValidToken();
1089
1578
  this.log("Fetching customer companies");
1090
- const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/companies`, {
1091
- method: "GET",
1092
- headers: {
1093
- "Authorization": `Bearer ${this.accessToken}`
1094
- },
1095
- credentials: "include"
1096
- });
1579
+ const response = await fetch(
1580
+ `${this.config.apiEndpoint}/api/client/v1/companies`,
1581
+ {
1582
+ method: "GET",
1583
+ headers: {
1584
+ Authorization: `Bearer ${this.accessToken}`
1585
+ },
1586
+ credentials: "include"
1587
+ }
1588
+ );
1097
1589
  if (!response.ok) {
1098
1590
  const error = await response.json().catch(() => ({ error: "Failed to get companies" }));
1099
- throw new TiquoAuthError(error.error || "Failed to get companies", "GET_COMPANIES_FAILED", response.status);
1591
+ throw new TiquoAuthError(
1592
+ error.error || "Failed to get companies",
1593
+ "GET_COMPANIES_FAILED",
1594
+ response.status
1595
+ );
1100
1596
  }
1101
1597
  const result = await response.json();
1102
1598
  return result.data || { companies: [] };
@@ -1118,19 +1614,25 @@ var TiquoAuth = class {
1118
1614
  async getCompanyColleagues(companyId) {
1119
1615
  await this.ensureValidToken();
1120
1616
  this.log("Fetching company colleagues for:", companyId);
1121
- const url = new URL(`${this.config.apiEndpoint}/api/client/v1/companies/colleagues`);
1617
+ const url = new URL(
1618
+ `${this.config.apiEndpoint}/api/client/v1/companies/colleagues`
1619
+ );
1122
1620
  url.searchParams.set("companyId", companyId);
1123
1621
  const response = await fetch(url.toString(), {
1124
1622
  method: "GET",
1125
1623
  headers: {
1126
- "Authorization": `Bearer ${this.accessToken}`
1624
+ Authorization: `Bearer ${this.accessToken}`
1127
1625
  },
1128
1626
  credentials: "include"
1129
1627
  });
1130
1628
  if (!response.ok) {
1131
1629
  const error = await response.json().catch(() => ({ error: "Failed to get colleagues" }));
1132
1630
  const code = response.status === 403 ? "NOT_COMPANY_ADMIN" : "GET_COLLEAGUES_FAILED";
1133
- throw new TiquoAuthError(error.error || "Failed to get colleagues", code, response.status);
1631
+ throw new TiquoAuthError(
1632
+ error.error || "Failed to get colleagues",
1633
+ code,
1634
+ response.status
1635
+ );
1134
1636
  }
1135
1637
  const result = await response.json();
1136
1638
  return result.data;
@@ -1151,13 +1653,17 @@ var TiquoAuth = class {
1151
1653
  });
1152
1654
  if (!response.ok) {
1153
1655
  const error = await response.json().catch(() => ({ error: "Failed to generate token" }));
1154
- throw new TiquoAuthError(error.error || "Failed to generate token", "IFRAME_TOKEN_FAILED", response.status);
1656
+ throw new TiquoAuthError(
1657
+ error.error || "Failed to generate token",
1658
+ "IFRAME_TOKEN_FAILED",
1659
+ response.status
1660
+ );
1155
1661
  }
1156
1662
  return response.json();
1157
1663
  }
1158
1664
  /**
1159
1665
  * Embed a customer flow with automatic authentication
1160
- *
1666
+ *
1161
1667
  * @param flowUrl - The URL of the customer flow (book.tiquo.app/...)
1162
1668
  * @param container - Container element or selector
1163
1669
  * @param options - Optional iframe configuration
@@ -1165,7 +1671,10 @@ var TiquoAuth = class {
1165
1671
  async embedCustomerFlow(flowUrl, container, options) {
1166
1672
  const containerEl = typeof container === "string" ? document.querySelector(container) : container;
1167
1673
  if (!containerEl) {
1168
- throw new TiquoAuthError("Container element not found", "CONTAINER_NOT_FOUND");
1674
+ throw new TiquoAuthError(
1675
+ "Container element not found",
1676
+ "CONTAINER_NOT_FOUND"
1677
+ );
1169
1678
  }
1170
1679
  let authToken;
1171
1680
  if (this.accessToken) {
@@ -1191,7 +1700,10 @@ var TiquoAuth = class {
1191
1700
  iframe.addEventListener("load", options.onLoad);
1192
1701
  }
1193
1702
  if (options?.onError) {
1194
- iframe.addEventListener("error", () => options.onError(new Error("Failed to load iframe")));
1703
+ iframe.addEventListener(
1704
+ "error",
1705
+ () => options.onError(new Error("Failed to load iframe"))
1706
+ );
1195
1707
  }
1196
1708
  containerEl.innerHTML = "";
1197
1709
  containerEl.appendChild(iframe);
@@ -1320,12 +1832,50 @@ var TiquoAuth = class {
1320
1832
  this.accessToken = accessToken;
1321
1833
  this.refreshToken = params.get("refresh_token");
1322
1834
  if (window.history?.replaceState) {
1323
- window.history.replaceState(null, "", window.location.pathname + window.location.search);
1835
+ window.history.replaceState(
1836
+ null,
1837
+ "",
1838
+ window.location.pathname + window.location.search
1839
+ );
1324
1840
  }
1325
1841
  return;
1326
1842
  }
1327
1843
  }
1328
1844
  }
1845
+ extractUploadableProfilePhoto(profilePhoto) {
1846
+ if (typeof Blob !== "undefined" && profilePhoto instanceof Blob) {
1847
+ return profilePhoto;
1848
+ }
1849
+ if (typeof profilePhoto === "string") {
1850
+ const trimmed = profilePhoto.trim();
1851
+ if (trimmed.startsWith("data:") || trimmed.startsWith("blob:")) {
1852
+ return trimmed;
1853
+ }
1854
+ }
1855
+ return null;
1856
+ }
1857
+ isProfilePhotoBlobUrl(photo) {
1858
+ return photo.trim().startsWith("data:") || photo.trim().startsWith("blob:");
1859
+ }
1860
+ async profilePhotoToBlob(photo) {
1861
+ if (typeof Blob !== "undefined" && photo instanceof Blob) {
1862
+ return photo;
1863
+ }
1864
+ if (typeof photo === "string" && this.isProfilePhotoBlobUrl(photo)) {
1865
+ const response = await fetch(photo);
1866
+ if (!response.ok) {
1867
+ throw new TiquoAuthError(
1868
+ "Failed to read profile photo URI",
1869
+ "INVALID_PROFILE_PHOTO"
1870
+ );
1871
+ }
1872
+ return response.blob();
1873
+ }
1874
+ throw new TiquoAuthError(
1875
+ "uploadProfilePhoto expects a File, Blob, data URI, or blob URI. Use updateProfile({ profilePhoto: url }) for remote URLs.",
1876
+ "INVALID_PROFILE_PHOTO"
1877
+ );
1878
+ }
1329
1879
  async request(path, options) {
1330
1880
  const url = `${this.config.apiEndpoint}${path}`;
1331
1881
  const headers = {
@@ -1385,16 +1935,19 @@ var TiquoAuth = class {
1385
1935
  this.isRefreshing = true;
1386
1936
  this.log("Refreshing access token");
1387
1937
  try {
1388
- const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/refresh`, {
1389
- method: "POST",
1390
- headers: {
1391
- "Content-Type": "application/json"
1392
- },
1393
- body: JSON.stringify({
1394
- refresh_token: this.refreshToken
1395
- }),
1396
- credentials: "include"
1397
- });
1938
+ const response = await fetch(
1939
+ `${this.config.apiEndpoint}/api/client/v1/refresh`,
1940
+ {
1941
+ method: "POST",
1942
+ headers: {
1943
+ "Content-Type": "application/json"
1944
+ },
1945
+ body: JSON.stringify({
1946
+ refresh_token: this.refreshToken
1947
+ }),
1948
+ credentials: "include"
1949
+ }
1950
+ );
1398
1951
  if (!response.ok) {
1399
1952
  this.log("Token refresh failed, clearing session");
1400
1953
  this.clearTokens();
@@ -1431,13 +1984,16 @@ var TiquoAuth = class {
1431
1984
  }
1432
1985
  try {
1433
1986
  await this.refreshTokenIfNeeded();
1434
- const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/profile`, {
1435
- method: "GET",
1436
- headers: {
1437
- "Authorization": `Bearer ${this.accessToken}`
1438
- },
1439
- credentials: "include"
1440
- });
1987
+ const response = await fetch(
1988
+ `${this.config.apiEndpoint}/api/client/v1/profile`,
1989
+ {
1990
+ method: "GET",
1991
+ headers: {
1992
+ Authorization: `Bearer ${this.accessToken}`
1993
+ },
1994
+ credentials: "include"
1995
+ }
1996
+ );
1441
1997
  if (!response.ok) {
1442
1998
  this.log("Session invalid, clearing");
1443
1999
  this.clearTokens();
@@ -1455,6 +2011,9 @@ var TiquoAuth = class {
1455
2011
  if (this.session.user?.id) {
1456
2012
  addCustomerUserId(this.session.user.id);
1457
2013
  }
2014
+ if (this.session.customer) {
2015
+ this.analytics?.identify({ identityLinkType: "auth_dom_login" }).catch(() => void 0);
2016
+ }
1458
2017
  this.notifyListeners();
1459
2018
  this.scheduleRefresh();
1460
2019
  return this.session;
@@ -1489,8 +2048,12 @@ var TiquoAuth = class {
1489
2048
  return;
1490
2049
  }
1491
2050
  try {
1492
- const accessToken = localStorage.getItem(`${this.config.storagePrefix}access_token`);
1493
- const refreshToken = localStorage.getItem(`${this.config.storagePrefix}refresh_token`);
2051
+ const accessToken = localStorage.getItem(
2052
+ `${this.config.storagePrefix}access_token`
2053
+ );
2054
+ const refreshToken = localStorage.getItem(
2055
+ `${this.config.storagePrefix}refresh_token`
2056
+ );
1494
2057
  if (accessToken) {
1495
2058
  this.accessToken = accessToken;
1496
2059
  this.refreshToken = refreshToken;
@@ -1602,7 +2165,12 @@ var TiquoAuth = class {
1602
2165
  if (message.tabId === this.tabId) return;
1603
2166
  if (this.isProcessingTabSync) return;
1604
2167
  this.isProcessingTabSync = true;
1605
- this.log("Received tab sync message:", message.type, "from tab:", message.tabId);
2168
+ this.log(
2169
+ "Received tab sync message:",
2170
+ message.type,
2171
+ "from tab:",
2172
+ message.tabId
2173
+ );
1606
2174
  try {
1607
2175
  switch (message.type) {
1608
2176
  case "LOGIN":
@@ -1629,7 +2197,9 @@ var TiquoAuth = class {
1629
2197
  }
1630
2198
  try {
1631
2199
  localStorage.removeItem(`${this.config.storagePrefix}access_token`);
1632
- localStorage.removeItem(`${this.config.storagePrefix}refresh_token`);
2200
+ localStorage.removeItem(
2201
+ `${this.config.storagePrefix}refresh_token`
2202
+ );
1633
2203
  } catch {
1634
2204
  }
1635
2205
  this.notifyListeners();
@@ -1701,6 +2271,7 @@ function useTiquoAuth(auth) {
1701
2271
  verifyOTP: (email, otp) => auth.verifyOTP(email, otp),
1702
2272
  logout: () => auth.logout(),
1703
2273
  updateProfile: (updates) => auth.updateProfile(updates),
2274
+ uploadProfilePhoto: (photo) => auth.uploadProfilePhoto(photo),
1704
2275
  getOrders: (options) => auth.getOrders(options),
1705
2276
  getBookings: (options) => auth.getBookings(options),
1706
2277
  getUpcomingBookings: (options) => auth.getUpcomingBookings(options),
@@ -1732,8 +2303,10 @@ TiquoPhone.buildPhone = buildPhone;
1732
2303
  var index_default = TiquoAuth;
1733
2304
  // Annotate the CommonJS export names for ESM import in node:
1734
2305
  0 && (module.exports = {
2306
+ TiquoAnalytics,
1735
2307
  TiquoAuth,
1736
2308
  TiquoAuthError,
2309
+ TiquoCMS,
1737
2310
  TiquoPhone,
1738
2311
  addCustomerUserId,
1739
2312
  clearCachedEmail,