Helper methods for NetworkStorage error handling. Parses JSON error payloads from the server, extracts codes, messages and security info, invalidates cached auth tokens on auth failures, logs or records endpoint errors, and handles special-casing for storage-not-found conditions.
using System;
using System.Text.Json;
namespace Sandbox;
public static partial class NetworkStorage
{
private static void LogServerError( string slug, JsonElement json )
{
var code = "UNKNOWN";
var message = "";
var authFailureKind = "";
var statusCode = 0;
if ( json.TryGetProperty( "error", out var err ) )
{
if ( err.ValueKind == JsonValueKind.Object )
{
code = err.TryGetProperty( "code", out var c ) ? c.GetString() ?? "UNKNOWN" : "UNKNOWN";
message = err.TryGetProperty( "message", out var m ) ? m.GetString() ?? "" : "";
authFailureKind = err.TryGetProperty( "authFailureKind", out var authKind )
? authKind.GetString() ?? ""
: "";
}
else if ( err.ValueKind == JsonValueKind.String )
{
code = err.GetString() ?? "UNKNOWN";
}
}
// Top-level message (server copies error.message here for convenience)
if ( string.IsNullOrEmpty( message ) && json.TryGetProperty( "message", out var topMsg ) )
message = topMsg.GetString() ?? "";
if ( json.TryGetProperty( "status", out var statusProp ) && statusProp.ValueKind == JsonValueKind.Number )
statusProp.TryGetInt32( out statusCode );
if ( string.Equals( code, "SBOX_AUTH_FAILED", StringComparison.OrdinalIgnoreCase )
|| string.Equals( authFailureKind, "token_rejected", StringComparison.OrdinalIgnoreCase ) )
{
InvalidateCachedAuthToken( $"{slug} {code} {authFailureKind}".Trim() );
}
if ( IsSecurityConfigMismatchCode( code ) )
{
JsonElement expected = default;
string expectedConfigVersion = null;
if ( err.ValueKind == JsonValueKind.Object )
{
if ( err.TryGetProperty( "security", out var secObj ) )
{
if ( secObj.TryGetProperty( "configVersion", out var cv ) )
expectedConfigVersion = cv.GetString();
secObj.TryGetProperty( "expected", out expected );
}
if ( expected.ValueKind != JsonValueKind.Object )
err.TryGetProperty( "expected", out expected );
}
if ( expected.ValueKind != JsonValueKind.Object && json.TryGetProperty( "security", out var topSec ) )
{
if ( expectedConfigVersion == null && topSec.TryGetProperty( "configVersion", out var cv ) )
expectedConfigVersion = cv.GetString();
topSec.TryGetProperty( "expected", out expected );
}
ApplyServerExpectedSecurityConfig( code, expected, expectedConfigVersion );
}
if ( NetworkStorageLogConfig.LogErrors )
{
var logMessage = $"{code}: {message}";
if ( IsExpectedStorageNotFound( slug, code, statusCode, message ) )
{
Log.Info( $"[NetworkStorage] {slug}: {logMessage}" );
NetLog.Info( slug, logMessage );
}
else
{
Log.Warning( $"[NetworkStorage] {slug}: {code} — {message}" );
NetLog.Error( slug, logMessage );
}
}
RecordEndpointError( slug, code, message );
}
private static bool IsExpectedStorageNotFound( string slug, string code, int statusCode, string message )
{
if ( !string.Equals( slug, "storage", StringComparison.OrdinalIgnoreCase ) )
return false;
if ( statusCode == 404 )
return true;
if ( string.Equals( code, "NOT_FOUND", StringComparison.OrdinalIgnoreCase )
|| string.Equals( code, "PROFILE_MISSING", StringComparison.OrdinalIgnoreCase ) )
return true;
return !string.IsNullOrWhiteSpace( message )
&& message.Contains( "404", StringComparison.OrdinalIgnoreCase )
&& message.Contains( "Not Found", StringComparison.OrdinalIgnoreCase );
}
private static bool IsHttpNotFoundException( Exception ex )
{
if ( ex is System.Net.Http.HttpRequestException httpEx && httpEx.StatusCode.HasValue && (int)httpEx.StatusCode.Value == 404 )
return true;
var message = ex?.Message ?? "";
return message.Contains( "404", StringComparison.OrdinalIgnoreCase )
&& message.Contains( "Not Found", StringComparison.OrdinalIgnoreCase );
}
private static void RecordStorageNotFound( string details )
{
var message = string.IsNullOrWhiteSpace( details ) ? "404 Not Found" : $"404 Not Found: {details}";
if ( NetworkStorageLogConfig.LogErrors )
{
Log.Info( $"[NetworkStorage] storage: {message}" );
NetLog.Info( "storage", message );
}
RecordEndpointError( "storage", "NOT_FOUND", message );
}
}