@primestyleai/tryon 5.10.194 → 5.10.195

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.
@@ -9321,53 +9321,6 @@ reactJsxRuntime_production_min.jsxs = q;
9321
9321
  jsxRuntime.exports = reactJsxRuntime_production_min;
9322
9322
  }
9323
9323
  var jsxRuntimeExports = jsxRuntime.exports;
9324
- function cx(base, override) {
9325
- return override ? `${base} ${override}` : base;
9326
- }
9327
- const MOBILE_BREAKPOINT = 768;
9328
- function useIsMobile() {
9329
- const [isMobile, setIsMobile] = reactExports.useState(false);
9330
- reactExports.useEffect(() => {
9331
- if (typeof window === "undefined") return;
9332
- const check = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9333
- check();
9334
- window.addEventListener("resize", check);
9335
- return () => window.removeEventListener("resize", check);
9336
- }, []);
9337
- return isMobile;
9338
- }
9339
- function ErrorView({
9340
- productImage,
9341
- productTitle,
9342
- onManualMeasurements,
9343
- cn,
9344
- t: t2
9345
- }) {
9346
- const isMobile = useIsMobile();
9347
- const RightColumn = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-no-chart-content ps-tryon-error-fallback", cn.error), children: [
9348
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-icon ps-tryon-error-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: "44", height: "44", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
9349
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 3l8.5 15H3.5L12 3z" }),
9350
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 9v4" }),
9351
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 17h.01" })
9352
- ] }) }),
9353
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-no-chart-title", children: t2("We couldn't find a matching size for you") }),
9354
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: cx("ps-tryon-no-chart-msg", cn.errorText), children: t2("Please try the manual measurement screen so we can calculate your fit from your measurements.") }),
9355
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-actions", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: onManualMeasurements, className: cx("ps-tryon-no-chart-cta", cn.submitButton), children: [
9356
- t2("Try manual measurements"),
9357
- " →"
9358
- ] }) })
9359
- ] });
9360
- if (isMobile) {
9361
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr", children: [
9362
- productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", style: { flex: "0 0 auto", maxHeight: "38vh" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9363
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-right-col", children: RightColumn })
9364
- ] });
9365
- }
9366
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-split", children: [
9367
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", children: productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9368
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col ps-tryon-no-chart-right-col", children: RightColumn })
9369
- ] }) });
9370
- }
9371
9324
  const STORAGE_KEY = "ps_session";
9372
9325
  const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
9373
9326
  const FALLBACK_PREFIX = "ps_mem_";
@@ -9412,6 +9365,14 @@ function writeRecord(record) {
9412
9365
  } catch {
9413
9366
  }
9414
9367
  }
9368
+ function attachReplaySession(sessionId) {
9369
+ if (typeof window === "undefined") return;
9370
+ try {
9371
+ const clarity = window.clarity;
9372
+ if (typeof clarity === "function") clarity("set", STORAGE_KEY, sessionId);
9373
+ } catch {
9374
+ }
9375
+ }
9415
9376
  function getOrCreateSessionId() {
9416
9377
  const now = Date.now();
9417
9378
  const existing = readRecord();
@@ -9419,10 +9380,12 @@ function getOrCreateSessionId() {
9419
9380
  if (now - existing.lastSeenAt > 5 * 60 * 1e3) {
9420
9381
  writeRecord({ ...existing, lastSeenAt: now });
9421
9382
  }
9383
+ attachReplaySession(existing.id);
9422
9384
  return existing.id;
9423
9385
  }
9424
9386
  const fresh = { id: uuid(), issuedAt: now, lastSeenAt: now };
9425
9387
  writeRecord(fresh);
9388
+ attachReplaySession(fresh.id);
9426
9389
  return fresh.id;
9427
9390
  }
9428
9391
  function getDeviceHint() {
@@ -9544,8 +9507,49 @@ class ApiClient {
9544
9507
  }
9545
9508
  return res.json();
9546
9509
  }
