Proxy/NetworkStorageProxyClient.cs
using System;
using System.Text.Json;
using System.Threading.Tasks;

namespace Sandbox;

public static partial class NetworkStorage
{
	// ── Proxy Methods (non-host clients route through host) ──

	/// <summary>
	/// Route an endpoint call through the game host via the registered RequestProxy delegate.
	/// </summary>
	private static async Task<JsonElement?> CallEndpointViaProxy( string slug, object input )
	{
		var steamId = Game.SteamId.ToString();
		var securityRequest = await BuildEndpointSecurityRequest( slug, input ?? new { }, allowAuthSession: false );
		string proxySlug = slug;
		string inputJson = JsonSerializer.Serialize( securityRequest.Body );

		// Get the client's auth token as consent proof (included in HMAC signature)
		string clientToken = null;
		try
		{
			clientToken = await GetAuthTokenWithRetry( $"{slug} proxy consent ({steamId})" );
		}
		catch ( Exception ex )
		{
			if ( NetworkStorageLogConfig.LogErrors )
				Log.Warning( $"[NetworkStorage] {slug} PROXY: failed to get client token — {ex.Message}" );
		}

		if ( NetworkStorageLogConfig.LogProxy )
		{
			NetLog.Request( slug, $"PROXY → host (steamId={steamId})" );
			Log.Info( $"[NetworkStorage] {slug} PROXY request via host for steamId={steamId} route={(string.IsNullOrEmpty( proxySlug ) ? "obfuscated" : proxySlug)} input={inputJson ?? "null"}" );
		}

		try
		{
			var result = await RequestProxy( steamId, clientToken ?? "", proxySlug, inputJson );
			if ( result == null )
			{
				if ( NetworkStorageLogConfig.LogErrors )
				{
					Log.Warning( $"[NetworkStorage] {slug} PROXY returned null — host may have rejected the request" );
					NetLog.Error( slug, "Proxy returned null" );
				}
				RecordEndpointError( slug, "PROXY_FAILED", "Proxy returned null" );
				return null;
			}

			if ( NetworkStorageLogConfig.LogProxy )
				Log.Info( $"[NetworkStorage] {slug} PROXY → {TruncateJson( result, 200 )}" );
			var parsed = ParseResponse( slug, result );
			if ( parsed.HasValue && NetworkStorageLogConfig.LogProxy )
				NetLog.Response( slug, $"PROXY OK — {TruncateJson( parsed.Value )}" );
			return parsed;
		}
		catch ( Exception ex )
		{
			if ( NetworkStorageLogConfig.LogErrors )
			{
				Log.Warning( $"[NetworkStorage] {slug} PROXY FAILED — {ex.Message}" );
				NetLog.Error( slug, $"Proxy error: {ex.Message}" );
			}
			RecordEndpointError( slug, "PROXY_FAILED", ex.Message );
			return null;
		}
	}

	/// <summary>
	/// Route a document read through the game host via the registered DocumentProxy delegate.
	/// </summary>
	private static async Task<JsonElement?> GetDocumentViaProxy( string collectionId, string documentId )
	{
		var steamId = Game.SteamId.ToString();
		var docId = documentId ?? steamId;

		// Get the client's auth token as consent proof (included in HMAC signature)
		string clientToken = null;
		try
		{
			clientToken = await GetAuthTokenWithRetry( $"storage proxy consent ({collectionId}/{docId})" );
		}
		catch ( Exception ex )
		{
			if ( NetworkStorageLogConfig.LogErrors )
				Log.Warning( $"[NetworkStorage] storage PROXY: failed to get client token — {ex.Message}" );
		}

		if ( NetworkStorageLogConfig.LogProxy )
		{
			NetLog.Request( "storage", $"PROXY → host GET {collectionId}/{docId}" );
			Log.Info( $"[NetworkStorage] storage PROXY request via host for {collectionId}/{docId}" );
		}

		try
		{
			var result = await DocumentProxy( steamId, clientToken ?? "", collectionId, docId );
			if ( result == null )
			{
				if ( NetworkStorageLogConfig.LogErrors )
				{
					Log.Warning( $"[NetworkStorage] storage PROXY returned null for {collectionId}/{docId}" );
					NetLog.Error( "storage", "Proxy returned null" );
				}
				RecordEndpointError( "storage", "PROXY_FAILED", "Proxy returned null" );
				return null;
			}

			if ( NetworkStorageLogConfig.LogProxy )
				Log.Info( $"[NetworkStorage] storage PROXY → {TruncateJson( result, 200 )}" );
			var parsed = ParseResponse( "storage", result );
			if ( parsed.HasValue && NetworkStorageLogConfig.LogProxy )
				NetLog.Response( "storage", $"PROXY OK ({result.Length} bytes)" );
			return parsed;
		}
		catch ( Exception ex )
		{
			if ( NetworkStorageLogConfig.LogErrors )
			{
				Log.Warning( $"[NetworkStorage] storage PROXY FAILED — {ex.Message}" );
				NetLog.Error( "storage", $"Proxy error: {ex.Message}" );
			}
			RecordEndpointError( "storage", "PROXY_FAILED", ex.Message );
			return null;
		}
	}

}