import { getProductApiErrorMessage } from '@/services/apiErrorLocalization' /** * Business-rule / expected client API codes. These are user-facing 4xx * outcomes (block, full event, invalid OTP…), not application crashes. * Keep INTERNAL_ERROR, SERVICE_UNAVAILABLE, FORBIDDEN, and NOT_FOUND out — * those still need Sentry when they leak as unhandled exceptions. */ export const EXPECTED_PRODUCT_API_CODES = new Set([ 'MESSAGING_BLOCKED', 'CHAT_CLOSED', 'NOT_PARTICIPANT', 'SELF_ACTION_FORBIDDEN', 'OTP_INVALID', 'OTP_EXPIRED', 'OTP_RATE_LIMITED', 'OTP_ATTEMPTS_EXCEEDED', 'TERMS_ACCEPTANCE_REQUIRED', 'EVENT_FULL', 'EVENT_NOT_BOOKABLE', 'WAITLIST_NOT_AVAILABLE', 'ACTIVE_BOOKING_EXISTS', 'ALREADY_BOOKED', 'IDENTITY_REQUIRED', 'HOST_EVENT_LIMIT_REACHED', 'REVIEW_WINDOW_EXPIRED', 'HOST_REPLY_WINDOW_EXPIRED', 'REVIEW_LOCKED', 'EVENT_NOT_EDITABLE', 'EVENT_FIELD_LOCKED', 'PRICE_LOCKED', 'CAPACITY_BELOW_BOOKED', 'BOOKING_NOT_CONFIRMED', 'NOT_CHECKED_IN', 'ACCOUNT_SUSPENDED', 'ACCOUNT_DELETED', 'VALIDATION_FAILED', 'RATE_LIMITED', 'PUSH_SUBSCRIPTION_LIMIT_EXCEEDED', 'EVENT_HAS_ACTIVE_BOOKINGS', 'UNAUTHORIZED', 'ACCESS_TOKEN_INVALID', 'ACCESS_TOKEN_EXPIRED', 'REFRESH_TOKEN_INVALID', 'REFRESH_TOKEN_EXPIRED', ]) const EXPECTED_PRODUCT_MESSAGES = new Set( [...EXPECTED_PRODUCT_API_CODES].flatMap((code) => { const message = getProductApiErrorMessage(code) return message ? [message] : [] }) ) /** Soft session-expiry signal from axios `handleSessionExpired` — not an app crash. */ EXPECTED_PRODUCT_MESSAGES.add('نشست منقضی شده است') const readApiCode = (error: object): string | undefined => { if ('apiCode' in error && typeof error.apiCode === 'string') return error.apiCode if ('code' in error && typeof error.code === 'string') return error.code return undefined } export const isExpectedProductMessage = (message: string): boolean => EXPECTED_PRODUCT_MESSAGES.has(message.trim()) export const isExpectedProductError = (error: unknown): boolean => { if (typeof error === 'string') return isExpectedProductMessage(error) if (!error || typeof error !== 'object') return false const apiCode = readApiCode(error) if (apiCode && EXPECTED_PRODUCT_API_CODES.has(apiCode)) return true return 'message' in error && typeof error.message === 'string' && isExpectedProductMessage(error.message) } export const shouldDropExpectedProductSentryEvent = ( event: { exception?: { values?: { value?: string }[] }; message?: string }, originalException?: unknown ): boolean => { if (isExpectedProductError(originalException)) return true const value = event.exception?.values?.[0]?.value ?? event.message return typeof value === 'string' && isExpectedProductMessage(value) }