@swan-admin/swan-ai-measurements 1.1.95 → 1.1.96

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.
@@ -23,6 +23,18 @@ declare class Measurement {
23
23
  constructor(accessKey?: string, urlType?: URLType, token?: string);
24
24
  getMeasurementResult(scanId: string): Promise<AxiosResponse<any>>;
25
25
  getMeasurementRecommendation({ scanId, shopDomain, productName }: MeasurementRecommendation): Promise<AxiosResponse<any>>;
26
+ /**
27
+ * Releases everything held for one body scan.
28
+ *
29
+ * Callers own the lifetime, because only they know when a result is final.
30
+ * The client cannot tell: a socket that has delivered a final frame looks the
31
+ * same as one still waiting, and closing on the caller's behalf would cut off
32
+ * the late result that is the only signal a scan failed after the user moved
33
+ * on. Safe to call more than once, and for a scan that never opened a socket.
34
+ */
35
+ closeMeasurementSocket(scanId: string): void;
36
+ /** As closeMeasurementSocket, for a face scan. */
37
+ closeFaceScanSocket(faceScanId: string): void;
26
38
  handleMeasurementSocket(options: MeasurementSocketOptions): void;
27
39
  handlFaceScaneSocket(options: FaceScanSocketOptions): void;
28
40
  }
@@ -18,7 +18,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
18
18
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
19
19
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
20
20
  };
21
- var _Measurement_instances, _Measurement_socketRefs, _Measurement_waitingTimers, _Measurement_pollingTimers, _Measurement_pollingCounts, _Measurement_accessKey, _Measurement_urlType, _Measurement_token, _Measurement_getHeaders, _Measurement_disconnectSocket, _Measurement_handleTimeOut, _Measurement_handlePolling, _Measurement_getMeasurementsCheck, _Measurement_handleSocket;
21
+ var _Measurement_instances, _Measurement_socketRefs, _Measurement_waitingTimers, _Measurement_pollingTimers, _Measurement_pollingCounts, _Measurement_openTimers, _Measurement_accessKey, _Measurement_urlType, _Measurement_token, _Measurement_getHeaders, _Measurement_disconnectSocket, _Measurement_closeSocketAndTimers, _Measurement_handleTimeOut, _Measurement_handlePolling, _Measurement_getMeasurementsCheck, _Measurement_handleSocket;
22
22
  import axios from "axios";
23
23
  import { API_ENDPOINTS, APP_AUTH_BASE_URL, APP_BASE_WEBSOCKET_URL, REQUIRED_MESSAGE } from "./constants.js";
24
24
  import { checkParameters, getUrl } from "./utils.js";
@@ -30,6 +30,15 @@ class Measurement {
30
30
  _Measurement_waitingTimers.set(this, {});
31
31
  _Measurement_pollingTimers.set(this, {});
32
32
  _Measurement_pollingCounts.set(this, {});
33
+ /**
34
+ * Handles for the deferred opens in #handleSocket.
35
+ *
36
+ * The socket is created after a delay (5s for measurement, 1s for face), so
37
+ * between the call and that timer firing there is nothing to close. Without
38
+ * this, closing in that window is a no-op and the socket opens afterwards
39
+ * anyway — which is exactly what a quick retry does.
40
+ */
41
+ _Measurement_openTimers.set(this, {});
33
42
  _Measurement_accessKey.set(this, void 0);
34
43
  _Measurement_urlType.set(this, void 0);
35
44
  _Measurement_token.set(this, void 0);
@@ -54,6 +63,26 @@ class Measurement {
54
63
  headers: __classPrivateFieldGet(this, _Measurement_instances, "m", _Measurement_getHeaders).call(this),
55
64
  });
56
65
  }
