Skip to content

Commit 84f06a0

Browse files
committed
STAR: JWT refresh (client-only), refresh token storage, quest/inventory fixes
- Client: in-memory JWT auto-renew after manual beam-in; 401 retry with refresh, clear token on refresh fail - Client: SetRefreshToken/GetCurrentRefreshToken + native exports for games to persist refresh_token - Client: throttle 'no cached quest' log spam; inventory logging for diagnostics - ODOOM: persist refresh_token in oasisstar.json; pass to client on restore; always call star_api_refresh_quest_cache_in_background (remove ODOOM_STAR_API_HAS_REFRESH_QUEST_BACKGROUND guards) so tracker and quest list load - No flow/callback changes to games; master remains stable Made-with: Cursor
1 parent 450b6c6 commit 84f06a0

2 files changed

Lines changed: 47 additions & 16 deletions

File tree

OASIS Omniverse/ODOOM/uzdoom_star_integration.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,9 +1371,7 @@ static void ODOOM_OnAuthDone(void* user_data) {
13711371
}
13721372
}
13731373
/* Start loading quest list so tracker title/objective show without opening popup (ODOOM_RefreshQuestCVars will use cache when ready). */
1374-
#ifdef ODOOM_STAR_API_HAS_REFRESH_QUEST_BACKGROUND
13751374
star_api_refresh_quest_cache_in_background();
1376-
#endif
13771375
ODOOM_RefreshQuestCVars(); /* push once immediately in case cache already has data */
13781376
}
13791377
/* Persist session to oasisstar.json immediately so we stay logged in after restart (or if game crashes before exit). */
@@ -1921,11 +1919,7 @@ void ODOOM_InventoryInputCaptureFrame(void)
19211919
static int s_quest_popup_was_open = 0;
19221920
static int s_quest_refresh_frames = 0;
19231921
if (questPopupOpen && !s_quest_popup_was_open) {
1924-
#ifdef ODOOM_STAR_API_HAS_REFRESH_QUEST_BACKGROUND
19251922
star_api_refresh_quest_cache_in_background();
1926-
#else
1927-
/* Without new API: do not invalidate – show existing cache and let 60-frame refresh update when fetch completes. */
1928-
#endif
19291923
ODOOM_RefreshQuestCVars(); /* push current cache to CVars so list shows immediately */
19301924
s_quest_refresh_frames = 0; /* do not run 60-frame refresh this frame */
19311925
}
@@ -1977,9 +1971,8 @@ void ODOOM_InventoryInputCaptureFrame(void)
19771971
oidVar->SetGenericRep(u, CVAR_String);
19781972
}
19791973
}
1980-
#ifdef ODOOM_STAR_API_HAS_REFRESH_QUEST_BACKGROUND
1974+
star_api_invalidate_quest_cache();
19811975
star_api_refresh_quest_cache_in_background();
1982-
#endif
19831976
ODOOM_RefreshQuestCVars();
19841977
}
19851978
} else {

OASIS Omniverse/STARAPIClient/StarApiClient.cs

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,6 @@ public async Task<OASISResult<bool>> RestoreSessionAsync(CancellationToken cance
472472
/* Invoke the same "profile loaded" operation callback that refresh_avatar_profile uses, so the game runs beamed-in logic: tracker, XP, quest cache, etc. */
473473
StarApiExports.InvokeOperationCallback(StarApiResultCode.Success, StarApiExports.StarApiOpProfileLoaded);
474474
StarApiExports.StarApiLogFileOnly("[Auth] RestoreSession: success, profile loaded (operation callback invoked)");
475-
/* Start proactive JWT refresh so token is renewed before expiry (same as after manual beam-in). */
476-
ScheduleBackgroundTokenRefresh();
477475
return Success(true, StarApiResultCode.Success, "Session restored.");
478476
}
479477

@@ -724,19 +722,26 @@ public Task<OASISResult<bool>> QueueHasItemAsync(string itemName, CancellationTo
724722
public async Task<OASISResult<List<StarItem>>> GetInventoryAsync(CancellationToken cancellationToken = default)
725723
{
726724
if (!IsInitialized())
725+
{
726+
StarApiExports.StarApiLogFileOnly("[Inventory] GetInventoryAsync: not initialized");
727727
return FailAndCallback<List<StarItem>>("Client is not initialized.", StarApiResultCode.NotInitialized);
728+
}
728729

729730
Task<OASISResult<List<StarItem>>>? task;
730731
lock (_inventoryCacheLock)
731732
{
732733
if (_cachedInventory is not null)
733734
{
734735
var merged = MergeLocalPendingIntoInventory(_cachedInventory);
736+
StarApiExports.StarApiLogFileOnly($"[Inventory] GetInventoryAsync: returning cache count={merged.Count}");
735737
InvokeCallback(StarApiResultCode.Success);
736738
return Success(merged, StarApiResultCode.Success, $"Loaded {merged.Count} item(s) (cached + pending).");
737739
}
738740
if (_inventoryFetchTask is null)
741+
{
742+
StarApiExports.StarApiLogFileOnly("[Inventory] GetInventoryAsync: cache miss, starting FetchInventoryOnceAsync");
739743
_inventoryFetchTask = FetchInventoryOnceAsync();
744+
}
740745
task = _inventoryFetchTask;
741746
}
742747

@@ -759,8 +764,10 @@ public async Task<OASISResult<List<StarItem>>> GetInventoryAsync(CancellationTok
759764
if (result.Result is not null)
760765
{
761766
var merged = MergeLocalPendingIntoInventory(result.Result);
767+
StarApiExports.StarApiLogFileOnly($"[Inventory] GetInventoryAsync: fetch completed count={merged.Count}");
762768
return Success(merged, StarApiResultCode.Success, result.Message ?? $"Loaded {merged.Count} item(s).");
763769
}
770+
StarApiExports.StarApiLogFileOnly($"[Inventory] GetInventoryAsync: fetch failed IsError={result.IsError} Message={result.Message ?? "(null)"}");
764771
return result;
765772
}
766773

@@ -817,9 +824,11 @@ private List<StarItem> MergeLocalPendingIntoInventory(List<StarItem> apiList)
817824

818825
private async Task<OASISResult<List<StarItem>>> FetchInventoryOnceAsync()
819826
{
827+
StarApiExports.StarApiLogFileOnly("[Inventory] FetchInventoryOnceAsync: ensuring avatar id");
820828
var avatarIdResult = await EnsureAvatarIdAsync(CancellationToken.None).ConfigureAwait(false);
821829
if (avatarIdResult.IsError || string.IsNullOrWhiteSpace(avatarIdResult.Result))
822830
{
831+
StarApiExports.StarApiLogFileOnly($"[Inventory] FetchInventoryOnceAsync: no avatar id Message={avatarIdResult.Message ?? "(null)"}");
823832
return new OASISResult<List<StarItem>>
824833
{
825834
IsError = true,
@@ -829,27 +838,38 @@ private async Task<OASISResult<List<StarItem>>> FetchInventoryOnceAsync()
829838
};
830839
}
831840

841+
var url = $"{_baseApiUrl}/api/avatar/inventory";
842+
StarApiExports.StarApiLogFileOnly($"[Inventory] FetchInventoryOnceAsync: GET {url}");
832843
try
833844
{
834-
var response = await SendRawAsync(HttpMethod.Get, $"{_baseApiUrl}/api/avatar/inventory", null, CancellationToken.None).ConfigureAwait(false);
845+
var response = await SendRawAsync(HttpMethod.Get, url, null, CancellationToken.None).ConfigureAwait(false);
835846
if (response.IsError && ParseCode(response.ErrorCode, StarApiResultCode.ApiError) == StarApiResultCode.Network)
836847
{
848+
StarApiExports.StarApiLogFileOnly("[Inventory] FetchInventoryOnceAsync: first attempt failed (network), retrying once");
837849
await Task.Delay(200, CancellationToken.None).ConfigureAwait(false);
838-
response = await SendRawAsync(HttpMethod.Get, $"{_baseApiUrl}/api/avatar/inventory", null, CancellationToken.None).ConfigureAwait(false);
850+
response = await SendRawAsync(HttpMethod.Get, url, null, CancellationToken.None).ConfigureAwait(false);
839851
}
840852
if (response.IsError)
853+
{
854+
StarApiExports.StarApiLogFileOnly($"[Inventory] FetchInventoryOnceAsync: GET failed Message={response.Message ?? "(null)"}");
841855
return FailAndCallback<List<StarItem>>(response.Message, ParseCode(response.ErrorCode, StarApiResultCode.ApiError), response.Exception);
856+
}
842857

843858
var parseResult = ParseEnvelopeOrPayload(response.Result, out var resultElement, out var parseErrorCode, out var parseErrorMessage);
844859
if (!parseResult)
860+
{
861+
StarApiExports.StarApiLogFileOnly($"[Inventory] FetchInventoryOnceAsync: parse failed {parseErrorMessage ?? "(null)"}");
845862
return FailAndCallback<List<StarItem>>(parseErrorMessage, parseErrorCode);
863+
}
846864

847865
var mapped = ParseInventoryItems(resultElement);
866+
StarApiExports.StarApiLogFileOnly($"[Inventory] FetchInventoryOnceAsync: GET OK parsed count={mapped?.Count ?? 0}");
848867
InvokeCallback(StarApiResultCode.Success);
849868
return Success(mapped, StarApiResultCode.Success, $"Loaded {mapped.Count} item(s).");
850869
}
851870
catch (Exception ex)
852871
{
872+
StarApiExports.StarApiLogFileOnly($"[Inventory] FetchInventoryOnceAsync: exception {ex.Message}");
853873
return FailAndCallback<List<StarItem>>($"Failed to load inventory: {ex.Message}", StarApiResultCode.Network, ex);
854874
}
855875
}
@@ -859,6 +879,7 @@ public void InvalidateInventoryCache()
859879
{
860880
lock (_inventoryCacheLock)
861881
{
882+
StarApiExports.StarApiLogFileOnly("[Inventory] InvalidateInventoryCache: clearing cache");
862883
_cachedInventory = null;
863884
_inventoryFetchTask = null;
864885
}
@@ -5249,6 +5270,7 @@ public static unsafe class StarApiExports
52495270
private static volatile int _starDebug;
52505271
private static int StarApiGetQuestsStringLastLoggedToCopy = -1;
52515272
private static bool _topLevelQuestsLastLoggedLoading;
5273+
private static long _lastNoCachedQuestLogTicks;
52525274

52535275
/// <summary>Whether STAR debug logging is on (games set via star_api_set_debug). When true, quest API and other requests log URI and response to file and console.</summary>
52545276
internal static bool GetStarDebug() => _starDebug != 0;
@@ -5488,18 +5510,24 @@ public static int StarApiGetInventory(star_item_list_t** itemList)
54885510

54895511
var client = GetClient();
54905512
if (client is null)
5513+
{
5514+
try { StarApiExports.StarApiLogFileOnly("[Inventory] star_api_get_inventory: no client"); } catch { }
54915515
return (int)SetErrorAndReturn("Client is not initialized.", StarApiResultCode.NotInitialized, StarApiOpGetInventory);
5516+
}
54925517

5518+
try { StarApiExports.StarApiLogFileOnly("[Inventory] star_api_get_inventory: calling GetInventoryAsync (blocking)"); } catch { }
54935519
var result = client.GetInventoryAsync().GetAwaiter().GetResult();
54945520
var resultCode = ExtractCode(result);
54955521
if (result.IsError || result.Result is null)
54965522
{
5523+
try { StarApiExports.StarApiLogFileOnly($"[Inventory] star_api_get_inventory: failed IsError={result.IsError} Message={result.Message ?? "(null)"} Count={result.Result?.Count ?? -1}"); } catch { }
54975524
SetError(result.Message ?? "Failed to load inventory.");
54985525
InvokeOperationCallback(resultCode, StarApiOpGetInventory);
54995526
return (int)resultCode;
55005527
}
55015528

55025529
var count = (nuint)result.Result.Count;
5530+
try { StarApiExports.StarApiLogFileOnly($"[Inventory] star_api_get_inventory: success count={count}"); } catch { }
55035531
var listPtr = (star_item_list_t*)NativeMemory.Alloc((nuint)1, (nuint)sizeof(star_item_list_t));
55045532
if (listPtr is null)
55055533
return (int)SetErrorAndReturn("Memory allocation failed for item list.", StarApiResultCode.InitFailed, StarApiOpGetInventory);
@@ -6043,8 +6071,13 @@ public static int StarApiGetActiveQuestId(sbyte* buf, nuint bufSize)
60436071
var id = client.GetCachedActiveQuestId();
60446072
if (!id.HasValue || id.Value == Guid.Empty)
60456073
{
6046-
try { StarApiExports.StarApiLogFileOnly("[Quest] star_api_get_active_quest_id: no cached value"); } catch { }
6047-
if (StarApiExports.GetStarDebug()) try { StarApiExports.StarApiLog("[Quest] get_active_quest_id: no cached quest (API may not return AvatarDetail.ActiveQuestId)"); } catch { }
6074+
var now = Environment.TickCount64;
6075+
if (now - Volatile.Read(ref _lastNoCachedQuestLogTicks) > 3000)
6076+
{
6077+
Volatile.Write(ref _lastNoCachedQuestLogTicks, now);
6078+
try { StarApiExports.StarApiLogFileOnly("[Quest] star_api_get_active_quest_id: no cached value (throttled)"); } catch { }
6079+
if (StarApiExports.GetStarDebug()) try { StarApiExports.StarApiLog("[Quest] get_active_quest_id: no cached quest (API may not return AvatarDetail.ActiveQuestId)"); } catch { }
6080+
}
60486081
buf[0] = 0; return 0;
60496082
}
60506083
try { StarApiExports.StarApiLog($"[Quest] star_api_get_active_quest_id: returning {id}"); } catch { }
@@ -6066,8 +6099,13 @@ public static int StarApiGetActiveObjectiveId(sbyte* buf, nuint bufSize)
60666099
var id = client.GetCachedActiveObjectiveId();
60676100
if (!id.HasValue || id.Value == Guid.Empty)
60686101
{
6069-
try { StarApiExports.StarApiLogFileOnly("[Quest] star_api_get_active_objective_id: no cached value"); } catch { }
6070-
if (StarApiExports.GetStarDebug()) try { StarApiExports.StarApiLog("[Quest] get_active_objective_id: no cached objective (API may not return AvatarDetail.ActiveObjectiveId)"); } catch { }
6102+
var now = Environment.TickCount64;
6103+
if (now - Volatile.Read(ref _lastNoCachedQuestLogTicks) > 3000)
6104+
{
6105+
Volatile.Write(ref _lastNoCachedQuestLogTicks, now);
6106+
try { StarApiExports.StarApiLogFileOnly("[Quest] star_api_get_active_objective_id: no cached value (throttled)"); } catch { }
6107+
if (StarApiExports.GetStarDebug()) try { StarApiExports.StarApiLog("[Quest] get_active_objective_id: no cached objective (API may not return AvatarDetail.ActiveObjectiveId)"); } catch { }
6108+
}
60716109
buf[0] = 0; return 0;
60726110
}
60736111
try { StarApiExports.StarApiLog($"[Quest] star_api_get_active_objective_id: returning {id}"); } catch { }

0 commit comments

Comments
 (0)