The GraphQL query or mutation string.
Optional
variables: Record<string, unknown>Optional variables for the query.
Optional
token: stringOptional authentication token.
Optional
abortSignal: AbortSignalOptional abort signal to cancel the request.
Optional
bypassCache: booleanOptional flag to bypass cache.
A promise resolving to an AniListResponse object.
export async function request<T>(
query: string,
variables?: Record<string, unknown>,
token?: string,
abortSignal?: AbortSignal,
bypassCache?: boolean,
): Promise<AniListResponse<T>> {
// Generate a unique request ID for tracking this request in logs
const requestId = Math.random().toString(36).substring(2, 8);
// Check if we're running in a browser or Electron environment
const isElectron = typeof window !== "undefined" && window.electronAPI;
// Create request options
const options: RequestInit = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
query,
variables,
}),
signal: abortSignal,
};
// Add authorization header if token is provided
if (token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`,
};
}
// Always log requests in development and production for debugging
const queryFirstLine = query.trim().split("\n")[0].substring(0, 50);
console.log(`📡 [${requestId}] GraphQL Request: ${queryFirstLine}...`);
console.log(
`📦 [${requestId}] Variables:`,
JSON.stringify(variables, null, 2),
);
console.log(
`📜 [${requestId}] Full Query:`,
query.replace(/\s+/g, " ").trim(),
);
// For Electron, use IPC to make the request through the main process
if (isElectron) {
try {
// Use the correct call format for the main process
const response = await window.electronAPI.anilist.request(
query,
{ ...variables, bypassCache }, // Pass bypassCache flag to main process
token,
// We can't pass AbortSignal through IPC, but we'll check it after
);
// Check for abort before returning the response
if (abortSignal?.aborted) {
throw new DOMException("The operation was aborted", "AbortError");
}
// Log response for debugging
console.log(
`✅ [${requestId}] Response received:`,
JSON.stringify(response, null, 2),
);
return response as AniListResponse<T>;
} catch (error) {
console.error(
`❌ [${requestId}] Error during AniList API request:`,
error,
);
throw error;
}
}
// For browser environment, use fetch directly
else {
try {
const response = await fetch("https://graphql.anilist.co", options);
if (!response.ok) {
const errorText = await response.text();
let errorData;
try {
errorData = JSON.parse(errorText);
} catch {
errorData = { raw: errorText };
}
console.error(
`❌ [${requestId}] HTTP Error ${response.status}:`,
errorData,
);
// Check for rate limiting
if (response.status === 429) {
// Extract the retry-after header
const retryAfter = response.headers.get("Retry-After");
const retrySeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
// Notify the application about rate limiting through a custom event
try {
// Dispatch global event for rate limiting that can be caught by our context
window.dispatchEvent(
new CustomEvent("anilist:rate-limited", {
detail: {
retryAfter: retrySeconds,
message: `Rate limited by AniList API. Please retry after ${retrySeconds} seconds.`,
},
}),
);
} catch (e) {
console.error("Failed to dispatch rate limit event:", e);
}
const error = {
status: response.status,
statusText: response.statusText,
message: `Rate limit exceeded. Please retry after ${retrySeconds} seconds.`,
retryAfter: retrySeconds,
isRateLimited: true,
...errorData,
};
console.warn(
`⏳ [${requestId}] Rate limited, retry after ${retrySeconds}s`,
);
throw error;
}
throw {
status: response.status,
statusText: response.statusText,
...errorData,
};
}
const jsonResponse = await response.json();
// Log response for debugging
console.log(
`✅ [${requestId}] Response received:`,
JSON.stringify(jsonResponse, null, 2),
);
// Check for GraphQL errors
if (jsonResponse.errors) {
console.error(`⚠️ [${requestId}] GraphQL Errors:`, jsonResponse.errors);
}
return jsonResponse as AniListResponse<T>;
} catch (error) {
console.error(
`❌ [${requestId}] Error during AniList API request:`,
error,
);
throw error;
}
}
}
Make a request to the AniList API.
Supports dynamic mutations where variable declarations may change based on the variables object passed. Handles both browser and Electron environments.