The Supabase UE5 Plugin implements comprehensive, production-ready error handling across all operations. This system ensures robust operation in production environments with graceful failure handling, detailed error reporting, and automatic recovery mechanisms.
USupabaseSubsystem (Top Level)
├── Connection Health Monitoring
├── Token Refresh Error Recovery
└── Global Error Broadcasting
USupabaseManager (Static Interface)
├── Subsystem Validation
├── Input Sanitization
└── Operation Routing
Async Operations (Individual Nodes)
├── Input Validation
├── HTTP Error Handling
├── JSON Parsing Errors
└── Memory Management
The USupabaseSubsystem provides centralized error handling and recovery:
// Automatic health monitoring
void USupabaseSubsystem::CheckConnectionHealth()
{
if (!IsConnectionHealthy())
{
OnConnectionLost.Broadcast();
AttemptReconnection();
}
}
// Token refresh error recovery
void USupabaseSubsystem::HandleTokenRefreshError(const FString& Error)
{
UE_LOG(LogSupabase, Warning, TEXT("Token refresh failed: %s"), *Error);
OnTokenRefreshFailed.Broadcast(Error);
// Attempt re-authentication if token is completely invalid
if (Error.Contains(TEXT("invalid_token")))
{
ClearAuthSession();
OnAuthenticationRequired.Broadcast();
}
}
All async operations implement comprehensive input validation:
// Example from AsyncExecuteSQL
bool UAsyncExecuteSQL::ValidateInputs()
{
if (SQLQuery.IsEmpty())
{
BroadcastError(TEXT("SQL query cannot be empty"));
return false;
}
// SQL injection protection
if (ContainsDangerousSQL(SQLQuery))
{
BroadcastError(TEXT("SQL query contains prohibited operations"));
return false;
}
// Length validation (DoS protection)
if (SQLQuery.Len() > MAX_SQL_QUERY_LENGTH)
{
BroadcastError(TEXT("SQL query exceeds maximum length"));
return false;
}
return true;
}
// Security validation
bool UAsyncExecuteSQL::ContainsDangerousSQL(const FString& SQL)
{
static const TArray<FString> ProhibitedOperations = {
TEXT("DROP"), TEXT("TRUNCATE"), TEXT("ALTER"), TEXT("CREATE"),
TEXT("DELETE"), TEXT("GRANT"), TEXT("REVOKE")
};
FString UpperSQL = SQL.ToUpper();
for (const FString& Operation : ProhibitedOperations)
{
if (UpperSQL.Contains(Operation))
{
return true;
}
}
return false;
}
The plugin categorizes HTTP errors for appropriate handling:
void UAsyncApiRequest::OnRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (!bWasSuccessful || !Response.IsValid())
{
HandleNetworkError(Request, Response);
return;
}
int32 StatusCode = Response->GetResponseCode();
// Classify error types
if (StatusCode >= 400 && StatusCode < 500)
{
HandleClientError(StatusCode, Response->GetContentAsString());
}
else if (StatusCode >= 500)
{
HandleServerError(StatusCode, Response->GetContentAsString());
}
else if (StatusCode == 200 || StatusCode == 201)
{
HandleSuccess(Response->GetContentAsString());
}
}
void UAsyncApiRequest::HandleClientError(int32 StatusCode, const FString& Content)
{
FString ErrorMessage;
switch (StatusCode)
{
case 400:
ErrorMessage = ExtractErrorFromResponse(Content, TEXT("Bad Request"));
break;
case 401:
ErrorMessage = TEXT("Authentication required");
// Trigger re-authentication
TriggerAuthenticationRequired();
break;
case 403:
ErrorMessage = TEXT("Insufficient permissions");
break;
case 404:
ErrorMessage = TEXT("Resource not found");
break;
case 409:
ErrorMessage = TEXT("Conflict - resource already exists");
break;
case 422:
ErrorMessage = ExtractValidationErrors(Content);
break;
case 429:
ErrorMessage = TEXT("Rate limit exceeded");
// Implement exponential backoff
ScheduleRetryWithBackoff();
return;
default:
ErrorMessage = FString::Printf(TEXT("Client error %d: %s"), StatusCode, *Content);
}
BroadcastError(ErrorMessage, StatusCode);
}
Connection Failures:
Handling Strategy:
void HandleNetworkError(FHttpRequestPtr Request, FHttpResponsePtr Response)
{
FString ErrorMsg = TEXT("Network error: ");
if (!Response.IsValid())
{
ErrorMsg += TEXT("No response received - check internet connection");
}
else if (Response->GetResponseCode() == 0)
{
ErrorMsg += TEXT("Connection timeout or DNS failure");
}
// Implement retry logic for network errors
if (RetryCount < MAX_RETRY_ATTEMPTS)
{
ScheduleRetry();
}
else
{
BroadcastError(ErrorMsg);
}
}
Token Issues:
Handling Strategy:
void HandleAuthenticationError(const FString& Error)
{
UE_LOG(LogSupabase, Warning, TEXT("Authentication error: %s"), *Error);
// Clear invalid session
ClearAuthSession();
// Notify UI for re-authentication
OnAuthenticationRequired.Broadcast();
// Attempt automatic token refresh if possible
if (HasValidRefreshToken())
{
AttemptTokenRefresh();
}
}
Input Validation:
Example Implementation:
bool ValidateUserRegistration(const FString& Email, const FString& Password)
{
// Email validation
if (!IsValidEmailFormat(Email))
{
BroadcastError(TEXT("Invalid email format"));
return false;
}
// Password strength validation
if (Password.Len() < MIN_PASSWORD_LENGTH)
{
BroadcastError(FString::Printf(TEXT("Password must be at least %d characters"), MIN_PASSWORD_LENGTH));
return false;
}
// Security checks
if (ContainsProhibitedCharacters(Password))
{
BroadcastError(TEXT("Password contains prohibited characters"));
return false;
}
return true;
}
JSON Parsing:
Handling Strategy:
bool TryParseJsonResponse(const FString& ResponseContent, FJsonObjectWrapper& OutJsonWrapper)
{
if (ResponseContent.IsEmpty())
{
UE_LOG(LogSupabase, Error, TEXT("Empty JSON response"));
return false;
}
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseContent);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
UE_LOG(LogSupabase, Error, TEXT("Failed to parse JSON: %s"), *ResponseContent.Left(200));
// Try alternative parsing strategies
return TryParseAsArray(ResponseContent, OutJsonWrapper);
}
OutJsonWrapper.JsonObject = JsonObject;
return true;
}
class FRetryManager
{
private:
int32 RetryCount = 0;
float BaseDelay = 1.0f;
static constexpr int32 MAX_RETRIES = 3;
public:
bool ShouldRetry(int32 StatusCode)
{
if (RetryCount >= MAX_RETRIES) return false;
// Retry on server errors and rate limits
return StatusCode == 429 || (StatusCode >= 500 && StatusCode < 600);
}
float GetRetryDelay()
{
// Exponential backoff with jitter
float Delay = BaseDelay * FMath::Pow(2.0f, RetryCount);
float Jitter = FMath::RandRange(0.0f, 0.5f);
return Delay + Jitter;
}
void IncrementRetry() { RetryCount++; }
void Reset() { RetryCount = 0; }
};
void USupabaseSubsystem::AttemptReconnection()
{
if (bIsReconnecting) return;
bIsReconnecting = true;
// Stop current operations
CancelActiveOperations();
// Reset connection state
ResetConnection();
// Attempt to restore connection
GetWorld()->GetTimerManager().SetTimer(
ReconnectionTimerHandle,
this,
&USupabaseSubsystem::PerformReconnection,
GetReconnectionDelay(),
false
);
}
void USupabaseSubsystem::PerformReconnection()
{
if (TestConnection())
{
bIsReconnecting = false;
OnConnectionRestored.Broadcast();
// Resume pending operations
ResumePendingOperations();
}
else
{
// Retry with exponential backoff
ReconnectionAttempts++;
if (ReconnectionAttempts < MAX_RECONNECTION_ATTEMPTS)
{
AttemptReconnection();
}
else
{
OnConnectionFailed.Broadcast(TEXT("Failed to reconnect after maximum attempts"));
}
}
}
DECLARE_LOG_CATEGORY_EXTERN(LogSupabase, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSupabaseAuth, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSupabaseRealtime, Log, All);
// Usage examples
UE_LOG(LogSupabase, Error, TEXT("Connection failed: %s"), *ErrorMessage);
UE_LOG(LogSupabaseAuth, Warning, TEXT("Token refresh required for user %s"), *UserId);
UE_LOG(LogSupabaseRealtime, Verbose, TEXT("WebSocket message received: %s"), *Message);
// Global error events
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSupabaseError, FString, ErrorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSupabaseHttpError, FString, ErrorMessage, int32, StatusCode);
// Subsystem-level events
UPROPERTY(BlueprintAssignable)
FOnSupabaseError OnConnectionLost;
UPROPERTY(BlueprintAssignable)
FOnSupabaseError OnConnectionRestored;
UPROPERTY(BlueprintAssignable)
FOnSupabaseHttpError OnApiError;
void USupabaseSubsystem::LogDebugInfo()
{
UE_LOG(LogSupabase, Log, TEXT("=== Supabase Debug Info ==="));
UE_LOG(LogSupabase, Log, TEXT("Connection State: %s"), *GetConnectionStateString());
UE_LOG(LogSupabase, Log, TEXT("Auth State: %s"), *GetAuthStateString());
UE_LOG(LogSupabase, Log, TEXT("Active Operations: %d"), ActiveOperations.Num());
UE_LOG(LogSupabase, Log, TEXT("Last Health Check: %s"), *LastHealthCheck.ToString());
UE_LOG(LogSupabase, Log, TEXT("Token Expires: %s"), *TokenExpirationTime.ToString());
UE_LOG(LogSupabase, Log, TEXT("==========================="));
}
// Blueprint-accessible error information
USTRUCT(BlueprintType)
struct FSupabaseError
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
FString Message;
UPROPERTY(BlueprintReadOnly)
int32 StatusCode;
UPROPERTY(BlueprintReadOnly)
FString Category; // "Network", "Auth", "Validation", etc.
UPROPERTY(BlueprintReadOnly)
bool bIsRetryable;
};
UCLASS(BlueprintType)
class SUPABASE_API USupabaseErrorUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "Supabase|Error Handling")
static bool IsNetworkError(const FSupabaseError& Error);
UFUNCTION(BlueprintPure, Category = "Supabase|Error Handling")
static bool IsAuthenticationError(const FSupabaseError& Error);
UFUNCTION(BlueprintPure, Category = "Supabase|Error Handling")
static FString GetUserFriendlyErrorMessage(const FSupabaseError& Error);
UFUNCTION(BlueprintCallable, Category = "Supabase|Error Handling")
static void LogError(const FSupabaseError& Error);
};
Problem: User loses internet connection during operation
Solution:
// Detect network loss
void HandleNetworkLoss()
{
// Pause all operations
PauseActiveOperations();
// Cache operations for retry
CacheFailedOperations();
// Monitor for connection restoration
StartNetworkMonitoring();
// Notify user
ShowOfflineMode();
}
// Resume when connection restored
void HandleNetworkRestoration()
{
// Resume cached operations
ResumeCachedOperations();
// Update UI
ShowOnlineMode();
}
Problem: User's session expires during active use
Solution:
// Handle token expiry
void HandleTokenExpiry()
{
// Cache current operation
CacheCurrentOperation();
// Attempt silent refresh
if (HasValidRefreshToken())
{
AttemptSilentRefresh();
}
else
{
// Require user re-authentication
ShowLoginPrompt();
}
}
// Resume after re-authentication
void HandleAuthenticationSuccess()
{
// Resume cached operation
ResumeCachedOperation();
}
Problem: API rate limits exceeded
Solution:
// Handle rate limiting
void HandleRateLimit(int32 RetryAfterSeconds)
{
// Show user-friendly message
ShowRateLimitMessage(RetryAfterSeconds);
// Schedule retry
ScheduleRetryAfterDelay(RetryAfterSeconds);
// Reduce operation frequency
ImplementBackoffStrategy();
}
USTRUCT(BlueprintType)
struct FSupabaseErrorSettings
{
GENERATED_BODY()
// Retry configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MaxRetryAttempts = 3;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float BaseRetryDelay = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MaxRetryDelay = 30.0f;
// Timeout configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float ConnectionTimeout = 30.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float RequestTimeout = 60.0f;
// Logging configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bEnableVerboseLogging = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bLogSensitiveData = false;
};
This documentation covers the comprehensive error handling system implemented in the Supabase UE5 Plugin. For specific implementation details, refer to the individual class documentation and code examples.
Added in v1.6.0. See EOS Integration Overview for setup.
EOS operations follow the same error handling patterns, with additional EOS-specific delegates:
| Error Pattern | Delegate/Method | Description |
|---|---|---|
FEOSAuthResult.bWasSuccessful |
OnAuthComplete |
Check after EOS authentication |
OnEOSError |
USupabaseEOSPresence |
Presence and social operation failures |
FSupabaseErrorDelegate |
Leaderboards/Achievements | Static API failure callbacks |
// EOS Auth error handling
void OnEOSAuthComplete(const FEOSAuthResult& AuthResult)
{
if (!AuthResult.bWasSuccessful)
{
UE_LOG(LogSupabase, Error, TEXT("EOS Auth Error: %s"), *AuthResult.ErrorMessage);
return;
}
}
// EOS Presence error delegate
void AMyClass::OnEOSError(const FString& Operation, const FString& ErrorMessage)
{
UE_LOG(LogSupabase, Error, TEXT("EOS Error in '%s': %s"), *Operation, *ErrorMessage);
}
// EOS Leaderboard/Achievement error delegate
FSupabaseErrorDelegate OnFailure;
OnFailure.BindLambda([](const FString& Error)
{
UE_LOG(LogSupabase, Error, TEXT("EOS Error: %s"), *Error);
});
| Error | Cause | Solution |
|---|---|---|
EOS subsystem not available |
OnlineSubsystemEOS not enabled | Enable in .uproject and DefaultEngine.ini |
Platform not initialized |
Initialize() not called |
Call Initialize() before other EOS operations |
Not authenticated |
EOS Connect auth not completed | Call Authenticate() and wait for callback |
Data exceeds max size |
WritePlayerData >256 KB | Split data or compress before writing |