@testdog/ai
Version:
SDK for integrating the Testdog AI Video Intelligence API
193 lines (190 loc) • 7.89 kB
JavaScript
// src/client.ts
import axios from "axios";
import { jwtDecode } from "jwt-decode";
// src/errors.ts
var SdkError = class _SdkError extends Error {
constructor(message) {
super(message);
this.name = "SdkError";
Object.setPrototypeOf(this, _SdkError.prototype);
}
};
var SdkAuthenticationError = class _SdkAuthenticationError extends SdkError {
constructor(message) {
super(message);
this.name = "SdkAuthenticationError";
Object.setPrototypeOf(this, _SdkAuthenticationError.prototype);
}
};
var SdkRequestError = class _SdkRequestError extends SdkError {
constructor(message, statusCode, details) {
super(message);
this.name = "SdkRequestError";
this.statusCode = statusCode;
this.details = details;
Object.setPrototypeOf(this, _SdkRequestError.prototype);
}
};
var SdkInputError = class _SdkInputError extends SdkError {
constructor(message) {
super(message);
this.name = "SdkInputError";
Object.setPrototypeOf(this, _SdkInputError.prototype);
}
};
// src/client.ts
var DEFAULT_API_BASE_URL = "https://ai.sheryians.com/api/v1";
var DEFAULT_IFRAME_BASE_URL = "https://ai.sheryians.com/source/chat";
var TOKEN_EXPIRY_BUFFER_SECONDS = 60;
var testdogAI = class {
// Store expiry timestamp (in seconds)
/**
* Initializes the testdogAI SDK.
* @param config - SDK configuration including accessKey and secretKey.
* @param config.accessKey - The access key for API authentication.
* @param config.secretKey - The secret key for API authentication.
* @param config.apiBaseUrl - Optional base URL for the API
*/
constructor(config) {
this.apiAccessToken = null;
this.apiAccessTokenExpiry = null;
this.accessKey = config.accessKey;
this.secretKey = config.secretKey;
this.iframeBaseUrl = config.iframeBaseUrl || DEFAULT_IFRAME_BASE_URL;
this.apiClient = axios.create({
baseURL: config.apiBaseUrl || DEFAULT_API_BASE_URL,
headers: {
"Content-Type": "application/json"
},
withCredentials: true
// Enable cookies with cross-origin requests
});
}
/**
* Retrieves a valid API Access Token, fetching a new one if necessary.
* @returns The API Access Token.
* @throws {SdkAuthenticationError} If fetching the token fails due to invalid credentials.
* @throws {SdkRequestError} If the API request fails for other reasons.
*/
async getApiAccessToken() {
var _a, _b, _c, _d, _e, _f;
const nowInSeconds = Math.floor(Date.now() / 1e3);
if (this.apiAccessToken && this.apiAccessTokenExpiry && this.apiAccessTokenExpiry > nowInSeconds + TOKEN_EXPIRY_BUFFER_SECONDS) {
return this.apiAccessToken;
}
console.debug("SDK: API Access Token expired or missing, fetching new token...");
try {
const response = await this.apiClient.post(
"/customers/generateAccessToken",
{},
// Empty body
{
headers: {
// Send keys in headers as expected by your backend endpoint
"accessKey": this.accessKey,
"secretKey": this.secretKey
// Use the stored secret key here
},
withCredentials: true
// Ensure cookies are sent with this request
}
);
const token = response.data.token;
if (!token) {
throw new SdkAuthenticationError("Failed to retrieve API access token from response.");
}
this.apiAccessToken = token;
try {
const decoded = jwtDecode(token);
this.apiAccessTokenExpiry = decoded.exp;
console.debug(`SDK: New API Access Token obtained, expires at ${new Date(decoded.exp * 1e3).toISOString()}`);
} catch (decodeError) {
console.error("SDK: Failed to decode new API access token:", decodeError);
this.apiAccessToken = null;
this.apiAccessTokenExpiry = null;
throw new SdkAuthenticationError("Received invalid API access token format.");
}
return this.apiAccessToken;
} catch (error) {
this.apiAccessToken = null;
this.apiAccessTokenExpiry = null;
if (axios.isAxiosError(error)) {
const axiosError = error;
if (((_a = axiosError.response) == null ? void 0 : _a.status) === 401 || ((_b = axiosError.response) == null ? void 0 : _b.status) === 400) {
console.error("SDK Authentication Error:", ((_c = axiosError.response) == null ? void 0 : _c.data) || axiosError.message);
throw new SdkAuthenticationError("Invalid accessKey or secretKey.");
}
console.error("SDK API Request Error (fetching access token):", ((_d = axiosError.response) == null ? void 0 : _d.data) || axiosError.message);
throw new SdkRequestError(
`Failed to fetch API access token: ${axiosError.message}`,
(_e = axiosError.response) == null ? void 0 : _e.status,
(_f = axiosError.response) == null ? void 0 : _f.data
);
}
console.error("SDK Unexpected Error (fetching access token):", error);
throw new SdkError("An unexpected error occurred while fetching the API access token.");
}
}
/**
* Generates a secure URL for the AI chat iframe.
* @param options - Options including studentId, studentName, sourceId, and optionally iframeBaseUrl.
* @returns The generated iframe URL containing a short-lived token.
* @throws {SdkInputError} If required options are missing.
* @throws {SdkAuthenticationError} If SDK authentication fails.
* @throws {SdkRequestError} If the API request to generate the iframe token fails.
*/
async generateIframeUrl(options) {
var _a, _b, _c, _d, _e, _f;
const { studentId, studentName, sourceId } = options;
if (!studentId || !studentName || !sourceId) {
throw new SdkInputError("studentId, studentName, and sourceId are required to generate the iframe URL.");
}
try {
const apiToken = await this.getApiAccessToken();
const response = await this.apiClient.post(
"/iframe/generate-url-token",
{ studentId, studentName, sourceId },
{
headers: {
"Authorization": `Bearer ${apiToken}`
// Use the API Access Token
}
}
);
const iframeToken = response.data.iframeToken;
if (!iframeToken) {
throw new SdkRequestError("Failed to retrieve iframe token from response.");
}
const targetIframeBaseUrl = options.iframeBaseUrl || this.iframeBaseUrl;
const iframeUrl = `${targetIframeBaseUrl}?token=${encodeURIComponent(iframeToken)}`;
return iframeUrl;
} catch (error) {
if (error instanceof SdkAuthenticationError || error instanceof SdkInputError) {
throw error;
}
if (axios.isAxiosError(error)) {
const axiosError = error;
console.error("SDK API Request Error (generating iframe token):", ((_a = axiosError.response) == null ? void 0 : _a.data) || axiosError.message);
if (((_b = axiosError.response) == null ? void 0 : _b.status) === 401 || ((_c = axiosError.response) == null ? void 0 : _c.status) === 403) {
this.apiAccessToken = null;
this.apiAccessTokenExpiry = null;
throw new SdkAuthenticationError(`API access token rejected (status: ${(_d = axiosError.response) == null ? void 0 : _d.status}). Please retry.`);
}
throw new SdkRequestError(
`Failed to generate iframe token: ${axiosError.message}`,
(_e = axiosError.response) == null ? void 0 : _e.status,
(_f = axiosError.response) == null ? void 0 : _f.data
);
}
console.error("SDK Unexpected Error (generating iframe URL):", error);
throw new SdkError("An unexpected error occurred while generating the iframe URL.");
}
}
};
export {
SdkAuthenticationError,
SdkError,
SdkInputError,
SdkRequestError,
testdogAI
};