9547
- getStreamUrl() {
9548
- const streamUrl = `${this.baseUrl}/api/v1/tryon/stream`;
9510
+ async reportEvent(input) {
9511
+ const res = await fetch(`${this.baseUrl}/api/v1/tryon/event`, {
9512
+ method: "POST",
9513
+ headers: this.headers,
9514
+ keepalive: true,
9515
+ body: JSON.stringify({
9516
+ ...input,
9517
+ sessionId: getOrCreateSessionId(),
9518
+ deviceHint: getDeviceHint()
9519
+ })
9520
+ });
9521
+ if (!res.ok) {
9522
+ const data = await res.json().catch(() => ({}));
9523
+ throw new PrimeStyleError(
9524
+ data.message || "Failed to report SDK event",
9525
+ "EVENT_REPORT_FAILED"
9526
+ );
9527
+ }
9528
+ return res.json();
9529
+ }
9530
+ async reportClientError(input) {
9531
+ const res = await fetch(`${this.baseUrl}/api/v1/tryon/client-error`, {
9532
+ method: "POST",
9533
+ headers: this.headers,
9534
+ keepalive: true,
9535
+ body: JSON.stringify({
9536
+ ...input,
9537
+ sessionId: getOrCreateSessionId(),
9538
+ deviceHint: getDeviceHint()
9539
+ })
9540
+ });
9541
+ if (!res.ok) {
9542
+ const data = await res.json().catch(() => ({}));
9543
+ throw new PrimeStyleError(
9544
+ data.message || "Failed to report SDK client error",
9545
+ "CLIENT_ERROR_REPORT_FAILED"
9546
+ );
9547
+ }
9548
+ return res.json();
9549
+ }
9550
+ getStreamUrl(jobId) {
9551
+ const streamPath = jobId ? `/api/v1/tryon/stream/${encodeURIComponent(jobId)}` : "/api/v1/tryon/stream";
9552
+ const streamUrl = `${this.baseUrl}${streamPath}`;
9549
9553
  return this.apiKey ? `${streamUrl}?key=${encodeURIComponent(this.apiKey)}` : streamUrl;
9550
9554
  }
9551
9555
  }
@@ -9561,6 +9565,89 @@ class PrimeStyleError extends Error {
9561
9565
  this.code = code;
9562
9566
  }
9563
9567
  }
9568
+ function detectLocale() {
9569
+ if (typeof navigator === "undefined") return "US";
9570
+ const lang = navigator.language || "";
9571
+ const region = lang.split("-")[1]?.toUpperCase();
9572
+ if (region === "GB") return "UK";
9573
+ if (region) return region;
9574
+ const map = {
9575
+ en: "US",
9576
+ ja: "JP",
9577
+ ko: "KR",
9578
+ zh: "CN",
9579
+ fr: "FR",
9580
+ it: "IT",
9581
+ de: "DE",
9582
+ es: "ES",
9583
+ pt: "BR"
9584
+ };
9585
+ return map[lang.split("-")[0].toLowerCase()] || "US";
9586
+ }
9587
+ function getApiKey() {
9588
+ let key = "";
9589
+ try {
9590
+ key = "shopify-proxy";
9591
+ } catch {
9592
+ }
9593
+ return key;
9594
+ }
9595
+ function getApiUrl(override) {
9596
+ if (override) return override;
9597
+ let envUrl = "";
9598
+ try {
9599
+ envUrl = "";
9600
+ } catch {
9601
+ }
9602
+ return envUrl || "http://localhost:4000";
9603
+ }
9604
+ function cx(base, override) {
9605
+ return override ? `${base} ${override}` : base;
9606
+ }
9607
+ const MOBILE_BREAKPOINT = 768;
9608
+ function useIsMobile() {
9609
+ const [isMobile, setIsMobile] = reactExports.useState(false);
9610
+ reactExports.useEffect(() => {
9611
+ if (typeof window === "undefined") return;
9612
+ const check = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9613
+ check();
9614
+ window.addEventListener("resize", check);
9615
+ return () => window.removeEventListener("resize", check);
9616
+ }, []);
9617
+ return isMobile;
9618
+ }
9619
+ function ErrorView({
9620
+ productImage,
9621
+ productTitle,
9622
+ onManualMeasurements,
9623
+ cn,
9624
+ t: t2
9625
+ }) {
9626
+ const isMobile = useIsMobile();
9627
+ const RightColumn = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-no-chart-content ps-tryon-error-fallback", cn.error), children: [
9628
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-icon ps-tryon-error-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: "44", height: "44", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
9629
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 3l8.5 15H3.5L12 3z" }),
9630
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 9v4" }),
9631
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 17h.01" })
9632
+ ] }) }),
9633
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-no-chart-title", children: t2("We couldn't find a matching size for you") }),
9634
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: cx("ps-tryon-no-chart-msg", cn.errorText), children: t2("Please try the manual measurement screen so we can calculate your fit from your measurements.") }),
9635
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-actions", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: onManualMeasurements, className: cx("ps-tryon-no-chart-cta", cn.submitButton), children: [
9636
+ t2("Try manual measurements"),
9637
+ " →"
9638
+ ] }) })
9639
+ ] });
9640
+ if (isMobile) {
9641
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr", children: [
9642
+ productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", style: { flex: "0 0 auto", maxHeight: "38vh" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9643
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-right-col", children: RightColumn })
9644
+ ] });
9645
+ }
9646
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-split", children: [
9647
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", children: productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9648
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col ps-tryon-no-chart-right-col", children: RightColumn })
9649
+ ] }) });
9650
+ }
9564
9651
  class SseClient {
9565
9652
  constructor(streamUrl) {
9566
9653
  this.eventSource = null;
@@ -10932,42 +11019,6 @@ function installCartHook() {
10932
11019
  }
10933
11020
  console.log(TAG$1, "attribution hook installed");
10934
11021
  }
10935
- function detectLocale() {
10936
- if (typeof navigator === "undefined") return "US";
10937
- const lang = navigator.language || "";
10938
- const region = lang.split("-")[1]?.toUpperCase();
10939
- if (region === "GB") return "UK";
10940
- if (region) return region;
10941
- const map = {
10942
- en: "US",
10943
- ja: "JP",
10944
- ko: "KR",
10945
- zh: "CN",
10946
- fr: "FR",
10947
- it: "IT",
10948
- de: "DE",
10949
- es: "ES",
10950
- pt: "BR"
10951
- };
10952
- return map[lang.split("-")[0].toLowerCase()] || "US";
10953
- }
10954
- function getApiKey() {
10955
- let key = "";
10956
- try {
10957
- key = "shopify-proxy";
10958
- } catch {
10959
- }
10960
- return key;
10961
- }
10962
- function getApiUrl(override) {
10963
- if (override) return override;
10964
- let envUrl = "";
10965
- try {
10966
- envUrl = "";
10967
- } catch {
10968
- }
10969
- return envUrl || "http://localhost:4000";
10970
- }
10971
11022
  const SESSION_KEY = "primestyle_profile_session";
10972
11023
  const AUTH_MESSAGE_TYPE = "PRIMESTYLE_SDK_AUTH";
10973
11024
  const POPUP_TIMEOUT_MS = 12e4;
@@ -11150,6 +11201,30 @@ async function saveRemoteProfiles(apiUrl, accessToken, profiles, activeProfileId
11150
11201
  });
