@swan-admin/swan-ai-measurements 1.1.45 → 1.1.47
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/auth.js +1 -10
- package/dist/esm/auth.js +1 -10
- package/dist/esm/measurement.js +1 -9
- package/dist/esm/tryOn.js +1 -9
- package/dist/measurement.js +1 -9
- package/dist/tryOn.js +1 -9
- package/package.json +1 -1
- package/src/constants.ts +1 -0
- package/src/fileUpload.ts +30 -21
- package/src/measurement.ts +1 -64
package/dist/auth.js
CHANGED
|
@@ -6,15 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const axios_1 = __importDefault(require("axios"));
|
|
7
7
|
const constants_js_1 = require("./constants.js");
|
|
8
8
|
const utils_js_1 = require("./utils.js");
|
|
9
|
-
const ws_1 = __importDefault(require("ws"));
|
|
10
|
-
// Conditionally import ws for Node.js
|
|
11
|
-
let WebSocketClient;
|
|
12
|
-
if (typeof window !== "undefined" && window.WebSocket) {
|
|
13
|
-
WebSocketClient = window.WebSocket;
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
WebSocketClient = ws_1.default;
|
|
17
|
-
}
|
|
18
9
|
class Auth {
|
|
19
10
|
#socketRef;
|
|
20
11
|
#accessKey;
|
|
@@ -64,7 +55,7 @@ class Auth {
|
|
|
64
55
|
}
|
|
65
56
|
if (this.#socketRef)
|
|
66
57
|
this.#socketRef.close();
|
|
67
|
-
this.#socketRef = new
|
|
58
|
+
this.#socketRef = new WebSocket(`${(0, utils_js_1.getUrl)({ urlName: constants_js_1.APP_BASE_WEBSOCKET_URL, stagingUrl: this.#stagingUrl })}${constants_js_1.API_ENDPOINTS.AUTH}`);
|
|
68
59
|
const detailObj = { email, scanId };
|
|
69
60
|
if (this.#socketRef) {
|
|
70
61
|
this.#socketRef.onopen = () => {
|
package/dist/esm/auth.js
CHANGED
|
@@ -13,15 +13,6 @@ var _Auth_socketRef, _Auth_accessKey, _Auth_stagingUrl;
|
|
|
13
13
|
import axios from "axios";
|
|
14
14
|
import { API_ENDPOINTS, APP_AUTH_BASE_URL, APP_BASE_WEBSOCKET_URL, REQUIRED_MESSAGE } from "./constants.js";
|
|
15
15
|
import { checkParameters, getUrl } from "./utils.js";
|
|
16
|
-
import WS from "ws";
|
|
17
|
-
// Conditionally import ws for Node.js
|
|
18
|
-
let WebSocketClient;
|
|
19
|
-
if (typeof window !== "undefined" && window.WebSocket) {
|
|
20
|
-
WebSocketClient = window.WebSocket;
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
WebSocketClient = WS;
|
|
24
|
-
}
|
|
25
16
|
class Auth {
|
|
26
17
|
constructor(accessKey, stagingUrl = false) {
|
|
27
18
|
_Auth_socketRef.set(this, void 0);
|
|
@@ -71,7 +62,7 @@ class Auth {
|
|
|
71
62
|
}
|
|
72
63
|
if (__classPrivateFieldGet(this, _Auth_socketRef, "f"))
|
|
73
64
|
__classPrivateFieldGet(this, _Auth_socketRef, "f").close();
|
|
74
|
-
__classPrivateFieldSet(this, _Auth_socketRef, new
|
|
65
|
+
__classPrivateFieldSet(this, _Auth_socketRef, new WebSocket(`${getUrl({ urlName: APP_BASE_WEBSOCKET_URL, stagingUrl: __classPrivateFieldGet(this, _Auth_stagingUrl, "f") })}${API_ENDPOINTS.AUTH}`), "f");
|
|
75
66
|
const detailObj = { email, scanId };
|
|
76
67
|
if (__classPrivateFieldGet(this, _Auth_socketRef, "f")) {
|
|
77
68
|
__classPrivateFieldGet(this, _Auth_socketRef, "f").onopen = () => {
|
package/dist/esm/measurement.js
CHANGED
|
@@ -22,14 +22,6 @@ var _Measurement_instances, _Measurement_tryOnSocketRef, _Measurement_measuremen
|
|
|
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";
|
|
25
|
-
import WS from "ws";
|
|
26
|
-
let WebSocketClient;
|
|
27
|
-
if (typeof window !== 'undefined' && window.WebSocket) {
|
|
28
|
-
WebSocketClient = window.WebSocket;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
WebSocketClient = WS;
|
|
32
|
-
}
|
|
33
25
|
class Measurement {
|
|
34
26
|
constructor(accessKey, stagingUrl = false) {
|
|
35
27
|
_Measurement_instances.add(this);
|
|
@@ -76,7 +68,7 @@ class Measurement {
|
|
|
76
68
|
urlName: APP_BASE_WEBSOCKET_URL,
|
|
77
69
|
stagingUrl: __classPrivateFieldGet(this, _Measurement_stagingUrl, "f"),
|
|
78
70
|
})}/develop?store_url=${shopDomain}&product_name=${productName}&scan_id=${scanId}`;
|
|
79
|
-
__classPrivateFieldSet(this, _Measurement_tryOnSocketRef, new
|
|
71
|
+
__classPrivateFieldSet(this, _Measurement_tryOnSocketRef, new WebSocket(url), "f");
|
|
80
72
|
if (__classPrivateFieldGet(this, _Measurement_tryOnSocketRef, "f")) {
|
|
81
73
|
__classPrivateFieldGet(this, _Measurement_tryOnSocketRef, "f").onopen = () => {
|
|
82
74
|
onOpen === null || onOpen === void 0 ? void 0 : onOpen();
|
package/dist/esm/tryOn.js
CHANGED
|
@@ -22,14 +22,6 @@ var _TryOn_instances, _TryOn_tryOnSocketRef, _TryOn_timerWaitingRef, _TryOn_acce
|
|
|
22
22
|
import axios from "axios";
|
|
23
23
|
import { API_ENDPOINTS, APP_AUTH_BASE_URL, APP_BASE_WEBSOCKET_URL, REQUIRED_ERROR_MESSAGE_INVALID_EMAIL, REQUIRED_MESSAGE } from "./constants.js";
|
|
24
24
|
import { checkParameters, getUrl, isValidEmail } from "./utils.js";
|
|
25
|
-
import WS from "ws";
|
|
26
|
-
let WebSocketClient;
|
|
27
|
-
if (typeof window !== 'undefined' && window.WebSocket) {
|
|
28
|
-
WebSocketClient = window.WebSocket;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
WebSocketClient = WS;
|
|
32
|
-
}
|
|
33
25
|
class TryOn {
|
|
34
26
|
constructor(accessKey, stagingUrl = false) {
|
|
35
27
|
_TryOn_instances.add(this);
|
|
@@ -59,7 +51,7 @@ class TryOn {
|
|
|
59
51
|
}
|
|
60
52
|
__classPrivateFieldGet(this, _TryOn_disconnectSocket, "f").call(this);
|
|
61
53
|
const url = `${getUrl({ urlName: APP_BASE_WEBSOCKET_URL, stagingUrl: __classPrivateFieldGet(this, _TryOn_stagingUrl, "f") })}${API_ENDPOINTS.TRY_ON}?tryonId=${tryonId}`;
|
|
62
|
-
__classPrivateFieldSet(this, _TryOn_tryOnSocketRef, new
|
|
54
|
+
__classPrivateFieldSet(this, _TryOn_tryOnSocketRef, new WebSocket(url), "f");
|
|
63
55
|
if (__classPrivateFieldGet(this, _TryOn_tryOnSocketRef, "f")) {
|
|
64
56
|
__classPrivateFieldGet(this, _TryOn_tryOnSocketRef, "f").onopen = () => __awaiter(this, void 0, void 0, function* () {
|
|
65
57
|
onOpen === null || onOpen === void 0 ? void 0 : onOpen();
|
package/dist/measurement.js
CHANGED
|
@@ -6,14 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const axios_1 = __importDefault(require("axios"));
|
|
7
7
|
const constants_js_1 = require("./constants.js");
|
|
8
8
|
const utils_js_1 = require("./utils.js");
|
|
9
|
-
const ws_1 = __importDefault(require("ws"));
|
|
10
|
-
let WebSocketClient;
|
|
11
|
-
if (typeof window !== 'undefined' && window.WebSocket) {
|
|
12
|
-
WebSocketClient = window.WebSocket;
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
WebSocketClient = ws_1.default;
|
|
16
|
-
}
|
|
17
9
|
class Measurement {
|
|
18
10
|
#tryOnSocketRef = null;
|
|
19
11
|
#measurementSocketRef = null;
|
|
@@ -58,7 +50,7 @@ class Measurement {
|
|
|
58
50
|
urlName: constants_js_1.APP_BASE_WEBSOCKET_URL,
|
|
59
51
|
stagingUrl: this.#stagingUrl,
|
|
60
52
|
})}/develop?store_url=${shopDomain}&product_name=${productName}&scan_id=${scanId}`;
|
|
61
|
-
this.#tryOnSocketRef = new
|
|
53
|
+
this.#tryOnSocketRef = new WebSocket(url);
|
|
62
54
|
if (this.#tryOnSocketRef) {
|
|
63
55
|
this.#tryOnSocketRef.onopen = () => {
|
|
64
56
|
onOpen?.();
|
package/dist/tryOn.js
CHANGED
|
@@ -6,14 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const axios_1 = __importDefault(require("axios"));
|
|
7
7
|
const constants_js_1 = require("./constants.js");
|
|
8
8
|
const utils_js_1 = require("./utils.js");
|
|
9
|
-
const ws_1 = __importDefault(require("ws"));
|
|
10
|
-
let WebSocketClient;
|
|
11
|
-
if (typeof window !== 'undefined' && window.WebSocket) {
|
|
12
|
-
WebSocketClient = window.WebSocket;
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
WebSocketClient = ws_1.default;
|
|
16
|
-
}
|
|
17
9
|
class TryOn {
|
|
18
10
|
#tryOnSocketRef = null;
|
|
19
11
|
#timerWaitingRef = null;
|
|
@@ -126,7 +118,7 @@ class TryOn {
|
|
|
126
118
|
}
|
|
127
119
|
this.#disconnectSocket();
|
|
128
120
|
const url = `${(0, utils_js_1.getUrl)({ urlName: constants_js_1.APP_BASE_WEBSOCKET_URL, stagingUrl: this.#stagingUrl })}${constants_js_1.API_ENDPOINTS.TRY_ON}?tryonId=${tryonId}`;
|
|
129
|
-
this.#tryOnSocketRef = new
|
|
121
|
+
this.#tryOnSocketRef = new WebSocket(url);
|
|
130
122
|
if (this.#tryOnSocketRef) {
|
|
131
123
|
this.#tryOnSocketRef.onopen = async () => {
|
|
132
124
|
onOpen?.();
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -65,3 +65,4 @@ export const requiredMetaData: RequiredMetaDataKeys[] = ["gender", "focal_length
|
|
|
65
65
|
export const REQUIRED_MESSAGE: string = "Please verify required parameters";
|
|
66
66
|
export const REQUIRED_MESSAGE_FOR_META_DATA: string = "Please verify required parameters in meta data";
|
|
67
67
|
export const REQUIRED_ERROR_MESSAGE_INVALID_EMAIL = "Invalid Email";
|
|
68
|
+
export const CHUNK_SIZE = 5 * 1024 * 1024;
|
package/src/fileUpload.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
-
import {
|
|
3
|
-
REQUIRED_MESSAGE,
|
|
4
|
-
REQUIRED_MESSAGE_FOR_META_DATA,
|
|
5
|
-
FILE_UPLOAD_ENDPOINT,
|
|
6
|
-
APP_AUTH_BASE_URL,
|
|
7
|
-
REQUIRED_ERROR_MESSAGE_INVALID_EMAIL,
|
|
8
|
-
API_ENDPOINTS,
|
|
9
|
-
} from "./constants.js";
|
|
2
|
+
import { REQUIRED_MESSAGE, REQUIRED_MESSAGE_FOR_META_DATA, FILE_UPLOAD_ENDPOINT, APP_AUTH_BASE_URL, REQUIRED_ERROR_MESSAGE_INVALID_EMAIL, API_ENDPOINTS, CHUNK_SIZE } from "./constants.js";
|
|
10
3
|
import { addScanType, checkMetaDataValue, checkParameters, fetchData, getFileChunks, getUrl, isValidEmail } from "./utils.js";
|
|
11
4
|
import Uppy from "@uppy/core";
|
|
12
5
|
import AwsS3Multipart from "@uppy/aws-s3-multipart";
|
|
@@ -26,7 +19,7 @@ interface SetDeviceInfo {
|
|
|
26
19
|
detection?: string;
|
|
27
20
|
model?: string;
|
|
28
21
|
gyro: { alpha?: string; gamma?: string; beta?: string; timestamp?: string }[];
|
|
29
|
-
scanId:string
|
|
22
|
+
scanId: string;
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
interface UploadOptions {
|
|
@@ -34,6 +27,7 @@ interface UploadOptions {
|
|
|
34
27
|
arrayMetaData: Partial<ObjMetaData>[];
|
|
35
28
|
scanId: string;
|
|
36
29
|
email: string;
|
|
30
|
+
callBack?: (a: { eventName: string; message: string }) => void;
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
export default class FileUpload {
|
|
@@ -46,7 +40,7 @@ export default class FileUpload {
|
|
|
46
40
|
this.#stagingUrl = stagingUrl;
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
async uploadFileFrontend({ file, arrayMetaData, scanId, email }: UploadOptions) {
|
|
43
|
+
async uploadFileFrontend({ file, arrayMetaData, scanId, email, callBack }: UploadOptions) {
|
|
50
44
|
if (!checkParameters(file, arrayMetaData, scanId, email)) {
|
|
51
45
|
throw new Error(REQUIRED_MESSAGE);
|
|
52
46
|
}
|
|
@@ -66,8 +60,10 @@ export default class FileUpload {
|
|
|
66
60
|
limit: 10,
|
|
67
61
|
retryDelays: [0, 1000, 3000, 5000],
|
|
68
62
|
companionUrl: getUrl({ urlName: APP_AUTH_BASE_URL, stagingUrl: this.#stagingUrl }),
|
|
69
|
-
getChunkSize: () =>
|
|
63
|
+
getChunkSize: () => CHUNK_SIZE,
|
|
70
64
|
createMultipartUpload: (file: any) => {
|
|
65
|
+
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
|
66
|
+
callBack?.({eventName:"uploading_start",message:`File ${file.name} will be divided into ${totalChunks} chunks`})
|
|
71
67
|
const objectKey = `${scanId}.${file.extension}`;
|
|
72
68
|
return fetchData({
|
|
73
69
|
path: FILE_UPLOAD_ENDPOINT.UPLOAD_START,
|
|
@@ -80,8 +76,9 @@ export default class FileUpload {
|
|
|
80
76
|
},
|
|
81
77
|
});
|
|
82
78
|
},
|
|
83
|
-
completeMultipartUpload: (file: any, { uploadId, key, parts }: { uploadId: string | number; key: string | number; parts: any }) =>
|
|
84
|
-
|
|
79
|
+
completeMultipartUpload: (file: any, { uploadId, key, parts }: { uploadId: string | number; key: string | number; parts: any }) => {
|
|
80
|
+
callBack?.({eventName:"uploading_complete_start",message:`${parts.length} chunks of file, uploaded`})
|
|
81
|
+
return fetchData({
|
|
85
82
|
path: FILE_UPLOAD_ENDPOINT.UPLOAD_COMPLETE,
|
|
86
83
|
apiKey: this.#accessKey,
|
|
87
84
|
stagingUrl: this.#stagingUrl,
|
|
@@ -91,7 +88,11 @@ export default class FileUpload {
|
|
|
91
88
|
parts,
|
|
92
89
|
originalFileName: file.name,
|
|
93
90
|
},
|
|
94
|
-
})
|
|
91
|
+
}).then((response) => {
|
|
92
|
+
callBack?.({eventName:"uploading_complete_end",message:`Multipart upload completed successfully`})
|
|
93
|
+
return response;
|
|
94
|
+
});
|
|
95
|
+
},
|
|
95
96
|
|
|
96
97
|
signPart: (file: any, partData: any) =>
|
|
97
98
|
fetchData({
|
|
@@ -114,7 +115,11 @@ export default class FileUpload {
|
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
this.#uppyIns.on("upload-error", (file: any, error: any, response: any) => {
|
|
117
|
-
|
|
118
|
+
if (error.isNetworkError) {
|
|
119
|
+
this.#uppyIns.retryUpload(file.id);
|
|
120
|
+
}else{
|
|
121
|
+
reject(error);
|
|
122
|
+
}
|
|
118
123
|
});
|
|
119
124
|
this.#uppyIns.on("upload-success", () => {
|
|
120
125
|
resolve({ message: "file uploaded successfully" });
|
|
@@ -185,13 +190,17 @@ export default class FileUpload {
|
|
|
185
190
|
}
|
|
186
191
|
});
|
|
187
192
|
}
|
|
188
|
-
async setDeviceInfo(data:SetDeviceInfo) {
|
|
189
|
-
const {scanId
|
|
193
|
+
async setDeviceInfo(data: SetDeviceInfo) {
|
|
194
|
+
const { scanId, ...rest } = data;
|
|
190
195
|
if (checkParameters(scanId) === false) {
|
|
191
196
|
throw new Error(REQUIRED_MESSAGE);
|
|
192
|
-
|
|
193
|
-
return axios.post(
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
}
|
|
198
|
+
return axios.post(
|
|
199
|
+
`${getUrl({ urlName: APP_AUTH_BASE_URL, stagingUrl: this.#stagingUrl })}${API_ENDPOINTS.DEVICE_INFO}/${scanId}`,
|
|
200
|
+
{ device_info: { ...rest } },
|
|
201
|
+
{
|
|
202
|
+
headers: { "X-Api-Key": this.#accessKey },
|
|
203
|
+
},
|
|
204
|
+
);
|
|
196
205
|
}
|
|
197
206
|
}
|
package/src/measurement.ts
CHANGED
|
@@ -3,15 +3,7 @@ import { API_ENDPOINTS, APP_AUTH_BASE_URL, APP_BASE_WEBSOCKET_URL, REQUIRED_MESS
|
|
|
3
3
|
import { checkParameters, getUrl } from "./utils.js";
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
shopDomain: string;
|
|
8
|
-
scanId: string;
|
|
9
|
-
productName: string;
|
|
10
|
-
onError?: (error: any) => void;
|
|
11
|
-
onSuccess?: (data: any) => void;
|
|
12
|
-
onClose?: () => void;
|
|
13
|
-
onOpen?: () => void;
|
|
14
|
-
}
|
|
6
|
+
|
|
15
7
|
|
|
16
8
|
interface MeasurementRecommendation {
|
|
17
9
|
shopDomain: string;
|
|
@@ -45,7 +37,6 @@ interface HandleTimeOutOptions {
|
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
class Measurement {
|
|
48
|
-
#tryOnSocketRef: WebSocket | null = null;
|
|
49
40
|
#measurementSocketRef: WebSocket | null = null;
|
|
50
41
|
#timerPollingRef: NodeJS.Timeout | null = null;
|
|
51
42
|
#timerWaitingRef: NodeJS.Timeout | null = null;
|
|
@@ -80,61 +71,7 @@ class Measurement {
|
|
|
80
71
|
);
|
|
81
72
|
}
|
|
82
73
|
|
|
83
|
-
getTryOnMeasurements({ scanId, shopDomain, productName }: TryOnSocketOptions): Promise<AxiosResponse<any>> {
|
|
84
|
-
if (!checkParameters(scanId, shopDomain, productName)) {
|
|
85
|
-
throw new Error(REQUIRED_MESSAGE);
|
|
86
|
-
}
|
|
87
|
-
const tryOnUrl = `${getUrl({ urlName: APP_AUTH_BASE_URL, stagingUrl: this.#stagingUrl })}${
|
|
88
|
-
API_ENDPOINTS.TRY_ON_SCAN
|
|
89
|
-
}/${scanId}/shop/${shopDomain}/product/${productName}`;
|
|
90
|
-
return axios.get(tryOnUrl, { headers: { "X-Api-Key": this.#accessKey } });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
handleTryOnSocket(options: TryOnSocketOptions): void {
|
|
94
|
-
const { shopDomain, scanId, productName, onError, onSuccess, onClose, onOpen } = options;
|
|
95
|
-
|
|
96
|
-
if (!checkParameters(shopDomain, scanId, productName)) {
|
|
97
|
-
throw new Error(REQUIRED_MESSAGE);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.#tryOnSocketRef?.close();
|
|
101
|
-
const url = `${getUrl({
|
|
102
|
-
urlName: APP_BASE_WEBSOCKET_URL,
|
|
103
|
-
stagingUrl: this.#stagingUrl,
|
|
104
|
-
})}/develop?store_url=${shopDomain}&product_name=${productName}&scan_id=${scanId}`;
|
|
105
|
-
this.#tryOnSocketRef = new WebSocket(url);
|
|
106
|
-
if (this.#tryOnSocketRef) {
|
|
107
|
-
this.#tryOnSocketRef.onopen = () => {
|
|
108
|
-
onOpen?.();
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
this.#tryOnSocketRef.onmessage = (event) => {
|
|
112
|
-
let data;
|
|
113
|
-
try {
|
|
114
|
-
data = JSON.parse(event.data);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.log(data, error, "noy correct format for data");
|
|
117
|
-
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (data?.tryOnProcessStatus === "available") {
|
|
121
|
-
onSuccess?.(data);
|
|
122
|
-
} else {
|
|
123
|
-
onError?.({ message: "failed to get image urls" });
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
74
|
|
|
127
|
-
this.#tryOnSocketRef.onclose = () => {
|
|
128
|
-
onClose?.();
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
this.#tryOnSocketRef.onerror = (event) => {
|
|
132
|
-
onError?.(event);
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
75
|
async #getMeasurementsCheck(options: GetMeasurementsCheckOptions): Promise<void> {
|
|
139
76
|
const { scanId, onSuccess, onError } = options;
|
|
140
77
|
|