import { HttpStatusCode } from "axios"; export interface ApiErrorResponse { code: string; message: string; } export class ApiError extends Error { httpStatusCode: number; errCode: string; constructor(message: string, errCode: string, httpStatus: number) { super(message); this.name = "ApiError"; this.errCode = errCode; this.httpStatusCode = httpStatus; } } export function isApiErrorResponse(object: any): object is ApiErrorResponse { return object && "code" in object && "message" in object; } export const CustomError = { THUMBNAIL_GENERATION_FAILED: "thumbnail generation failed", VIDEO_PLAYBACK_FAILED: "video playback failed", ETAG_MISSING: "no header/etag present in response body", KEY_MISSING: "encrypted key missing from localStorage", FAILED_TO_LOAD_WEB_WORKER: "failed to load web worker", CHUNK_MORE_THAN_EXPECTED: "chunks more than expected", CHUNK_LESS_THAN_EXPECTED: "chunks less than expected", UNSUPPORTED_FILE_FORMAT: "unsupported file format", FILE_TOO_LARGE: "file too large", SUBSCRIPTION_EXPIRED: "subscription expired", STORAGE_QUOTA_EXCEEDED: "storage quota exceeded", SESSION_EXPIRED: "session expired", INVALID_MIME_TYPE: (type: string) => `invalid mime type -${type}`, SIGNUP_FAILED: "signup failed", FAV_COLLECTION_MISSING: "favorite collection missing", INVALID_COLLECTION_OPERATION: "invalid collection operation", TO_MOVE_FILES_FROM_MULTIPLE_COLLECTIONS: "to move files from multiple collections", WAIT_TIME_EXCEEDED: "operation wait time exceeded", REQUEST_CANCELLED: "request canceled", REQUEST_FAILED: "request failed", TOKEN_EXPIRED: "token expired", TOKEN_MISSING: "token missing", TOO_MANY_REQUESTS: "too many requests", BAD_REQUEST: "bad request", SUBSCRIPTION_NEEDED: "subscription not present", NOT_FOUND: "not found ", NO_METADATA: "no metadata", TOO_LARGE_LIVE_PHOTO_ASSETS: "too large live photo assets", NOT_A_DATE: "not a date", NOT_A_LOCATION: "not a location", FILE_ID_NOT_FOUND: "file with id not found", WEAK_DEVICE: "password decryption failed on the device", INCORRECT_PASSWORD: "incorrect password", UPLOAD_CANCELLED: "upload cancelled", REQUEST_TIMEOUT: "request taking too long", HIDDEN_COLLECTION_SYNC_FILE_ATTEMPTED: "hidden collection sync file attempted", UNKNOWN_ERROR: "Something went wrong, please try again", TYPE_DETECTION_FAILED: (fileFormat: string) => `type detection failed ${fileFormat}`, WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED: "Windows native image processing is not supported", NETWORK_ERROR: "Network Error", NOT_FILE_OWNER: "not file owner", UPDATE_EXPORTED_RECORD_FAILED: "update file exported record failed", EXPORT_STOPPED: "export stopped", NO_EXPORT_FOLDER_SELECTED: "no export folder selected", EXPORT_FOLDER_DOES_NOT_EXIST: "export folder does not exist", NO_INTERNET_CONNECTION: "no internet connection", AUTH_KEY_NOT_FOUND: "auth key not found", EXIF_DATA_NOT_FOUND: "exif data not found", SELECT_FOLDER_ABORTED: "select folder aborted", NON_MEDIA_FILE: "non media file", NOT_AVAILABLE_ON_WEB: "not available on web", UNSUPPORTED_RAW_FORMAT: "unsupported raw format", NON_PREVIEWABLE_FILE: "non previewable file", PROCESSING_FAILED: "processing failed", EXPORT_RECORD_JSON_PARSING_FAILED: "export record json parsing failed", TWO_FACTOR_ENABLED: "two factor enabled", PASSKEYS_TWO_FACTOR_ENABLED: "passkeys two factor enabled", CLIENT_ERROR: "client error", ServerError: "server error", FILE_NOT_FOUND: "file not found", UNSUPPORTED_PLATFORM: "Unsupported platform", MODEL_DOWNLOAD_PENDING: "Model download pending, skipping clip search request", DOWNLOAD_MANAGER_NOT_READY: "Download manager not initialized", UPDATE_URL_FILE_ID_MISMATCH: "update url file id mismatch", URL_ALREADY_SET: "url already set", FILE_CONVERSION_FAILED: "file conversion failed", }; export function handleUploadError(error: any): Error { const parsedError = parseUploadErrorCodes(error); // breaking errors switch (parsedError.message) { case CustomError.SUBSCRIPTION_EXPIRED: case CustomError.STORAGE_QUOTA_EXCEEDED: case CustomError.SESSION_EXPIRED: case CustomError.UPLOAD_CANCELLED: throw parsedError; } return parsedError; } export function errorWithContext(originalError: Error, context: string) { const errorWithContext = new Error(context); errorWithContext.stack = errorWithContext.stack?.split("\n").slice(2, 4).join("\n") + "\n" + originalError.stack; return errorWithContext; } export function parseUploadErrorCodes(error: any) { let parsedMessage = null; if (error instanceof ApiError) { switch (error.httpStatusCode) { case HttpStatusCode.PaymentRequired: parsedMessage = CustomError.SUBSCRIPTION_EXPIRED; break; case HttpStatusCode.UpgradeRequired: parsedMessage = CustomError.STORAGE_QUOTA_EXCEEDED; break; case HttpStatusCode.Unauthorized: parsedMessage = CustomError.SESSION_EXPIRED; break; case HttpStatusCode.PayloadTooLarge: parsedMessage = CustomError.FILE_TOO_LARGE; break; default: parsedMessage = `${CustomError.UNKNOWN_ERROR} statusCode:${error.httpStatusCode}`; } } else { parsedMessage = error.message; } return new Error(parsedMessage); } export const parseSharingErrorCodes = (error: any) => { let parsedMessage = null; if (error instanceof ApiError) { switch (error.httpStatusCode) { case HttpStatusCode.BadRequest: parsedMessage = CustomError.BAD_REQUEST; break; case HttpStatusCode.PaymentRequired: parsedMessage = CustomError.SUBSCRIPTION_NEEDED; break; case HttpStatusCode.NotFound: parsedMessage = CustomError.NOT_FOUND; break; case HttpStatusCode.Unauthorized: case HttpStatusCode.Gone: parsedMessage = CustomError.TOKEN_EXPIRED; break; case HttpStatusCode.TooManyRequests: parsedMessage = CustomError.TOO_MANY_REQUESTS; break; default: parsedMessage = `${CustomError.UNKNOWN_ERROR} statusCode:${error.httpStatusCode}`; } } else { parsedMessage = error.message; } return new Error(parsedMessage); };