erlc.ts
Version:
Clean and typesafe PRC API client for TypeScript
319 lines • 12.1 kB
JavaScript
import { Cache } from './cache.js';
import { PRCAPIError } from './errors.js';
import { RateLimiter } from './ratelimit.js';
/**
* PRCClient provides methods to interact with the Police Roleplay Community API.
* Handles caching, rate limiting, and error management for API requests.
*/
export class PRCClient {
baseURL;
serverKey;
globalKey;
cache;
headers;
/**
* Creates a new PRCClient instance.
* @param {PRCClientOptions} options - Configuration options for the client.
* @throws {Error} If neither serverKey nor globalKey is provided.
*/
constructor(options = {}) {
if (!options.serverKey && !options.globalKey) {
throw new Error('Either serverKey or globalKey must be provided');
}
this.baseURL = options.baseURL || 'https://api.policeroleplay.community/v1';
this.serverKey = options.serverKey;
this.globalKey = options.globalKey;
this.cache = options.cache !== false ? new Cache(options.cacheMaxAge, options.redisUrl, options.redisKeyPrefix) : null;
this.headers = this.buildHeaders();
}
/**
* Builds the headers for API requests.
* @returns {Record<string, string>} The headers object.
*/
buildHeaders() {
const headers = {
'Content-Type': 'application/json',
'Accept': '*/*',
};
if (this.globalKey) {
headers['Authorization'] = this.globalKey;
}
if (this.serverKey) {
headers['Server-Key'] = this.serverKey;
}
return headers;
}
/**
* Retrieves cached data if available.
* @template T
* @param {string} cacheKey - The cache key.
* @returns {Promise<T | null>} The cached data or null.
*/
async getCachedData(cacheKey) {
if (this.cache) {
return await this.cache.get(cacheKey);
}
return null;
}
/**
* Sets data in the cache.
* @template T
* @param {string} cacheKey - The cache key.
* @param {T} data - The data to cache.
* @param {number} [maxAge] - Optional custom max age for this cache entry.
*/
async setCachedData(cacheKey, data, maxAge) {
if (this.cache) {
await this.cache.set(cacheKey, data, maxAge);
}
}
/**
* Builds the fetch options for the request.
* @param {'GET' | 'POST'} method - HTTP method.
* @param {any} [body] - Request body.
* @returns {RequestInit} The fetch options.
*/
buildFetchOptions(method, body) {
const fetchOptions = {
method,
headers: this.headers,
};
if (method !== 'GET' && body) {
fetchOptions.body = JSON.stringify(body);
}
return fetchOptions;
}
/**
* Determines if the request should be retried based on the error.
* @param {any} errorBody - The error response body.
* @param {number} retryCount - Current retry count.
* @param {number} maxRetries - Maximum retries.
* @returns {Promise<boolean>} Whether to retry.
*/
async shouldRetry(errorBody, retryCount, maxRetries) {
if (errorBody &&
(errorBody.code === 4001 || errorBody.errorCode === 4001) &&
retryCount < maxRetries) {
if (typeof errorBody.retry_after === 'number' && errorBody.retry_after > 0) {
await RateLimiter.waitForRetryAfter(errorBody.retry_after);
}
return true;
}
return false;
}
/**
* Parses the response data based on content type.
* @template T
* @param {Response} response - The fetch response.
* @returns {Promise<T>} The parsed data.
*/
async parseResponseData(response) {
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
return (await response.json());
}
return null;
}
/**
* Handles error responses by throwing an appropriate error.
* @param {Response} response - The fetch response.
* @param {any} errorBody - The error response body.
*/
handleErrorResponse(response, errorBody) {
throw PRCAPIError.fromResponse(response, errorBody);
}
/**
* Makes an HTTP request to the API, handling caching, rate limiting, and retries.
* @template T
* @param {'GET' | 'POST'} method - HTTP method.
* @param {string} endpoint - API endpoint.
* @param {any} [body] - Request body for POST requests.
* @param {boolean} [cacheable=false] - Whether to use cache for GET requests.
* @param {number} [cacheMaxAge] - Optional custom cache max age for this request.
* @param {boolean} [cacheOverride] - Optional per-method cache override.
* @returns {Promise<APIResponse<T>>} The API response.
* @throws {PRCAPIError} If the request fails.
*/
async makeRequest(method, endpoint, body, cacheable = false, cacheMaxAge, cacheOverride) {
const url = `${this.baseURL}${endpoint}`;
const cacheKey = `${endpoint}`;
const MAX_RETRIES = 3;
// Determine if caching should be used for this request
const shouldCache = method === 'GET' && (cacheOverride !== undefined ? cacheOverride : cacheable);
if (shouldCache) {
const cachedData = await this.getCachedData(cacheKey);
if (cachedData !== null)
return { data: cachedData };
}
let retryCount = 0;
while (true) {
const fetchOptions = this.buildFetchOptions(method, body);
const response = await fetch(url, fetchOptions);
if (!response.ok) {
let errorBody = {};
try {
errorBody = await response.json();
}
catch { }
if (await this.shouldRetry(errorBody, retryCount, MAX_RETRIES)) {
retryCount++;
continue;
}
this.handleErrorResponse(response, errorBody);
}
const data = await this.parseResponseData(response);
if (shouldCache)
await this.setCachedData(cacheKey, data, cacheMaxAge);
return { data };
}
}
/**
* Gets the current server status.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<ServerStatus>>} The server status response.
*/
async getServerStatus(options) {
return this.makeRequest('GET', '/server', undefined, true, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the list of current players on the server.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<Player[]>>} The players response.
*/
async getPlayers(options) {
return this.makeRequest('GET', '/server/players', undefined, true, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the current server queue.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<number[]>>} The queue response.
*/
async getQueue(options) {
return this.makeRequest('GET', '/server/queue', undefined, true, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the list of vehicles on the server.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<Vehicle[]>>} The vehicles response.
*/
async getVehicles(options) {
return this.makeRequest('GET', '/server/vehicles', undefined, true, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the list of server bans.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<ServerBans>>} The bans response.
*/
async getBans(options) {
return this.makeRequest('GET', '/server/bans', undefined, true, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the list of server staff.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<ServerStaff>>} The staff response.
*/
async getStaff(options) {
return this.makeRequest('GET', '/server/staff', undefined, true, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the join logs for the server.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<JoinLog[]>>} The join logs response.
*/
async getJoinLogs(options) {
const shouldCache = options?.cache === true && !!options?.cacheMaxAge;
return this.makeRequest('GET', '/server/joinlogs', undefined, shouldCache, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the kill logs for the server.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<KillLog[]>>} The kill logs response.
*/
async getKillLogs(options) {
const shouldCache = options?.cache === true && !!options?.cacheMaxAge;
return this.makeRequest('GET', '/server/killlogs', undefined, shouldCache, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the command logs for the server.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<CommandLog[]>>} The command logs response.
*/
async getCommandLogs(options) {
const shouldCache = options?.cache === true && !!options?.cacheMaxAge;
return this.makeRequest('GET', '/server/commandlogs', undefined, shouldCache, options?.cacheMaxAge, options?.cache);
}
/**
* Gets the mod calls for the server.
* @param {MethodOptions} [options] - Optional method options.
* @returns {Promise<APIResponse<ModCall[]>>} The mod calls response.
*/
async getModCalls(options) {
const shouldCache = options?.cache === true && !!options?.cacheMaxAge;
return this.makeRequest('GET', '/server/modcalls', undefined, shouldCache, options?.cacheMaxAge, options?.cache);
}
/**
* Executes a command on the server.
* @param {string} command - The command to execute.
* @returns {Promise<APIResponse<null>>} The response from the command execution.
*/
async executeCommand(command) {
return this.makeRequest('POST', '/server/command', { command });
}
/**
* Clears the internal cache.
* @returns A promise that resolves when the cache is cleared.
*/
async clearCache() {
if (this.cache) {
await this.cache.clear();
}
}
/**
* Gets the current cache size.
* @returns A promise that resolves to the number of cached items.
*/
async getCacheSize() {
return this.cache ? await this.cache.size() : 0;
}
/**
* Gets a cache entry directly for debugging purposes (only works with in-memory cache).
* @param key - The cache key.
* @returns The cached data or null if not found or using Redis.
*/
getCacheEntry(key) {
if (!this.cache)
return null;
try {
const entry = this.cache.getRawEntry(key);
return entry ? entry.data : null;
}
catch (error) {
return null;
}
}
/**
* Gets all cache keys for debugging purposes (only works with in-memory cache).
* @returns Array of cache keys.
* @throws Error if using Redis cache.
*/
getCacheKeys() {
if (!this.cache)
return [];
try {
return this.cache.getAllKeys();
}
catch (error) {
return [];
}
}
/**
* Disconnects the Redis client if using Redis cache.
* @returns A promise that resolves when disconnected.
*/
async disconnect() {
if (this.cache) {
await this.cache.disconnect();
}
}
}
//# sourceMappingURL=client.js.map