ttf-api
Version:
The TrueToForm API SDK
364 lines (340 loc) • 11 kB
JavaScript
import { createClient, sessionIdKey, userIdKey } from "./client";
import { analyticsEventsWrapper } from "./analytics";
/**
* Creates an SDK instance with the provided API key.
*/
const sdk = (apiKey) => {
const client = createClient(apiKey);
const isStale = async () => {
try {
const eTag = localStorage.getItem("ttf-sdk-eTag");
const response = await client.get("stale-check", null, eTag);
const { currentETag } = response;
localStorage.setItem("ttf-sdk-eTag", currentETag);
return eTag !== currentETag;
} catch (error) {
console.error("Error checking if SDK is stale:", error);
return true;
}
};
const getAnalyticsEvents = analyticsEventsWrapper(client);
const createRequestEvent = (props) => {
const createCustomEvent = getAnalyticsEvents({}).addCustomEvent;
return createCustomEvent("server_request", props);
};
const initAnalytics = async () => {
return client.post("analytics/init").then((response) => {
const { userId, sessionId } = response;
localStorage.setItem(userIdKey, userId);
localStorage.setItem(sessionIdKey, sessionId);
return response;
});
};
const getGarment = (id) => {
const startTime = Date.now();
if (!id) {
throw new Error("garment ID is required");
}
return client.get(`garments/${id}`).finally(() => {
createRequestEvent({ name: "get_garment", startTime, garmentId: id });
});
};
const getSurvey = (id) => {
const startTime = Date.now();
if (!id) {
throw new Error("survey ID is required");
}
return client.get(`surveys/${id}`).finally(() => {
createRequestEvent({ name: "get_survey", startTime, surveyId: id });
});
};
const createSurvey = (surveyData) => {
const startTime = Date.now();
if (!surveyData) {
throw new Error("survey data is required");
}
// TODO: Validate survey data on SDK side
return client.post("surveys", surveyData).finally(() => {
createRequestEvent({ name: "create_survey", startTime });
});
};
const getMeasurements = (id) => {
const startTime = Date.now();
if (!id) {
throw new Error("measurement ID is required");
}
return client.get(`measurements/${id}`).finally(() => {
createRequestEvent({
name: "get_measurements",
startTime,
measurementId: id,
});
});
};
const createMeasurements = (measurementData) => {
const startTime = Date.now();
if (!measurementData) {
throw new Error("measurement data is required");
}
return client.post("measurements", measurementData).finally(() => {
createRequestEvent({ name: "create_measurements", startTime });
});
};
const getScan = (id) => {
const startTime = Date.now();
if (!id) {
throw new Error("scan ID is required");
}
return client.get(`scans/${id}`).finally(() => {
createRequestEvent({ name: "get_scan", startTime, scanId: id });
});
};
const createScan = (sessionId) => {
const startTime = Date.now();
if (!sessionId) {
throw new Error("session ID is required");
}
return client.post("scans", { sessionId }).finally(() => {
createRequestEvent({ name: "create_scan", startTime });
});
};
const getPrediction = (id) => {
const startTime = Date.now();
if (!id) {
throw new Error("prediction ID is required");
}
return client.get(`predictions/${id}`).finally(() => {
createRequestEvent({
name: "get_prediction",
startTime,
predictionId: id,
});
});
};
const createSession = () => {
const startTime = Date.now();
return client.post("sessions").finally(() => {
createRequestEvent({ name: "create_session", startTime });
});
};
const getSessionStatus = (sessionId) => {
const startTime = Date.now();
if (!sessionId) {
throw new Error("session ID is required");
}
return client.get(`sessions/${sessionId}/status`).finally(() => {
createRequestEvent({ name: "get_session_status", startTime });
});
};
const getRealtimeSession = (sessionId, { onChange, onSuccess, onError }) => {
let unsubscribe = () => {};
async function handleRealtimeSession() {
try {
const startTime = Date.now();
const controller = new AbortController();
const signal = controller.signal;
unsubscribe = () => controller.abort();
const resBody = await client
.get(`sessions/${sessionId}`, signal)
.finally(() => {
createRequestEvent({
name: "get_realtime_session",
startTime,
sessionId,
});
});
const reader = resBody?.getReader();
const decoder = new TextDecoder();
let { done, value } = await reader.read();
while (!done) {
const chunk = decoder.decode(value, { stream: true });
try {
const data = chunk
?.split("\n")
.filter((str) => str)
.map((line) => {
return JSON.parse(line);
});
console.log("SSE data:", data);
// loop through the data array
for (let i = 0; i < data.length; i++) {
const item = data[i];
console.log("SSE item:", item);
switch (item.type) {
case "SESSION_DATA":
if (onChange && typeof onChange === "function") {
try {
onChange(item.payload?.progress);
} catch (error) {
console.error("Error calling onChange:", error);
}
}
if (onSuccess && typeof onSuccess === "function") {
const isReady = item.payload?.progress === "READY";
if (isReady) {
try {
onSuccess(item.payload);
} catch (error) {
console.error("Error calling onSuccess:", error);
}
}
}
if (onError && typeof onError === "function") {
const isFailed = item.payload?.progress === "FAILED";
if (isFailed) {
try {
onError(item.payload);
} catch (error) {
console.error("Error calling onError:", error);
}
}
}
break;
case "ERROR":
console.error("SSE error:", data);
if (onError && typeof onError === "function") {
try {
onError(item);
} catch (error) {
console.error("Error calling onError:", error);
}
}
break;
default:
break;
}
}
} catch (parseError) {
console.error("Error parsing SSE data:", parseError);
if (onError && typeof onError === "function") {
try {
onError({ type: "ERROR", message: "Parsing error" });
} catch (error) {
console.error("Error calling onError:", error);
}
}
}
// Continue reading the stream
({ done, value } = await reader.read());
}
} catch (error) {
console.error("Stream error:", error);
if (onError && typeof onError === "function") {
// call onError only if the error is not an abort error
if (error.name !== "AbortError") {
onError({ type: "ERROR", message: error.message });
}
}
}
}
handleRealtimeSession();
return unsubscribe;
};
const createScanPrediction = async (garmentId, sessionId) => {
const startTime = Date.now();
if (!garmentId) {
throw new Error("garment ID is required");
}
if (!sessionId) {
throw new Error("session ID is required");
}
const scan = await createScan(sessionId);
if (!scan || !scan.id) {
throw new Error("Failed to create scan");
}
return client
.post("predictions", { garmentId, creatorId: scan.id })
.finally(() => {
createRequestEvent({
name: "create_scan_prediction",
startTime,
garmentId,
sessionId,
});
});
};
const createSurveyPrediction = async (garmentId, surveyData) => {
const startTime = Date.now();
if (!garmentId) {
throw new Error("garment ID is required");
}
if (!surveyData) {
throw new Error("survey data is required");
}
const survey = await createSurvey(surveyData);
if (!survey || !survey.id) {
throw new Error("Failed to create survey");
}
return client
.post("predictions", { garmentId, creatorId: survey.id })
.finally(() => {
createRequestEvent({
name: "create_survey_prediction",
startTime,
garmentId,
surveyId: survey.id,
});
});
};
const createMeasurementsPrediction = async (garmentId, measurementsData) => {
const startTime = Date.now();
if (!garmentId) {
throw new Error("garment ID is required");
}
if (!measurementsData) {
throw new Error("measurements data is required");
}
const measurements = await createMeasurements(measurementsData);
if (!measurements || !measurements.id) {
throw new Error("Failed to create measurements");
}
return client
.post("predictions", { garmentId, creatorId: measurements.id })
.finally(() => {
createRequestEvent({
name: "create_measurements_prediction",
startTime,
garmentId,
measurementsId: measurements.id,
});
});
};
const createPredictionFromId = (garmentId, creatorId) => {
const startTime = Date.now();
if (!garmentId) {
throw new Error("garment ID is required");
}
if (!creatorId) {
throw new Error("creator ID is required");
}
return client.post("predictions", { garmentId, creatorId }).finally(() => {
createRequestEvent({
name: "create_background_prediction",
startTime,
garmentId,
creatorId,
});
});
};
return {
isStale,
initAnalytics,
getGarment,
createSurvey,
getSurvey,
createMeasurements,
getMeasurements,
createScan,
getScan,
getPrediction,
createSession,
createScanPrediction,
createSurveyPrediction,
createMeasurementsPrediction,
createPredictionFromId,
getRealtimeSession,
getSessionStatus,
getAnalyticsEvents,
};
};
export default sdk;