Code/Core/NetworkStorageHttp.cs

Static partial class handling construction of URLs and HTTP headers for NetworkStorage API requests. It ensures configuration, builds request URLs with apiKey and optional flags, constructs public and authenticated headers including Steam ID and tokens, and adds runtime metadata headers.

Http CallsCredential Access
🌐 storage.sbox.cool, storage.sboxcool.com
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Sandbox;

public static partial class NetworkStorage
{
	public static void EnsureConfigured()
	{
		if ( !IsConfigured )
			AutoConfigure();

		if ( !IsConfigured )
			throw new InvalidOperationException( "NetworkStorage not configured. Add credentials via Editor → Network Storage → Setup, or call NetworkStorage.Configure() manually." );

		NetworkStorageAnalyticsRuntime.EnsureCreated( "ensure-configured" );
	}

	private static bool IsCdnRoot( string root )
		=> root.Contains( "storage.sbox.cool" ) || root.Contains( "storage.sboxcool.com" );

	/// <summary>
	/// Build the request URL with API key query param (no auth tokens in URL).
	/// </summary>
	private static string BuildUrl( string path, bool includeDedicatedSecretFlag = false )
	{
		var baseUrl = $"{ApiRoot}{path}?apiKey={Uri.EscapeDataString( ApiKey )}";
		if ( includeDedicatedSecretFlag )
			baseUrl += "&secret-key=1";
		if ( string.Equals( PublishTarget, "next", StringComparison.OrdinalIgnoreCase ) )
			baseUrl += "&revisionTarget=next&includeStaged=true";
		if ( IsCdnRoot( ApiRoot ) )
		{
			var v = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			return $"{baseUrl}&v={v}";
		}
		return baseUrl;
	}

	/// <summary>
	/// Build auth headers containing the Steam ID and auth token.
	/// Sending auth via headers avoids URL-encoding issues that corrupt
	/// base64 tokens in query strings (+ decoded as space, etc.).
	/// </summary>
	private static Dictionary<string, string> BuildPublicHeaders( string clientMode = null )
	{
		var headers = new Dictionary<string, string>
		{
			{ "x-public-key", ApiKey ?? "" }
		};
		if ( !string.IsNullOrWhiteSpace( clientMode ) )
			headers["x-ns-security-mode"] = clientMode;
		if ( !string.IsNullOrWhiteSpace( RuntimeSecurityConfigVersion ) )
			headers["x-ns-security-config-version"] = RuntimeSecurityConfigVersion;

		AddRuntimeTargetHeaders( headers );
		return headers;
	}

	private static async Task<Dictionary<string, string>> BuildAuthHeaders( string authSessionToken = null, string clientMode = null )
	{
		if ( IsDedicatedServerProcess )
		{
			LogDedicatedPlayerAuthSuppressedOnce();
			return BuildPublicHeaders( clientMode );
		}

		var steamId = Game.SteamId.ToString();
		var token = TryTakePreparedAuthToken() ?? await GetAuthTokenWithRetry( $"steamId={steamId}" );

		if ( string.IsNullOrEmpty( token ) )
		{
			if ( NetworkStorageLogConfig.LogTokens )
				Log.Warning( $"[NetworkStorage] Auth token is empty for steamId={steamId} — requests may fail" );
		}
		else if ( NetworkStorageLogConfig.LogTokens )
		{
			var preview = token.Length > 8 ? $"{token[..4]}...{token[^4..]}" : "****";
			Log.Info( $"[NetworkStorage] Auth token acquired for steamId={steamId} ({token.Length} chars, preview={preview})" );
		}

		var headers = new Dictionary<string, string>
		{
			{ "x-public-key", ApiKey ?? "" },
			{ "x-steam-id", steamId },
			{ "x-sbox-token", token ?? "" }
		};
		if ( !string.IsNullOrWhiteSpace( authSessionToken ) )
			headers["x-auth-session"] = authSessionToken;
		if ( !string.IsNullOrWhiteSpace( clientMode ) )
			headers["x-ns-security-mode"] = clientMode;
		if ( !string.IsNullOrWhiteSpace( RuntimeSecurityConfigVersion ) )
			headers["x-ns-security-config-version"] = RuntimeSecurityConfigVersion;

		AddRuntimeTargetHeaders( headers );

		return headers;
	}

	private static void AddRuntimeTargetHeaders( Dictionary<string, string> headers )
	{
		headers["x-ns-library-version"] = LibraryVersion;

		var revisionId = NetworkStoragePackageInfo.RuntimeRevisionId;
		if ( revisionId.HasValue )
			headers["x-ns-revision-id"] = revisionId.Value.ToString();

		if ( string.Equals( PublishTarget, "next", StringComparison.OrdinalIgnoreCase ) )
			headers["x-ns-publish-target"] = "next";

		var clientType = GetClientType();
		if ( !string.IsNullOrEmpty( clientType ) )
			headers["x-ns-client-type"] = clientType;
	}

}