219 lines
7.7 KiB
TypeScript
219 lines
7.7 KiB
TypeScript
import { VkApiRawError } from './vk-types';
|
|
|
|
/**
|
|
* Base abstract class for all VK client errors.
|
|
* Guarantees that access tokens are never leaked into error messages or details.
|
|
*/
|
|
export abstract class VkClientError extends Error {
|
|
abstract readonly isRetryable: boolean;
|
|
abstract readonly category: string;
|
|
readonly errorCode?: number;
|
|
readonly method?: string;
|
|
readonly details?: any;
|
|
|
|
constructor(message: string, options?: { errorCode?: number; method?: string; details?: any }) {
|
|
super(message);
|
|
this.name = this.constructor.name;
|
|
this.errorCode = options?.errorCode;
|
|
this.method = options?.method;
|
|
this.details = options?.details;
|
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* VK Auth Error: Invalid, expired, or missing access_token (VK error codes: 4, 5, 28, HTTP 401)
|
|
*/
|
|
export class VkAuthError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'AUTH';
|
|
}
|
|
|
|
/**
|
|
* VK Reauthentication Required Error: Stored organizer credentials expired, revoked, or refresh failed.
|
|
*/
|
|
export class VkReauthenticationRequiredError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'REAUTHENTICATION_REQUIRED';
|
|
}
|
|
|
|
/**
|
|
* VK Permission Error: Insufficient permissions for method or scope (VK error codes: 7, 15, 260, HTTP 403)
|
|
*/
|
|
export class VkPermissionError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'PERMISSION';
|
|
}
|
|
|
|
/**
|
|
* VK Rate Limit Error: Too many requests per second or flood control (VK error codes: 6, 9, 29, HTTP 429)
|
|
*/
|
|
export class VkRateLimitError extends VkClientError {
|
|
readonly isRetryable = true;
|
|
readonly category = 'RATE_LIMIT';
|
|
}
|
|
|
|
/**
|
|
* VK Private Resource Error: Target group/user profile is private or access is restricted (VK error codes: 15, 30, 203)
|
|
*/
|
|
export class VkPrivateResourceError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'PRIVATE_RESOURCE';
|
|
}
|
|
|
|
/**
|
|
* VK Not Found Error: Wall post, group, or resource does not exist (VK error codes: 104, 210, 214, HTTP 404)
|
|
*/
|
|
export class VkNotFoundError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'NOT_FOUND';
|
|
}
|
|
|
|
/**
|
|
* VK Validation Error: Malformed parameters or bad request (VK error codes: 8, 100, 113, 150, HTTP 400)
|
|
*/
|
|
export class VkValidationError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'VALIDATION';
|
|
}
|
|
|
|
/**
|
|
* VK Temporary Error: Unknown error or internal server error from VK API (VK error codes: 1, 10, HTTP 500, 502, 503, 504)
|
|
*/
|
|
export class VkTemporaryError extends VkClientError {
|
|
readonly isRetryable = true;
|
|
readonly category = 'TEMPORARY';
|
|
}
|
|
|
|
/**
|
|
* VK Network Error: Connection refused, DNS failure, or aborted network socket
|
|
*/
|
|
export class VkNetworkError extends VkClientError {
|
|
readonly isRetryable = true;
|
|
readonly category = 'NETWORK';
|
|
}
|
|
|
|
/**
|
|
* VK Timeout Error: Request was aborted due to internal client timeout or VK method timeout (VK error code: 36)
|
|
*/
|
|
export class VkTimeoutError extends VkClientError {
|
|
readonly isRetryable = true;
|
|
readonly category = 'TIMEOUT';
|
|
}
|
|
|
|
/**
|
|
* VK Cancelled Error: Request was cancelled by the caller via AbortSignal (NEVER retryable)
|
|
*/
|
|
export class VkCancelledError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'CANCELLED';
|
|
}
|
|
|
|
/**
|
|
* VK Pagination Limit Error: Pagination hit maxPages safety threshold before fetching all items
|
|
*/
|
|
export class VkPaginationLimitError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'PAGINATION_LIMIT_REACHED';
|
|
}
|
|
|
|
/** VK stopped returning pages before its reported total was collected. */
|
|
export class VkIncompleteDataError extends VkClientError {
|
|
readonly isRetryable = false;
|
|
readonly category = 'INCOMPLETE_DATA';
|
|
}
|
|
|
|
// Alias for backwards compatibility
|
|
export const VkPaginationTruncatedError = VkPaginationLimitError;
|
|
|
|
/**
|
|
* Sanitizes request params returned by VK to redact any sensitive token values.
|
|
*/
|
|
function sanitizeRequestParams(params?: Array<{ key: string; value: string }>): Array<{ key: string; value: string }> | undefined {
|
|
if (!params) return undefined;
|
|
return params.map(p => {
|
|
if (p.key.toLowerCase().includes('token') || p.key.toLowerCase().includes('access_token')) {
|
|
return { key: p.key, value: '[REDACTED]' };
|
|
}
|
|
return p;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Maps raw VK API error_code strictly according to official VKCOM/vk-api-schema.
|
|
*/
|
|
export function mapVkApiError(raw: VkApiRawError, method: string): VkClientError {
|
|
const code = raw.error_code;
|
|
const sanitizedParams = sanitizeRequestParams(raw.request_params);
|
|
const msg = raw.error_msg || raw.error_text || `VK API Error (${code})`;
|
|
|
|
switch (code) {
|
|
case 1: // Unknown error occurred
|
|
case 10: // Internal server error
|
|
return new VkTemporaryError(`VK Server Temporary Error (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 4: // Incorrect signature
|
|
case 5: // User authorization failed
|
|
case 28: // Application authorization failed
|
|
return new VkAuthError(`VK Authentication Error (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 6: // Too many requests per second
|
|
case 9: // Flood control
|
|
case 29: // Rate limit reached
|
|
return new VkRateLimitError(`VK Rate Limit Error (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 7: // Permission to perform this action is denied
|
|
case 260: // Access to the group is denied
|
|
return new VkPermissionError(`VK Permission Denied (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 8: // Invalid request
|
|
case 100: // One of the parameters specified was missing or invalid
|
|
case 113: // Invalid user id
|
|
case 150: // Invalid timestamp
|
|
return new VkValidationError(`VK Invalid Parameters (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 15: // Access denied (private object)
|
|
case 30: // This profile is private
|
|
case 203: // Access to the group is denied
|
|
return new VkPrivateResourceError(`VK Private Resource Access Denied (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 36: // Compile or method execution timeout on VK server side
|
|
return new VkTimeoutError(`VK Method Execution Timeout (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
case 104: // Not found
|
|
case 210: // Access to wall's post denied or post not found
|
|
case 214: // Access to adding post denied / not found
|
|
return new VkNotFoundError(`VK Resource Not Found (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
|
|
default:
|
|
return new VkValidationError(`VK API Error (${code}): ${msg}`, { errorCode: code, method, details: sanitizedParams });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps HTTP status codes into typed VK client errors with proper retry classification.
|
|
*/
|
|
export function mapHttpStatusError(status: number, statusText: string, method: string): VkClientError {
|
|
const msg = `VK API HTTP error ${status}: ${statusText || 'Unknown'}`;
|
|
|
|
if (status === 429) {
|
|
return new VkRateLimitError(msg, { errorCode: status, method });
|
|
}
|
|
if (status >= 500 && status <= 504) {
|
|
return new VkTemporaryError(msg, { errorCode: status, method });
|
|
}
|
|
if (status === 401) {
|
|
return new VkAuthError(msg, { errorCode: status, method });
|
|
}
|
|
if (status === 403) {
|
|
return new VkPermissionError(msg, { errorCode: status, method });
|
|
}
|
|
if (status === 404) {
|
|
return new VkNotFoundError(msg, { errorCode: status, method });
|
|
}
|
|
if (status >= 400 && status < 500) {
|
|
return new VkValidationError(msg, { errorCode: status, method });
|
|
}
|
|
|
|
return new VkNetworkError(msg, { errorCode: status, method });
|
|
}
|