66
+ /**
67
+ * Releases everything held for one body scan.
68
+ *
69
+ * Callers own the lifetime, because only they know when a result is final.
70
+ * The client cannot tell: a socket that has delivered a final frame looks the
71
+ * same as one still waiting, and closing on the caller's behalf would cut off
72
+ * the late result that is the only signal a scan failed after the user moved
73
+ * on. Safe to call more than once, and for a scan that never opened a socket.
74
+ */
75
+ closeMeasurementSocket(scanId) {
76
+ if (!scanId)
77
+ return;
78
+ __classPrivateFieldGet(this, _Measurement_instances, "m", _Measurement_closeSocketAndTimers).call(this, `measurement-${scanId}`);
79
+ }
80
+ /** As closeMeasurementSocket, for a face scan. */
81
+ closeFaceScanSocket(faceScanId) {
82
+ if (!faceScanId)
83
+ return;
84
+ __classPrivateFieldGet(this, _Measurement_instances, "m", _Measurement_closeSocketAndTimers).call(this, `faceScan-${faceScanId}`);
85
+ }
57
86
  handleMeasurementSocket(options) {
58
87
  const { scanId, onError, onSuccess, onClose, onOpen } = options;
59
88
  if (!checkParameters(scanId)) {
@@ -69,7 +98,7 @@ class Measurement {
69
98
  __classPrivateFieldGet(this, _Measurement_instances, "m", _Measurement_handleSocket).call(this, { onOpen, faceScanId, onSuccess, onError, onClose, paramsKey: "faceScanId", isFallback: false, delay: 1000 });
70
99
  }
71
100
  }
72
- _Measurement_socketRefs = new WeakMap(), _Measurement_waitingTimers = new WeakMap(), _Measurement_pollingTimers = new WeakMap(), _Measurement_pollingCounts = new WeakMap(), _Measurement_accessKey = new WeakMap(), _Measurement_urlType = new WeakMap(), _Measurement_token = new WeakMap(), _Measurement_instances = new WeakSet(), _Measurement_getHeaders = function _Measurement_getHeaders() {
101
+ _Measurement_socketRefs = new WeakMap(), _Measurement_waitingTimers = new WeakMap(), _Measurement_pollingTimers = new WeakMap(), _Measurement_pollingCounts = new WeakMap(), _Measurement_openTimers = new WeakMap(), _Measurement_accessKey = new WeakMap(), _Measurement_urlType = new WeakMap(), _Measurement_token = new WeakMap(), _Measurement_instances = new WeakSet(), _Measurement_getHeaders = function _Measurement_getHeaders() {
73
102
  return Object.assign(Object.assign({}, (__classPrivateFieldGet(this, _Measurement_accessKey, "f") ? { "X-Api-Key": __classPrivateFieldGet(this, _Measurement_accessKey, "f") } : {})), (__classPrivateFieldGet(this, _Measurement_token, "f") ? { Authorization: `Bearer ${__classPrivateFieldGet(this, _Measurement_token, "f")}` } : {}));
74
103
  }, _Measurement_disconnectSocket = function _Measurement_disconnectSocket(key) {
75
104
  var _a;
@@ -79,6 +108,17 @@ _Measurement_socketRefs = new WeakMap(), _Measurement_waitingTimers = new WeakMa
79
108
  clearTimeout(__classPrivateFieldGet(this, _Measurement_waitingTimers, "f")[key]);
80
109
  __classPrivateFieldGet(this, _Measurement_waitingTimers, "f")[key] = null;
81
110
  }
111
+ }, _Measurement_closeSocketAndTimers = function _Measurement_closeSocketAndTimers(key) {
112
+ if (__classPrivateFieldGet(this, _Measurement_openTimers, "f")[key]) {
113
+ clearTimeout(__classPrivateFieldGet(this, _Measurement_openTimers, "f")[key]);
114
+ __classPrivateFieldGet(this, _Measurement_openTimers, "f")[key] = null;
115
+ }
116
+ __classPrivateFieldGet(this, _Measurement_instances, "m", _Measurement_disconnectSocket).call(this, key);
117
+ if (__classPrivateFieldGet(this, _Measurement_pollingTimers, "f")[key]) {
118
+ clearTimeout(__classPrivateFieldGet(this, _Measurement_pollingTimers, "f")[key]);
119
+ __classPrivateFieldGet(this, _Measurement_pollingTimers, "f")[key] = null;
120
+ }
121
+ __classPrivateFieldGet(this, _Measurement_pollingCounts, "f")[key] = 1;
82
122
  }, _Measurement_handleTimeOut = function _Measurement_handleTimeOut(options, key) {
83
123
  const { scanId, onSuccess, onError } = options;
84
124
  __classPrivateFieldGet(this, _Measurement_pollingCounts, "f")[key] = 1;
@@ -128,7 +168,8 @@ _Measurement_socketRefs = new WeakMap(), _Measurement_waitingTimers = new WeakMa
128
168
  });