11151
11202
  return normalizeStore(await parseResponse(response));
11152
11203
  }
11204
+ function shouldUseShopifyProxy(apiUrl) {
11205
+ if (typeof window === "undefined") return false;
11206
+ try {
11207
+ const w2 = window;
11208
+ if (w2.Shopify?.shop) return true;
11209
+ return /\/apps\/primestyle\/?$/.test(apiUrl);
11210
+ } catch {
11211
+ return false;
11212
+ }
11213
+ }
11214
+ function reportDirectSdkEvent(apiUrl, apiKey, body) {
11215
+ if (!apiKey || shouldUseShopifyProxy(apiUrl)) return;
11216
+ void fetch(`${apiUrl}/api/v1/tryon/event`, {
11217
+ method: "POST",
11218
+ headers: jsonHeaders(apiKey),
11219
+ keepalive: true,
11220
+ body: JSON.stringify({
11221
+ ...body,
11222
+ sessionId: getOrCreateSessionId(),
11223
+ deviceHint: getDeviceHint()
11224
+ })
11225
+ }).catch(() => {
11226
+ });
11227
+ }
11153
11228
  let cachedMP = null;
11154
11229
  const MP_CACHE_TTL_MS = 6e4;
11155
11230
  function setCachedMediaPipe(landmarks) {
@@ -11223,6 +11298,13 @@ async function recommendForProduct(input) {
11223
11298
  recommendedSize: cached.recommendedSize,
11224
11299
  fromCache: true
11225
11300
  });
11301
+ reportDirectSdkEvent(apiUrl, apiKey, {
11302
+ eventType: "SIZE_RECOMMENDATION_SHOWN",
11303
+ productId: input.productId,
11304
+ productTitle: input.productTitle,
11305
+ recommendedSize: cached.recommendedSize,
11306
+ metadata: { fromCache: true, profileId: profile.id }
11307
+ });
11226
11308
  const reconstructedRaw = cached.sectionsFull ? {
11227
11309
  recommendedSize: cached.recommendedSize,
11228
11310
  confidence: cached.confidence || "high",
@@ -11434,6 +11516,19 @@ async function recommendForProduct(input) {
11434
11516
  size: result.recommendedSize,
11435
11517
  recommendedSize: result.recommendedSize
11436
11518
  });
