@swan-admin/swan-ai-measurements
Version:
provides ai measurement suggestion
303 lines (263 loc) • 9.23 kB
text/typescript
import axios, { AxiosResponse } from "axios";
import { API_ENDPOINTS, APP_AUTH_BASE_URL, APP_BASE_WEBSOCKET_URL, REQUIRED_ERROR_MESSAGE_INVALID_EMAIL, REQUIRED_MESSAGE } from "./constants.js";
import { checkParameters, getUrl, isValidEmail } from "./utils.js";
import { URLType } from "./enum.js";
interface UploadFileParams {
files: File[];
userEmail: string;
fileNoLimit?: number;
}
interface DeleteImageParams {
userEmail: string;
fileName: string;
}
interface EligibiltyImageParams {
storeUrl: string;
productHandle: string;
imageURL: string;
productDescription: string;
}
interface HandleTryOnWebSocketParams {
tryonId: string;
onError?: (error: any) => void;
onSuccess?: (data: any) => void;
onClose?: () => void;
onOpen?: () => void;
}
interface Products {
productUrl: string;
productHandle: string;
openTryonId?: string;
selectedProductImageUrl?: string;
}
interface HandleForLatestImageParams {
shopDomain: string;
products: Products[];
selectedUserImages?: string[];
requestSource?: string;
callbackUrl?: string;
openTryonId?: string;
selectedProductImageUrl?: string;
requestedTryonViews?: string[];
}
interface HandleTimeOutParams {
onSuccess?: (data: any) => void;
onError?: (error: any) => void;
tryonId: string;
}
interface GetTryOnResultParams {
tryonId: string;
}
class TryOn {
#socketMap: Map<string, WebSocket> = new Map();
#timerMap: Map<string, ReturnType<typeof setTimeout>> = new Map();
#accessKey?: string;
#urlType: URLType;
#token?: string;
constructor(accessKey?: string, urlType = URLType.PROD, token?: string) {
this.#accessKey = accessKey;
this.#urlType = urlType;
this.#token = token;
}
#getHeaders(token?: string, extraHeaders: Record<string, string> = {}): Record<string, string> {
const requestToken = token ?? this.#token;
return {
...extraHeaders,
...(this.#accessKey ? { "X-Api-Key": this.#accessKey } : {}),
...(requestToken ? { Authorization: `Bearer ${requestToken}` } : {}),
};
}
async uploadFile({ files, userEmail, fileNoLimit = 2 }: UploadFileParams): Promise<string> {
if (checkParameters(files, userEmail) === false) {
throw new Error(REQUIRED_MESSAGE);
}
if (!isValidEmail(userEmail.trim())) {
throw new Error(REQUIRED_ERROR_MESSAGE_INVALID_EMAIL);
}
if (fileNoLimit <= 0) {
throw new Error(`Provide valid file number limit ${fileNoLimit}.`);
}
if (files?.length > fileNoLimit) {
throw new Error(`Cannot allow more than ${fileNoLimit} files.`);
}
try {
const payload: { userEmail: string; userImages: string[] } = {
userEmail,
userImages: [],
};
files?.forEach((file: File) => {
payload.userImages.push(file.name);
});
const signedUrlRes = await this.#getSignedUrl(payload);
for (const file of files) {
await this.#s3Upload(signedUrlRes.data.uploadUrls[file.name].url, file);
}
return `uploaded successfully!`;
} catch (error) {
throw error;
}
}
#getSignedUrl(payload: { userEmail: string; userImages: string[] }): Promise<AxiosResponse<any>> {
if (checkParameters(payload) === false) {
throw new Error(REQUIRED_MESSAGE);
}
return axios.post(`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_IMAGE_UPLOAD}`, payload, {
headers: this.#getHeaders(undefined, { "Content-Type": "application/json" }),
});
}
#s3Upload(url: string, file: File): Promise<AxiosResponse<any>> {
if (checkParameters(url, file) === false) {
throw new Error(REQUIRED_MESSAGE);
}
return axios.put(url, file, {
headers: {
"Content-Type": file.type,
},
});
}
getUploadedFiles(userEmail: string): Promise<AxiosResponse<any>> {
if (checkParameters(userEmail) === false) {
throw new Error(REQUIRED_MESSAGE);
}
if (!isValidEmail(userEmail.trim())) {
throw new Error(REQUIRED_ERROR_MESSAGE_INVALID_EMAIL);
}
const payload = {
userEmail,
};
return axios.post(`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_IMAGE_DOWNLOAD}`, payload, {
headers: this.#getHeaders(),
});
}
deleteImage({ userEmail, fileName }: DeleteImageParams): Promise<AxiosResponse<any>> {
if (checkParameters(userEmail, fileName) === false) {
throw new Error(REQUIRED_MESSAGE);
}
if (!isValidEmail(userEmail.trim())) {
throw new Error(REQUIRED_ERROR_MESSAGE_INVALID_EMAIL);
}
const payload = {
userEmail,
file: fileName,
};
return axios.delete(`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_IMAGE_URLS}`, {
headers: this.#getHeaders(),
data: payload,
});
}
#disconnectSocket = (tryonId?: string): void => {
if (tryonId) {
const socket = this.#socketMap.get(tryonId);
const timer = this.#timerMap.get(tryonId);
socket?.close();
if (timer) clearTimeout(timer);
this.#socketMap.delete(tryonId);
this.#timerMap.delete(tryonId);
} else {
// Disconnect all
this.#socketMap.forEach((socket) => socket.close());
this.#timerMap.forEach((timer) => clearTimeout(timer));
this.#socketMap.clear();
this.#timerMap.clear();
}
};
#handleTimeOut = ({ onSuccess, onError, tryonId }: HandleTimeOutParams): void => {
const timer = setTimeout(() => {
this.#handleGetTryOnResult({ onSuccess, onError, tryonId });
this.#disconnectSocket(tryonId);
}, 300000);
this.#timerMap.set(tryonId, timer);
};
handleTryOnWebSocket = ({ tryonId, onError, onSuccess, onClose, onOpen }: HandleTryOnWebSocketParams): void => {
if (checkParameters(tryonId) === false) {
throw new Error(REQUIRED_MESSAGE);
}
this.#disconnectSocket(tryonId);
const url = `${getUrl({ urlName: APP_BASE_WEBSOCKET_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON}?tryonId=${tryonId}`;
const socket = new WebSocket(url);
this.#socketMap.set(tryonId, socket);
socket.onopen = () => {
onOpen?.();
this.#handleTimeOut({ onSuccess, onError, tryonId });
};
socket.onmessage = (event) => {
let data;
try {
data = JSON.parse(event.data);
} catch (error) {
console.log("Invalid JSON:", event.data);
return;
}
if (data?.eventType === "tryon.completed") {
onSuccess?.(data);
} else if (data?.eventType=="tryon.failed") {
onError?.(data);
}
const timer = this.#timerMap.get(tryonId);
if (timer) clearTimeout(timer);
this.#timerMap.delete(tryonId);
};
socket.onclose = () => {
onClose?.();
};
socket.onerror = (event) => {
onError?.(event);
};
};
handleTryOnSubmit({ shopDomain, products, selectedUserImages, requestSource, callbackUrl, openTryonId, selectedProductImageUrl ,requestedTryonViews}: HandleForLatestImageParams): Promise<AxiosResponse<any>> {
if (checkParameters(shopDomain, products) === false) {
throw new Error(REQUIRED_MESSAGE);
}
const payload = {
products,
customerStoreUrl: shopDomain,
...(selectedUserImages !== undefined && selectedUserImages !== null && { selectedUserImages }),
...(requestSource !== undefined && requestSource !== null && { requestSource }),
...(callbackUrl !== undefined && callbackUrl !== null && { callbackUrl }),
...(openTryonId !== undefined && openTryonId !== null && { openTryonId }),
...(selectedProductImageUrl !== undefined && selectedProductImageUrl !== null && { selectedProductImageUrl }),
...(requestedTryonViews && requestedTryonViews?.length > 0 && { requestedTryonViews }),
};
const url = `${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON}`;
return axios.post(url, payload, {
headers: this.#getHeaders(),
});
}
#handleGetTryOnResult = async ({ onSuccess, onError, tryonId }: HandleTimeOutParams): Promise<void> => {
try {
const data = await this.getTryOnResult({ tryonId });
onSuccess?.(data.data);
} catch (error) {
onError?.(error);
}
};
getShareLink(tryonId: string): Promise<AxiosResponse<any>> {
return axios.post(
`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_SHARE}`,
{ tryonId },
{
headers: this.#getHeaders(),
},
);
}
getTryOnResult = ({ tryonId }: GetTryOnResultParams): Promise<AxiosResponse<any>> => {
if (checkParameters(tryonId) === false) {
throw new Error(REQUIRED_MESSAGE);
}
const url = `${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_RESULT_IMAGE_DOWNLOAD}/${tryonId}`;
return axios.post(url, null, {
headers: this.#getHeaders(),
});
};
getProductImageEligibility({ storeUrl, productHandle, imageURL, productDescription }: EligibiltyImageParams) {
if (checkParameters(storeUrl, productHandle, imageURL) === false) {
throw new Error(REQUIRED_MESSAGE);
}
const payload = { storeUrl, productHandle, imageURL, productDescription: productDescription ?? null };
const url = `${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_PRODUCT_IMAGE_ELIGIBILTY}`;
return axios.post(url, payload, {
headers: this.#getHeaders(),
});
}
}
export default TryOn;