129
169
  }, _Measurement_handleSocket = function _Measurement_handleSocket({ onOpen, isFallback, scanId, onSuccess, onError, onClose, paramsKey, faceScanId, delay, onPreopen }) {
130
170
  const key = isFallback ? `measurement-${scanId}` : `faceScan-${faceScanId}`;
131
- setTimeout(() => {
171
+ __classPrivateFieldGet(this, _Measurement_openTimers, "f")[key] = setTimeout(() => {
172
+ __classPrivateFieldGet(this, _Measurement_openTimers, "f")[key] = null;
132
173
  __classPrivateFieldGet(this, _Measurement_instances, "m", _Measurement_disconnectSocket).call(this, key);
133
174
  onPreopen === null || onPreopen === void 0 ? void 0 : onPreopen();
134
175
  const url = `${getUrl({ urlName: APP_BASE_WEBSOCKET_URL, urlType: __classPrivateFieldGet(this, _Measurement_urlType, "f") })}${API_ENDPOINTS.SCANNING}?${paramsKey}=${scanId || faceScanId}`;
@@ -23,6 +23,18 @@ declare class Measurement {
23
23
  constructor(accessKey?: string, urlType?: URLType, token?: string);
24
24
  getMeasurementResult(scanId: string): Promise<AxiosResponse<any>>;
25
25
  getMeasurementRecommendation({ scanId, shopDomain, productName }: MeasurementRecommendation): Promise<AxiosResponse<any>>;
26
+ /**
27
+ * Releases everything held for one body scan.
28
+ *
29
+ * Callers own the lifetime, because only they know when a result is final.
30
+ * The client cannot tell: a socket that has delivered a final frame looks the
31
+ * same as one still waiting, and closing on the caller's behalf would cut off
32
+ * the late result that is the only signal a scan failed after the user moved
33
+ * on. Safe to call more than once, and for a scan that never opened a socket.
34
+ */
35
+ closeMeasurementSocket(scanId: string): void;
36
+ /** As closeMeasurementSocket, for a face scan. */
37
+ closeFaceScanSocket(faceScanId: string): void;
26
38
  handleMeasurementSocket(options: MeasurementSocketOptions): void;
27
39
  handlFaceScaneSocket(options: FaceScanSocketOptions): void;
28
40
  }
@@ -12,6 +12,15 @@ class Measurement {
12
12
  #waitingTimers = {};
13
13
  #pollingTimers = {};
14
14
  #pollingCounts = {};
15
+ /**
16
+ * Handles for the deferred opens in #handleSocket.
17
+ *
18
+ * The socket is created after a delay (5s for measurement, 1s for face), so
19
+ * between the call and that timer firing there is nothing to close. Without
20
+ * this, closing in that window is a no-op and the socket opens afterwards
21
+ * anyway — which is exactly what a quick retry does.
22
+ */
23
+ #openTimers = {};
15
24
  #accessKey;
16
25
  #urlType;
17
26
  #token;
@@ -51,6 +60,47 @@ class Measurement {
51
60
  this.#waitingTimers[key] = null;
52
61
  }
53
62
  }
63
+ /**
64
+ * Everything holding a scan open: the pending open, the socket, the fallback
65
+ * timer and the polling timer.
66
+ *
67
+ * Deliberately separate from #disconnectSocket rather than folded into it.
68
+ * #handleTimeOut starts polling and *then* calls #disconnectSocket, so a
69
+ * version of that method which also cleared #pollingTimers would cancel the
70
+ * polling it had just scheduled — the fallback would silently stop working.
71
+ */
72
+ #closeSocketAndTimers(key) {
73
+ if (this.#openTimers[key]) {
74
+ clearTimeout(this.#openTimers[key]);
75
+ this.#openTimers[key] = null;
76
+ }
77
+ this.#disconnectSocket(key);
78
+ if (this.#pollingTimers[key]) {
79
+ clearTimeout(this.#pollingTimers[key]);
80
+ this.#pollingTimers[key] = null;
81
+ }
82
+ this.#pollingCounts[key] = 1;
83
+ }
84
+ /**
85
+ * Releases everything held for one body scan.
86
+ *
87
+ * Callers own the lifetime, because only they know when a result is final.
88
+ * The client cannot tell: a socket that has delivered a final frame looks the
89
+ * same as one still waiting, and closing on the caller's behalf would cut off
90
+ * the late result that is the only signal a scan failed after the user moved
91
+ * on. Safe to call more than once, and for a scan that never opened a socket.
92
+ */
93
+ closeMeasurementSocket(scanId) {
94
+ if (!scanId)
95
+ return;
96
+ this.#closeSocketAndTimers(`measurement-${scanId}`);
97
+ }
98
+ /** As closeMeasurementSocket, for a face scan. */
99
+ closeFaceScanSocket(faceScanId) {
100
+ if (!faceScanId)
101
+ return;
102
+ this.#closeSocketAndTimers(`faceScan-${faceScanId}`);
103
+ }
54
104
  #handleTimeOut(options, key) {
55
105
  const { scanId, onSuccess, onError } = options;
56
106
  this.#pollingCounts[key] = 1;
@@ -114,7 +164,8 @@ class Measurement {
114
164
  }
115
165
  #handleSocket({ onOpen, isFallback, scanId, onSuccess, onError, onClose, paramsKey, faceScanId, delay, onPreopen }) {
116
166
  const key = isFallback ? `measurement-${scanId}` : `faceScan-${faceScanId}`;
117
- setTimeout(() => {
167
+ this.#openTimers[key] = setTimeout(() => {
168
+ this.#openTimers[key] = null;
118
169
  this.#disconnectSocket(key);
119
170
  onPreopen?.();
120
171
  const url = `${(0, utils_js_1.getUrl)({ urlName: constants_js_1.APP_BASE_WEBSOCKET_URL, urlType: this.#urlType })}${constants_js_1.API_ENDPOINTS.SCANNING}?${paramsKey}=${scanId || faceScanId}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-admin/swan-ai-measurements",
3
- "version": "1.1.95",
3
+ "version": "1.1.96",
4
4
  "description": "provides ai measurement suggestion",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -51,6 +51,15 @@ class Measurement {
51
51
  #waitingTimers: Record<string, NodeJS.Timeout | null> = {};
52
52
  #pollingTimers: Record<string, NodeJS.Timeout | null> = {};
53
53
  #pollingCounts: Record<string, number> = {};
54
+ /**
55
+ * Handles for the deferred opens in #handleSocket.
56
+ *
57
+ * The socket is created after a delay (5s for measurement, 1s for face), so
58
+ * between the call and that timer firing there is nothing to close. Without
59
+ * this, closing in that window is a no-op and the socket opens afterwards
60
+ * anyway — which is exactly what a quick retry does.
61
+ */
62
+ #openTimers: Record<string, NodeJS.Timeout | null> = {};
54
63
  #accessKey?: string;
55
64
  #urlType: URLType;
56
65
  #token?: string;
@@ -96,6 +105,48 @@ class Measurement {
96
105
  }
97
106
  }
98
107
 
108
+ /**
109
+ * Everything holding a scan open: the pending open, the socket, the fallback
110
+ * timer and the polling timer.
111
+ *
112
+ * Deliberately separate from #disconnectSocket rather than folded into it.
113
+ * #handleTimeOut starts polling and *then* calls #disconnectSocket, so a
114
+ * version of that method which also cleared #pollingTimers would cancel the
115
+ * polling it had just scheduled — the fallback would silently stop working.
116
+ */
117
+ #closeSocketAndTimers(key: string): void {
118
+ if (this.#openTimers[key]) {
119
+ clearTimeout(this.#openTimers[key]!);
120
+ this.#openTimers[key] = null;
121
+ }
122
+ this.#disconnectSocket(key);
123
+ if (this.#pollingTimers[key]) {
124
+ clearTimeout(this.#pollingTimers[key]!);
125
+ this.#pollingTimers[key] = null;
126
+ }
127
+ this.#pollingCounts[key] = 1;
128
+ }
129
+
130
+ /**
131
+ * Releases everything held for one body scan.
132
+ *
133
+ * Callers own the lifetime, because only they know when a result is final.
134
+ * The client cannot tell: a socket that has delivered a final frame looks the
135
+ * same as one still waiting, and closing on the caller's behalf would cut off
136
+ * the late result that is the only signal a scan failed after the user moved
137
+ * on. Safe to call more than once, and for a scan that never opened a socket.
138
+ */
139
+ closeMeasurementSocket(scanId: string): void {
140
+ if (!scanId) return;
141
+ this.#closeSocketAndTimers(`measurement-${scanId}`);
142
+ }
143
+
144
+ /** As closeMeasurementSocket, for a face scan. */
145
+ closeFaceScanSocket(faceScanId: string): void {
146
+ if (!faceScanId) return;
147
+ this.#closeSocketAndTimers(`faceScan-${faceScanId}`);
148
+ }
149
+
99
150
  #handleTimeOut(options: HandleTimeOutOptions, key: string): void {
100
151
  const { scanId, onSuccess, onError } = options;
101
152
  this.#pollingCounts[key] = 1;
@@ -160,7 +211,8 @@ class Measurement {
160
211
 
161
212
  #handleSocket({ onOpen, isFallback, scanId, onSuccess, onError, onClose, paramsKey, faceScanId, delay, onPreopen }: HandleSocket) {
162
213
  const key = isFallback ? `measurement-${scanId}` : `faceScan-${faceScanId}`;
163
- setTimeout(() => {
214
+ this.#openTimers[key] = setTimeout(() => {
215
+ this.#openTimers[key] = null;
164
216
  this.#disconnectSocket(key);
165
217
  onPreopen?.()
166
218
  const url = `${getUrl({ urlName: APP_BASE_WEBSOCKET_URL, urlType: this.#urlType })}${API_ENDPOINTS.SCANNING}?${paramsKey}=${scanId || faceScanId}`;