11519
+ logSizeShown({
11520
+ productId: input.productId,
11521
+ productTitle: input.productTitle,
11522
+ recommendedSize: result.recommendedSize,
11523
+ fromCache: false
11524
+ });
11525
+ reportDirectSdkEvent(apiUrl, apiKey, {
11526
+ eventType: "SIZE_RECOMMENDATION_SHOWN",
11527
+ productId: input.productId,
11528
+ productTitle: input.productTitle,
11529
+ recommendedSize: result.recommendedSize,
11530
+ metadata: { fromCache: false, profileId: profile.id, found: result.found }
11531
+ });
11437
11532
  return {
11438
11533
  recommendedSize: result.recommendedSize,
11439
11534
  confidence: result.confidence,
@@ -35031,7 +35126,7 @@ function PrimeStyleTryonInner({
35031
35126
  const key = getApiKey();
35032
35127
  const url = getApiUrl(apiUrl);
35033
35128
  apiRef.current = new ApiClient(key, url);
35034
- sseRef.current = shouldUseShopifyCartAttribution(url) ? null : new SseClient(apiRef.current.getStreamUrl());
35129
+ sseRef.current = null;
35035
35130
  } catch {
35036
35131
  }
35037
35132
  return () => {
@@ -35040,6 +35135,52 @@ function PrimeStyleTryonInner({
35040
35135
  if (pollingRef.current) clearInterval(pollingRef.current);
35041
35136
  };
35042
35137
  }, [apiUrl]);
35138
+ const reportSdkEvent = reactExports.useCallback((eventType, input = {}) => {
35139
+ if (shouldUseShopifyCartAttribution(apiUrl)) return;
35140
+ const client = apiRef.current;
35141
+ if (!client) return;
35142
+ void client.reportEvent({
35143
+ eventType,
35144
+ productId: effectiveProductId,
35145
+ productTitle: restoredProductTitle || productTitle,
35146
+ productUrl: restoredProductUrl || effectiveProductUrl,
35147
+ jobId: input.jobId ?? currentTryOnJobIdRef.current ?? void 0,
35148
+ recommendedSize: input.recommendedSize ?? sizingResultRef.current?.recommendedSize,
35149
+ metadata: input.metadata
35150
+ }).catch(() => {
35151
+ });
35152
+ }, [apiUrl, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl]);
35153
+ const reportSdkClientError = reactExports.useCallback((input) => {
35154
+ if (shouldUseShopifyCartAttribution(apiUrl)) return;
35155
+ const client = apiRef.current;
35156
+ if (!client) return;
35157
+ void client.reportClientError({
35158
+ ...input,
35159
+ productId: effectiveProductId,
35160
+ productTitle: restoredProductTitle || productTitle,
35161
+ productUrl: restoredProductUrl || effectiveProductUrl,
35162
+ jobId: input.jobId ?? currentTryOnJobIdRef.current ?? void 0
35163
+ }).catch(() => {
35164
+ });
35165
+ }, [apiUrl, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl]);
35166
+ const productViewLoggedRef = reactExports.useRef(null);
35167
+ reactExports.useEffect(() => {
35168
+ const key = `${effectiveProductId || ""}|${effectiveProductUrl || ""}`;
35169
+ if (!key.trim() || productViewLoggedRef.current === key) return;
35170
+ productViewLoggedRef.current = key;
35171
+ if (shouldUseShopifyCartAttribution(apiUrl)) {
35172
+ logProductView({ productId: effectiveProductId, productTitle });
35173
+ return;
35174
+ }
35175
+ reportSdkEvent("PRODUCT_VIEW", {
35176
+ metadata: {
35177
+ productUrl: effectiveProductUrl,
35178
+ productFitType: resolvedProductFitType,
35179
+ productCategory,
35180
+ productSubcategory
35181
+ }
35182
+ });
35183
+ }, [apiUrl, effectiveProductId, effectiveProductUrl, productCategory, productSubcategory, productTitle, reportSdkEvent, resolvedProductFitType]);
35043
35184
  const pickFireCountRef = reactExports.useRef(0);
35044
35185
  const toBackendFetchableImageUrl = reactExports.useCallback((url) => {
35045
35186
  const trimmed = String(url || "").trim();
@@ -35580,10 +35721,11 @@ function PrimeStyleTryonInner({
35580
35721
  });
35581
35722
  const handleOpen = reactExports.useCallback(() => {
35582
35723
  console.log("[ps-sdk] handleOpen fired — opening modal");
35724
+ reportSdkEvent("SDK_OPENED", { metadata: { view: "body-profile" } });
35583
35725
  setBodyProfileInitialStep(null);
35584
35726
  setView("body-profile");
35585
35727
  onOpen?.();
35586
- }, [onOpen]);
35728
+ }, [onOpen, reportSdkEvent]);
35587
35729
  const handleClose = reactExports.useCallback(() => {
35588
35730
  const tryOnInFlight = tryOnProcessing;
35589
35731
  setView("idle");
@@ -35673,12 +35815,28 @@ function PrimeStyleTryonInner({
35673
35815
  if (!isValidImageFile(file)) {
35674
35816
  setErrorMessage(t2("Please upload a JPEG, PNG, or WebP image."));
35675
35817
  setView("error");
35818
+ reportSdkClientError({
35819
+ message: "Invalid image file type",
35820
+ code: "INVALID_FILE",
35821
+ component: "PhotoUpload",
35822
+ view,
35823
+ severity: "low",
35824
+ metadata: { fileType: file.type, fileSize: file.size }
35825
+ });
35676
35826
  onError?.({ message: "Invalid file type", code: "INVALID_FILE" });
35677
35827
  return;
35678
35828
  }
35679
35829
  if (file.size > 10 * 1024 * 1024) {
35680
35830
  setErrorMessage(t2("Image must be under 10MB."));
35681
35831
  setView("error");
35832
+ reportSdkClientError({
35833
+ message: "Image file too large",
35834
+ code: "FILE_TOO_LARGE",
35835
+ component: "PhotoUpload",
35836
+ view,
35837
+ severity: "low",
35838
+ metadata: { fileType: file.type, fileSize: file.size }
35839
+ });
35682
35840
  onError?.({ message: "File too large", code: "FILE_TOO_LARGE" });
35683
35841
  return;
35684
35842
  }
@@ -35687,13 +35845,23 @@ function PrimeStyleTryonInner({
35687
35845
  modelImageIdRef.current = null;
35688
35846
  const objUrl = URL.createObjectURL(file);
35689
35847
  setPreviewUrl(objUrl);
35848
+ reportSdkEvent("PHOTO_UPLOADED", {
35849
+ metadata: { fileType: file.type, fileSize: file.size, view }
35850
+ });
35690
35851
  onUpload?.(file);
35691
35852
  modelPoseRef.current = null;
35692
35853
  detectMeasurementLines(objUrl).then((lines) => {
35693
35854
  modelPoseRef.current = lines;
35694
- }).catch(() => {
35855
+ }).catch((error) => {
35856
+ reportSdkClientError({
35857
+ message: error instanceof Error ? error.message : "Pose detection failed",
35858
+ code: "POSE_DETECTION_FAILED",
35859
+ component: "MediaPipe",
35860
+ view,
35861
+ severity: "low"
35862
+ });
35695
35863
  });
35696
- }, [onUpload, onError]);
35864
+ }, [onUpload, onError, reportSdkClientError, reportSdkEvent, t2, view]);
35697
35865
  const handleRemovePreview = reactExports.useCallback(() => {
35698
35866
  setSelectedFile(null);
35699
35867
  if (previewUrl) URL.revokeObjectURL(previewUrl);
@@ -35762,12 +35930,21 @@ function PrimeStyleTryonInner({
35762
35930
  setTryOnProcessing(false);
35763
35931
  setTryOnStartedAt(null);
35764
35932
  const msg = update.error || t2("Try-on generation failed");
35933
+ reportSdkClientError({
35934
+ message: msg,
35935
+ code: "TRYON_GENERATION_FAILED",
35936
+ component: "TryOn",
35937
+ view,
35938
+ severity: "medium",
35939
+ jobId: update.galleryId,
35940
+ metadata: { status: update.status }
35941
+ });
35765
35942
  setErrorMessage(msg);
35766
35943
  setView("error");
35767
35944
  onError?.({ message: msg });
35768
35945
  }
35769
35946
  }
35770
- }, [apiUrl, effectiveProductId, productTitle, onComplete, onError, cleanupJob]);
35947
+ }, [apiUrl, effectiveProductId, productTitle, onComplete, onError, cleanupJob, reportSdkClientError, t2, view]);
35771
35948
  const dynamicFields = reactExports.useMemo(() => {
35772
35949
  if (sizeGuide?.found && sizeGuide.requiredFields && sizeGuide.requiredFields.length > 0) {
35773
35950
  return sizeGuide.requiredFields;
@@ -35781,6 +35958,14 @@ function PrimeStyleTryonInner({
35781
35958
  const method = methodOverride || sizingMethod;
35782
35959
  const baseUrl = getApiUrl(apiUrl);
35783
35960
  const key = getApiKey();
35961
+ reportSdkEvent("SIZING_STARTED", {
35962
+ metadata: {
35963
+ method,
35964
+ measurementType,
35965
+ productFitType: resolvedProductFitType,
35966
+ hasSizeGuide: !!sizeGuide?.found
35967
+ }
35968
+ });
35784
35969
  if (measurementType === "face" || measurementType === "head") {
35785
35970
  const f2 = formRef.current;
35786
35971
  const toNum = (v2) => {
@@ -35839,16 +36024,42 @@ function PrimeStyleTryonInner({
35839
36024
  const data = await resp.json();
35840
36025
  await minVisible;
35841
36026
  if (data?.found === false) {
36027
+ reportSdkEvent("SIZING_FAILED", {
36028
+ metadata: {
36029
+ reason: data?.reasoning || "NO_MATCH",
36030
+ measurementType,
36031
+ found: false
36032
+ }
36033
+ });
35842
36034
  setNoSizeReason(data?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
35843
36035
  setView("no-chart");
35844
36036
  setEstimationDone(true);
35845
36037
  return;
35846
36038
  }
35847
36039
  setSizingResult(data);
36040
+ reportSdkEvent("SIZE_RECOMMENDATION_SHOWN", {
36041
+ recommendedSize: data?.recommendedSize,
36042
+ metadata: {
36043
+ confidence: data?.confidence,
36044
+ measurementType,
36045
+ source: "face-recommend"
36046
+ }
36047
+ });
35848
36048
  onComplete?.(data);
35849
36049
  } else {
35850
36050
  const body = await resp.text().catch(() => "");
35851
36051
  console.error("[PS-SDK] face-recommend failed:", resp.status, body);
36052
+ reportSdkEvent("SIZING_FAILED", {
36053
+ metadata: { status: resp.status, measurementType, source: "face-recommend" }
36054
+ });
36055
+ reportSdkClientError({
36056
+ message: body || "Face/head sizing request failed",
36057
+ code: "FACE_RECOMMEND_FAILED",
36058
+ component: "Sizing",
36059
+ view,
36060
+ severity: "medium",
36061
+ metadata: { status: resp.status, measurementType }
36062
+ });
35852
36063
  await minVisible;
35853
36064
  setErrorMessage(t2("Unable to get a size suggestion. Please try again."));
35854
36065
  setView("error");
@@ -35856,6 +36067,18 @@ function PrimeStyleTryonInner({
35856
36067
  }
35857
36068
  } catch (err) {
35858
36069
  console.error("[PS-SDK] face-recommend network error:", err);
36070
+ reportSdkEvent("SIZING_FAILED", {
36071
+ metadata: { measurementType, source: "face-recommend", network: true }
36072
+ });
36073
+ reportSdkClientError({
36074
+ message: err instanceof Error ? err.message : "Face/head sizing network error",
36075
+ code: "FACE_RECOMMEND_NETWORK_ERROR",
36076
+ stack: err instanceof Error ? err.stack : void 0,
36077
+ component: "Sizing",
36078
+ view,
36079
+ severity: "medium",
36080
+ metadata: { measurementType }
36081
+ });
35859
36082
  await minVisible;
35860
36083
  setErrorMessage(t2("Unable to connect to sizing service. Please try again."));
35861
36084
  setView("error");
@@ -35916,6 +36139,17 @@ function PrimeStyleTryonInner({
35916
36139
  const qWeight = parseFloat(formRef.current.weight || "0");
35917
36140
  if (!qHeight || !qWeight) {
35918
36141
  console.error("[PS-SDK] submitSizing ABORT — qHeight:", qHeight, "qWeight:", qWeight, "formRef:", JSON.stringify(formRef.current));
36142
+ reportSdkEvent("SIZING_FAILED", {
36143
+ metadata: { reason: "missing_height_or_weight", method, measurementType }
36144
+ });
36145
+ reportSdkClientError({
36146
+ message: "Sizing submitted without height or weight",
36147
+ code: "SIZING_INPUT_MISSING",
36148
+ component: "Sizing",
36149
+ view,
36150
+ severity: "low",
36151
+ metadata: { method, measurementType, qHeight, qWeight }
36152
+ });
35919
36153
  setSizingLoading(false);
35920
36154
  return;
35921
36155
  }
@@ -35948,12 +36182,29 @@ function PrimeStyleTryonInner({
35948
36182
  const data = await res.json();
35949
36183
  console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
35950
36184
  if (data?.found === false) {
36185
+ reportSdkEvent("SIZING_FAILED", {
36186
+ metadata: {
36187
+ reason: data?.reasoning || "NO_MATCH",
36188
+ method,
36189
+ measurementType,
36190
+ found: false
36191
+ }
36192
+ });
35951
36193
  setNoSizeReason(data?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
35952
36194
  setView("no-chart");
35953
36195
  setEstimationDone(true);
35954
36196
  return;
35955
36197
  }
35956
36198
  setSizingResult(data);
36199
+ reportSdkEvent("SIZE_RECOMMENDATION_SHOWN", {
36200
+ recommendedSize: data?.recommendedSize,
36201
+ metadata: {
36202
+ confidence: data?.confidence,
36203
+ method,
36204
+ measurementType,
36205
+ found: data?.found
36206
+ }
36207
+ });
35957
36208
  onComplete?.(data);
35958
36209
  const m2 = payload.measurements || {};
35959
36210
  const qe2 = payload.quickEstimate || {};
@@ -35981,26 +36232,49 @@ function PrimeStyleTryonInner({
35981
36232
  } else {
35982
36233
  const errBody = await res.text().catch(() => "");
35983
36234
  console.error("[PS-SDK] Sizing recommend failed:", res.status, errBody);
36235
+ reportSdkEvent("SIZING_FAILED", {
36236
+ metadata: { status: res.status, method, measurementType, source: "sizing-recommend" }
36237
+ });
36238
+ reportSdkClientError({
36239
+ message: errBody || "Sizing request failed",
36240
+ code: "SIZING_RECOMMEND_FAILED",
36241
+ component: "Sizing",
36242
+ view,
36243
+ severity: "medium",
36244
+ metadata: { status: res.status, method, measurementType }
36245
+ });
35984
36246
  setErrorMessage(t2("Unable to get a size suggestion. Please try again."));
35985
36247
  setView("error");
35986
36248
  setEstimationDone(true);
35987
36249
  }
35988
36250
  } catch (err) {
35989
36251
  console.error("[PS-SDK] Sizing recommend network error:", err);
36252
+ reportSdkEvent("SIZING_FAILED", {
36253
+ metadata: { method, measurementType, source: "sizing-recommend", network: true }
36254
+ });
36255
+ reportSdkClientError({
36256
+ message: err instanceof Error ? err.message : "Sizing network error",
36257
+ code: "SIZING_RECOMMEND_NETWORK_ERROR",
36258
+ stack: err instanceof Error ? err.stack : void 0,
36259
+ component: "Sizing",
36260
+ view,
36261
+ severity: "medium",
36262
+ metadata: { method, measurementType }
36263
+ });
35990
36264
  setErrorMessage(t2("Unable to connect to sizing service. Please try again."));
35991
36265
  setView("error");
35992
36266
  setEstimationDone(true);
35993
36267
  } finally {
35994
36268
  setSizingLoading(false);
35995
36269
  }
35996
- }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productContext, measurementType, dynamicFields, persistResultToProfile]);
36270
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productContext, measurementType, resolvedProductFitType, dynamicFields, persistResultToProfile, reportSdkClientError, reportSdkEvent, t2, view]);
35997
36271
  const handleQuickEstimate = reactExports.useCallback(async (height, weight, heightUnit2, weightUnit2, gender, age, bodyType, chestProfile, midsectionProfile, hipProfile, bodyImage) => {
35998
36272
  if (!apiRef.current) {
35999
36273
  const msg = t2("SDK not configured. Please refresh and try again.");
36000
36274
  console.warn("[ps-sdk] handleQuickEstimate BAILED — apiRef is null. API key not loaded.");
36001
36275
  setErrorMessage(msg);
36002
36276
  setView("error");
36003
- onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
36277
+ onError?.({ message: msg, code: !apiRef.current ? "SDK_NOT_CONFIGURED" : "PHOTO_MISSING" });
36004
36278
  return;
36005
36279
  }
36006
36280
  getApiUrl(apiUrl);
@@ -36379,6 +36653,13 @@ function PrimeStyleTryonInner({
36379
36653
  const msg = !apiRef.current ? t2("SDK not configured. Please provide an API key.") : t2("Please upload a photo first.");
36380
36654
  setErrorMessage(msg);
36381
36655
  setView("error");
36656
+ reportSdkClientError({
36657
+ message: msg,
36658
+ code: !apiRef.current ? "SDK_NOT_CONFIGURED" : "PHOTO_MISSING",
36659
+ component: "TryOn",
36660
+ view,
36661
+ severity: !apiRef.current ? "high" : "low"
36662
+ });
36382
36663
  onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
36383
36664
  return;
36384
36665
  }
@@ -36536,9 +36817,9 @@ function PrimeStyleTryonInner({
36536
36817
  if (response.modelImageId) modelImageIdRef.current = response.modelImageId;
36537
36818
  onProcessing?.(response.jobId);
36538
36819
  const usePollingOnly = shouldUseShopifyCartAttribution(apiUrl);
36539
- if (!usePollingOnly && response.streamUrl) {
36820
+ if (!usePollingOnly) {
36540
36821
  sseRef.current?.disconnect();
36541
- sseRef.current = new SseClient(response.streamUrl);
36822
+ sseRef.current = new SseClient(response.streamUrl || apiRef.current.getStreamUrl(response.jobId));
36542
36823
  }
36543
36824
  unsubRef.current?.();
36544
36825
  unsubRef.current = usePollingOnly ? null : sseRef.current?.onJob(response.jobId, handleVtoUpdate) ?? null;
@@ -36566,11 +36847,19 @@ function PrimeStyleTryonInner({
36566
36847
  } catch (err) {
36567
36848
  const message = err instanceof Error ? err.message : t2("Failed to start try-on");
36568
36849
  const code = err instanceof PrimeStyleError ? err.code : void 0;
36850
+ reportSdkClientError({
36851
+ message,
36852
+ code: code || "TRYON_SUBMIT_FAILED",
36853
+ stack: err instanceof Error ? err.stack : void 0,
36854
+ component: "TryOn",
36855
+ view,
36856
+ severity: "medium"
36857
+ });
36569
36858
  setErrorMessage(message);
36570
36859
  setView("error");
36571
36860
  onError?.({ message, code });
36572
36861
  }
36573
- }, [selectedFile, productImage, effectiveProductImages, garmentReferenceImage, garmentDetailImage, productTitle, productCategory, productSubcategory, resolvedProductFitType, productType, productTagsList, productDescription, productMaterial, measurementType, sizingResult, sizeGuide, apiUrl, onProcessing, onError, handleVtoUpdate]);
36862
+ }, [selectedFile, productImage, effectiveProductImages, garmentReferenceImage, garmentDetailImage, productTitle, productCategory, productSubcategory, resolvedProductFitType, productType, productTagsList, productDescription, productMaterial, measurementType, sizingResult, sizeGuide, apiUrl, onProcessing, onError, handleVtoUpdate, reportSdkClientError, t2, view]);
36574
36863
  reactExports.useEffect(() => {
36575
36864
  if (view !== "size-result") {
36576
36865
  autoTryOnFiredRef.current = false;
@@ -36662,7 +36951,7 @@ function PrimeStyleTryonInner({
36662
36951
  }, [sizingResult]);
36663
36952
  const handleAddToBag = reactExports.useCallback(async () => {
36664
36953
  if (!onAddToBag || !sizingResult) return;
36665
- await onAddToBag({
36954
+ const payload = {
36666
36955
  productId,
36667
36956
  productTitle: restoredProductTitle || productTitle,
36668
36957
  productUrl: restoredProductUrl || effectiveProductUrl,
@@ -36671,7 +36960,33 @@ function PrimeStyleTryonInner({
36671
36960
  resultImageUrl: resultImageUrlRef.current || resultImageUrl,
36672
36961
  historyEntryId: currentHistoryEntryIdRef.current ?? void 0,
36673
36962
  selectedSizes: buildAddToBagSelectedSizes()
36674
- });
36963
+ };
36964
+ try {
36965
+ await onAddToBag(payload);
36966
+ reportSdkEvent("SIZE_RECOMMENDATION_ACCEPTED", {
36967
+ recommendedSize: sizingResult.recommendedSize,
36968
+ metadata: { selectedSizes: payload.selectedSizes }
36969
+ });
36970
+ reportSdkEvent("ADD_TO_CART_FROM_TRYON", {
36971
+ recommendedSize: sizingResult.recommendedSize,
36972
+ metadata: {
36973
+ selectedSizes: payload.selectedSizes,
36974
+ historyEntryId: payload.historyEntryId,
36975
+ hasResult: Boolean(payload.resultImageUrl)
36976
+ }
36977
+ });
36978
+ } catch (error) {
36979
+ reportSdkClientError({
36980
+ message: error instanceof Error ? error.message : "Add to bag failed",
36981
+ code: "ADD_TO_BAG_FAILED",
36982
+ stack: error instanceof Error ? error.stack : void 0,
36983
+ component: "ResultActions",
36984
+ view,
36985
+ severity: "medium",
36986
+ metadata: { selectedSizes: payload.selectedSizes }
36987
+ });
36988
+ throw error;
36989
+ }
36675
36990
  }, [
36676
36991
  buildAddToBagSelectedSizes,
36677
36992
  effectiveProductUrl,
@@ -36681,7 +36996,10 @@ function PrimeStyleTryonInner({
36681
36996
  restoredProductTitle,
36682
36997
  restoredProductUrl,
36683
36998
  resultImageUrl,
36684
- sizingResult
36999
+ reportSdkClientError,
37000
+ reportSdkEvent,
37001
+ sizingResult,
37002
+ view
36685
37003
  ]);
36686
37004
  const handleTryOnFeedbackSubmit = reactExports.useCallback(async ({ rating, note }) => {
36687
37005
  const profileLoggedIn = Boolean(profileSession);
@@ -37891,8 +38209,20 @@ class PrimeStyleTryonErrorBoundary extends reactExports.Component {
37891
38209
  }
37892
38210
  componentDidCatch(error) {
37893
38211
  console.error("[ps-sdk] PrimeStyleTryon render failed:", error);
38212
+ const message = error instanceof Error ? error.message : "PrimeStyleTryon render failed";
38213
+ void new ApiClient(getApiKey(), getApiUrl(this.props.apiUrl)).reportClientError({
38214
+ message,
38215
+ code: "RENDER_ERROR",
38216
+ stack: error instanceof Error ? error.stack : void 0,
38217
+ component: "PrimeStyleTryon",
38218
+ productId: this.props.productId || this.props.productImage,
38219
+ productTitle: this.props.productTitle,
38220
+ productUrl: this.props.productUrl,
38221
+ severity: "high"
38222
+ }).catch(() => {
38223
+ });
37894
38224
  this.props.onError?.({
37895
- message: error instanceof Error ? error.message : "PrimeStyleTryon render failed",
38225
+ message,
37896
38226
  code: "RENDER_ERROR"
37897
38227
  });
37898
38228
  }