Open RaynoldVanHeyningen opened 1 month ago
@RaynoldVanHeyningen in case you're interested, check out sumosuno.com
They are able to keep the token alive and it's a plug and play option in case you're interested
@FergaliciousPixelicious Thanks, but this is not open-source, it's managed payware, so completely unrelated to this project.
I've built my own solution.
@RaynoldVanHeyningen what is your solution?
@RaynoldVanHeyningen what is your solution?
i noticed there was a form of token refresh going on, the main in problem 'in my situation' was that i was running the SunoAPI in a serverless environment, so the refreshed token wasn't persisted.
Since i'm running in Azure and using .NET for my application, i rebuild this Suno API solution into a custom .NET solution, hosted on Azure, with the main difference being that when a unauthorized request happens in the api, i refresh the session_id and cookie and store these in a redis cache. Afterwards the request will be executed again with the refreshed information.
Here is the relevant code, but as i said this is in .NET:
SunoCookieService.cs:
public class SunoCookieService : BackgroundService
{
private readonly IHttpClientFactory _clientFactory;
private readonly IConfiguration _configuration;
private readonly ILogger<SunoCookieService> _logger;
private readonly IDistributedCache _cache;
public SunoCookie SunoAuth { get; }
private DateTime _tokenExpiryTime;
public SunoCookieService(IHttpClientFactory clientFactory, IConfiguration configuration, ILogger<SunoCookieService> logger, IDistributedCache cache)
{
_clientFactory = clientFactory;
_configuration = configuration;
_logger = logger;
_cache = cache;
SunoAuth = new SunoCookie();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"ExecuteAsync");
await InitializeFromCache();
while (!stoppingToken.IsCancellationRequested)
{
try
{
if (DateTime.UtcNow >= _tokenExpiryTime)
{
await UpdateTokenAndSession();
}
else
{
await TouchSession();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating token or touching session");
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
private async Task InitializeFromCache()
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"InitializeFromCache");
var sessionId = await _cache.GetStringAsync("Suno:SessionId");
var cookie = await _cache.GetStringAsync("Suno:Cookie");
var token = await _cache.GetStringAsync("Suno:Token");
var tokenExpiry = await _cache.GetStringAsync("Suno:TokenExpiry");
if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(cookie))
{
sessionId = _configuration["SunoAPI:SessionId"];
cookie = _configuration["SunoAPI:Cookie"];
if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(cookie))
{
throw new InvalidOperationException("SessionId and Cookie must be provided either in cache or configuration.");
}
await _cache.SetStringAsync("Suno:SessionId", sessionId);
await _cache.SetStringAsync("Suno:Cookie", cookie);
}
else
{
_logger.LogInformation("Initialized from cache");
}
SunoAuth.SetSessionId(sessionId);
SunoAuth.LoadCookie(cookie);
if (!string.IsNullOrEmpty(token))
{
SunoAuth.SetToken(token);
if (DateTime.TryParse(tokenExpiry, out var expiry))
{
_tokenExpiryTime = expiry;
}
_logger.LogInformation($"Initialized with cached token: {token}, expiry time: {_tokenExpiryTime}");
}
}
public async Task UpdateTokenAndSession()
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"UpdateTokenAndSession");
var client = _clientFactory.CreateClient();
client.DefaultRequestHeaders.Add("Cookie", SunoAuth.GetCookie());
var response = await client.PostAsync(
$"https://clerk.suno.com/v1/client/sessions/{SunoAuth.SessionId}/tokens?_clerk_js_version=4.72.0-snapshot.vc141245",
null);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Token update failed with status code {StatusCode}. Attempting to refresh session.", response.StatusCode);
await RefreshSession(client);
return;
}
var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault();
if (!string.IsNullOrEmpty(setCookie))
{
SunoAuth.LoadCookie(setCookie);
await _cache.SetStringAsync("Suno:Cookie", setCookie);
}
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
var token = jsonResponse["jwt"]?.ToString();
var sessionId = ExtractSessionIdFromToken(token);
if (!string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(sessionId))
{
SunoAuth.SetToken(token);
SunoAuth.SetSessionId(sessionId);
await _cache.SetStringAsync("Suno:Token", token);
await _cache.SetStringAsync("Suno:SessionId", sessionId);
// Set token expiry time assuming token expiry time is 30 minutes from now
_tokenExpiryTime = DateTime.UtcNow.AddMinutes(30); // Adjust according to actual token lifetime
await _cache.SetStringAsync("Suno:TokenExpiry", _tokenExpiryTime.ToString());
_logger.LogInformation($"Token updated successfully: {token}, new session ID: {sessionId}, new expiry time: {_tokenExpiryTime}");
}
}
private async Task TouchSession()
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"TouchSession");
var client = _clientFactory.CreateClient();
client.DefaultRequestHeaders.Add("Cookie", SunoAuth.GetCookie());
var response = await client.PostAsync(
$"https://clerk.suno.com/v1/client/sessions/{SunoAuth.SessionId}/touch?_clerk_js_version=4.72.0-snapshot.vc141245",
null);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Touch session failed with status code {StatusCode}.", response.StatusCode);
return;
}
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JObject.Parse(responseContent);
var session = jsonResponse["response"]?["id"]?.ToString();
if (!string.IsNullOrEmpty(session))
{
_logger.LogInformation($"Session touched successfully: {session}");
}
}
private string ExtractSessionIdFromToken(string token)
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"ExtractSessionIdFromToken");
if (string.IsNullOrEmpty(token)) return null;
var parts = token.Split('.');
if (parts.Length != 3) return null;
var payload = parts[1];
var json = Base64UrlDecode(payload);
var jwtPayload = JObject.Parse(json);
return jwtPayload["sid"]?.ToString();
}
private static string Base64UrlDecode(string input)
{
input = input.Replace('-', '+').Replace('_', '/');
switch (input.Length % 4)
{
case 2: input += "=="; break;
case 3: input += "="; break;
}
var bytes = Convert.FromBase64String(input);
return Encoding.UTF8.GetString(bytes);
}
private async Task RefreshSession(HttpClient client)
{
_logger.LogInformation($"------------------------------------------------------------------");
_logger.LogInformation($"RefreshSession");
_logger.LogWarning("Session expired. Attempting to refresh...");
var newSessionId = _configuration["SunoAPI:SessionId"];
var newCookie = _configuration["SunoAPI:Cookie"];
SunoAuth.SetSessionId(newSessionId);
SunoAuth.LoadCookie(newCookie);
await _cache.SetStringAsync("Suno:SessionId", newSessionId);
await _cache.SetStringAsync("Suno:Cookie", newCookie);
_logger.LogInformation("Session refreshed successfully");
await UpdateTokenAndSession(); // Attempt to update the token again with the new session
}
}
I am running the Suno API properly, however i notice every 7 days, i have to update my session id and cookie value.
In the readme it says: "Automatic token maintenance and keep-alive" How can i make sure my token gets properly refreshed, without me having to manually update this information?