randomayzer/src/integrations/vk/vk-errors.ts

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 });
}