feat(auth): partner region login (Jio, bro.game, etc.) - #64
Conversation
|
@owenselles Thinking out loud, maybe we can check if this PR have some overlap with #67 . Maybe we should unify and combine the server/location feature. |
|
Agree also for testing this we need someone with a partner account |
|
Hi! I have an active GeForce NOW account through Digevo and I can help test the partner-provider implementation in PR #64. I also have an Apple TV 4K 3rd generation, so I can test the complete flow on real hardware: provider detection, authentication, library loading, game launch, server routing, and streaming. If needed, I can build and test the PR branch myself and provide logs or other diagnostic information. |
|
Yea @RichiX9711 if you can that would be of great help as no one else I know has a partner account |
|
So I have no real way to test and implement this. Current implementation is based on other open source apps |
|
I tested this PR on real hardware with an active Digevo GeForce NOW partner account in Peru. Test environment Apple TV 4K (3rd generation) The core partner authentication flow is working: Digevo is correctly returned and displayed as a login provider. The logs also confirm that the selected partner service URL is initially resolved correctly: startSession: base=https://prod.DIG.geforcenow.nvidiagrid.net So the provider selection and streamingServiceUrl plumbing are working with a real partner account. Routing issue found I found an issue with the current partner detection in GamesViewModel.measureTopZones(). The current check is: let streamingUrl = authManager.session?.provider.streamingServiceUrl ?? "" This does not reliably distinguish NVIDIA's default service from a partner service. Digevo's actual partner URL is: https://prod.DIG.geforcenow.nvidiagrid.net Because it also contains nvidiagrid.net, NVIDIA global zone discovery still runs for the Digevo account. During my test, CloudNow measured zones such as: NP-ATL-04 At game launch it selected: [Zones] best at launch: Then the session was created with: sessionBase=https://np-atl-04.cloudmatchbeta.nvidiagrid.net/ and the POST was sent directly to: https://np-atl-04.cloudmatchbeta.nvidiagrid.net/v2/session Therefore, although the selected provider initially resolves to Digevo, the auto-zone logic replaces the partner streamingServiceUrl with a global NVIDIA CloudMatch zone before session creation. This appears to be caused by the interaction between: measureTopZones() allowing Digevo because its URL also contains nvidiagrid.net. I think partner detection should be based on the selected provider/default NVIDIA service rather than a substring check. Partner sessions should keep the provider's streamingServiceUrl and use no NVIDIA global zone hint, unless partner-specific zone discovery is implemented. It may also be worth enforcing this at session creation, not only in measureTopZones(), so a partner session cannot accidentally reuse previously measured or persisted NVIDIA zones. Build issue The branch also failed to compile because measureTopZones() references authManager, but authManager is not available in that method's scope. I locally fixed it by changing: func measureTopZones() async to: func measureTopZones(authManager: AuthManager) async and changing the call in MainTabView to: .task { await viewModel.measureTopZones(authManager: authManager) } After that change, the branch compiled and I was able to complete the full Digevo test. tvOS provider picker issue I also found a UI/focus issue in the provider picker. There are more providers than can fit on screen. The focus can continue moving to providers outside the visible area, but the UI does not scroll correctly to keep the focused provider visible. During this interaction I also saw repeated tvOS focus-engine warnings: Ignoring attempt to add focus items in already-visited container. I cannot confirm yet whether those warnings are the direct cause of the scrolling issue, but the provider picker needs focus-aware scrolling on tvOS. Suggested next step I can prepare a small fix that: fixes the authManager compile error; I would keep the provider-picker scrolling issue separate because it is an independent tvOS UI/focus problem. I can also continue testing the changes with my real Digevo account and Apple TV hardware. |
|
Working on fixes for those issues will push to this pr in a few mins @RichiX9711 Thanks a lot for testing and please check again when you got time :) |
|
Hi guys, it's been a few days since my last comment, I have completed the real-hardware validation of PR #64 using an active Digevo GeForce NOW account in Peru. PR scope validationThe original goal of this PR — authenticating and launching games through a third-party GeForce NOW partner — is working successfully in my environment. Test environment:
The following flow now works end to end:
Based on these results, I consider the original functional scope of PR #64 complete. Partner routing and automatic region selectionThe official GeForce NOW macOS client only exposes one selectable region for my account, named “LATAM West”. For CloudNow, however, I tested Digevo using provider-managed automatic routing instead of forcing that NVIDIA region manually. The important behavior is:
This is consistent with the changes already added to the PR that identify NVIDIA-direct sessions using the provider That distinction is necessary because Digevo’s own service URL also uses an NVIDIA Grid domain. Additional local telemetry workWhile testing Rocket League, I noticed visible stuttering during rapid camera movement. This appeared more frequently at 4K. To determine whether the stuttering came from Digevo/NVIDIA, the Apple TV decoder, or CloudNow’s rendering pipeline, I added local diagnostic telemetry. These diagnostics are not required for the partner-login functionality and are currently outside the intended scope of PR #64. The local telemetry uses CloudNow’s existing one-second WebRTC statistics timer. It does not create another timer or perform an additional WebRTC statistics query. In Diagnostic mode, it records:
I also corrected the interval packet-loss calculation so that it is reset for every statistics sample and cannot reuse the previous interval’s percentage. Comparative testsI performed separate 1080p and 4K Rocket League sessions on the same Apple TV and provider account. 1080pDuring the stable gameplay portion:
4KDuring the stable gameplay portion:
The camera-motion stuttering was consistent with these chronological FPS drops. Current findingsThe network does not currently appear to be the main cause:
The existing local pipeline counters also remained at zero:
The Apple TV requires more decode time at 4K than at 1080p, as expected, but the measured 4K decode time remained below the approximately There were also complete 4K windows that maintained 60 FPS with similar decode times. Therefore, the current data does not demonstrate decoder saturation or that the Apple TV is incapable of decoding the stream. The most significant observation is that, during severe 4K FPS reductions, bitrate decreases almost exactly in proportion to the number of frames. For example, a reduction from approximately 60 FPS at 95 Mbps to 42 FPS resulted in approximately 66.6 Mbps. The approximate number of bits per frame remained almost constant. This suggests that fewer frames may already be produced or delivered during those intervals, rather than CloudNow receiving 60 complete frames and dropping them locally. However, the current
Because of that, I do not think the logs are sufficient to assign the problem definitively to Digevo/NVIDIA, VideoToolbox, or CloudNow’s presentation pipeline. There is also one uncontrolled variable in the comparison: the 1080p and 4K sessions were assigned to different Digevo infrastructure hosts, although both were routed through the same Digevo CloudMatch service. Suggested follow-up outside PR #64The next useful diagnostic step would be to instrument the complete frame path:
In particular:
This would establish the responsible stage:
Sharing the evidenceI can provide sanitized versions of the 1080p and 4K telemetry logs. Before sharing them publicly, I will remove or normalize authentication data, account identifiers, request identifiers, local addresses, session-specific values and any potentially sensitive infrastructure details. I also have the diagnostic instrumentation as a local code change. Since it is outside the scope of PR #64, I have not assumed that it should be added to this branch. Please let me know which form would be most useful:
For now, my conclusion is that the third-party login and provider-managed Digevo routing are working correctly, while the 4K stuttering investigation should be treated as a separate diagnostic topic. |
|
Nicee good that it works now! |
|
@RichiX9711 Thank you for the thorough validation report — this is exactly the kind of real-hardware evidence needed to confirm the partner routing is correct. Two things addressed based on your findings: Provider picker scrolling — fixed. The Partner routing guard — the For the 4K diagnostic work — a separate draft PR from your fork with just the telemetry instrumentation would be ideal. That keeps the frame-path instrumentation reviewable on its own and avoids scope creep here. If you open it I'll take a look. The 4K stuttering investigation findings (frames-received vs decoded vs rendered breakdown) are worth tracking separately too — feel free to open an issue for that so it doesn't get lost. |
6f85002 to
bd2071c
Compare
- LoginView: fetch providers on appear and show a scrollable picker when multiple providers are available; @focusstate drives ScrollViewReader to keep the focused button centred (fixes tvOS focus-engine blind spot) - StreamView: guard NVIDIA zone/region selection with isNvidiaProvider so partner sessions route through their own infrastructure unchanged - README: document partner provider support and update requirements line
bd2071c to
6f6ecad
Compare
|
@owenselles The approach looks directionally good to me. I especially like that the routing fix moved away from checking whether the streaming URL contains Before merging, I think there are a few things worth tightening up:
|
- LoginProvider.isNvidiaDirect: centralizes the idpId==defaultIdpId check so it is not repeated inline across StreamView and SettingsView - LoginView: replace the nil-providers fallback with an explicit ProvidersState enum (.loading / .loaded); shows a spinner while the provider list is fetching so a partner-region user cannot accidentally tap the NVIDIA button before providers arrive; sets initial @focusstate to the first provider for deterministic tvOS focus on load - SettingsView: hide the server-location picker for partner sessions and replace it with a "Managed by partner provider" note, since those settings are silently ignored for partner routing anyway - Localization: add managed_by_partner key to all 34 locale tables
|
@owenselles I reviewed the latest head ( The latest commit adds the requested loading state, partner-managed Settings message, initial provider focus, and centralized MAJOR — Partner session creation can still fall back to NVIDIA
Every partner endpoint differs from This bypasses the new partner-routing guarantee and could:
The fallback policy should be explicit:
Please test a simulated partner HTTP failure and confirm that no request is sent to MAJOR — A single returned partner is treated as NVIDIA
For If the endpoint returns one partner provider, the UI therefore:
Please distinguish these states explicitly:
A single provider should use its own The NVIDIA fallback should only appear after an actual provider-fetch failure or when the sole provider is confirmed as NVIDIA-direct. MAJOR — “Try Again” forgets the selected partner
If the user selected a non-first partner provider and authentication fails, “Try Again” fetches the providers again and selects Please retain the attempted provider in “Cancel” can continue returning the user to the provider picker. MAJOR — Network Test can still test a stale NVIDIA route for partner users
The Settings picker is hidden for partner sessions, but A partner account can therefore still test:
For partner sessions:
Please test NVIDIA-direct → logout → partner login → Network Test without restarting the app. MAJOR — Persisted session and VPC state has no provider/account identity
After signing out and selecting another provider, The cached VPC ID is also used immediately while revalidation happens in the background. A newly selected partner can therefore receive initial catalog/library requests containing an NVIDIA or different-partner VPC ID. Suggested handling:
This is especially important for partner-to-partner switching because it avoids sending one partner’s token to another partner’s endpoint. Localization follow-upMINOR — New text is English in every locale
The new key currently contains |
…on identity, cache keying, l10n - CloudMatchClient: partner sessions no longer fall back to NVIDIA's global endpoint; SessionCreateRequest gains skipNvidiaFallback which gates the [preferredBase, fallbackBase] retry list to [preferredBase] only for partners. - LoginView: add @State selectedProvider so the correct provider is carried through to "Try Again" after a login failure; handle the single-provider case explicitly so a solo partner provider shows its own displayName and calls login(with:) instead of the NVIDIA fallback. - LastSessionRecord: add idpId field (backward-compat decode defaults to NVIDIAAuth.defaultIdpId); GamesViewModel.load() discards a persisted session whose idpId doesn't match the current provider before restoring it. - StreamView: pass skipNvidiaFallback and the current provider's idpId when creating and persisting a session. - ServerInfoClient: add cachedBase tracking and cachedForBase(_:) so the cache is provider-keyed; SettingsView NetworkTest resolveTarget() guards routing-mode branches with isNvidiaSession and uses cachedForBase(). - Localization: translate managed_by_partner into all 33 non-English locales.
|
@owenselles Shall I review it later today? Not sure if this is ready since there are merge conflicts. Just tag me once I should prioritise it's review :) |
Resolve conflicts between partner region login and the features merged into main since this branch was cut (#94-#105). Localization (34 files): both sides appended keys to the same dictionary tail. Kept both sets — main's library-refresh strings and the branch's "managed_by_partner". All locales verified at identical 317-key parity. ServerInfoClient: main reworked the client into an injectable type with a TTL'd, base-scoped cache; this branch had added a `cachedBase` field plus `cachedForBase(_:)` so partner providers never read each other's region data. Kept main's init and TTL cache, and reimplemented `cachedForBase(_:)` on top of main's `cachedBaseURL`. Base normalization is now a shared `normalizedBase` helper so `fetch` and `cachedForBase` agree on how a URL compares. SettingsView: `resolveTarget` kept main's always-refresh-with-cache-fallback shape, with the cache read scoped to the current provider's base. `loadRegions` merged cleanly but read the unscoped cache, which would surface another provider's regions; it now seeds from `cachedForBase` and clears the optimistic seed from `init` when the provider does not match. StreamView: `createNewSession` combined main's generation/attempt-token signature and guard with this branch's partner-provider branch that skips NVIDIA zone/region selection. README: took main's tvOS/Xcode 26.2+ requirements plus this branch's partner-region account note.
|
@aarikmudgal Conflicts are resolved — Two of the conflicts are worth flagging because they touch your earlier review directly:
One thing beyond the conflict markers: main added a second server-info cache read in The rest was mechanical: 34 localization files where both sides appended keys to the same dictionary tail. Kept both sets; all locales verified at identical key parity with no duplicates. Generated by Claude Code |
Merging main into this branch produced a semantic conflict git could not detect: this branch added a required skipNvidiaFallback parameter to SessionCreateRequest, while main's test suite (#101) added four call sites that construct the type without it. Both sides merged cleanly and the test target then failed to build with "Missing argument for parameter 'skipNvidiaFallback' in call". All four fixtures are NVIDIA-direct, so they pass false (fallback allowed), preserving the behavior each test asserts — including the CloudMatchClient test that exercises the preferred-base failure falling back to the NVIDIA global endpoint. Kept the parameter required rather than giving it a default, so no call site can silently opt back into NVIDIA fallback for a partner session.
|
@owenselles I reviewed the latest head ( First, thank you for working through the previous feedback. Most of it is now fixed:
The Digevo testing also gives us good evidence that the main feature works: authentication completes, the library loads, games launch through the partner service, and streaming works on real Apple TV hardware. I want to separate the remaining points by where they came from. Some were already in my earlier review, some became visible after the fixes and the merge from Items still open from my earlier review1. Persisted state still needs both provider and user identityThis was part of my July 22 review. The current change adds Relevant code:
This means two users of the same provider still look compatible. After User A signs out and User B signs in through the same provider, CloudNow can try to claim or stop User A's saved session using User B's token. The VPC and subscription caches also remain global:
The saved VPC is used immediately while a refresh runs in the background. After an account or provider change, the first catalog and library requests can therefore use the previous identity's VPC. A failed subscription refresh can also leave the previous subscription in memory. Requested fix:
2. The partner fallback regression test is still missingThe production code now correctly prevents partner sessions from falling back to NVIDIA. That part is fixed. The requested test was not added, though. Every updated test currently passes Requested test:
This test matters because the branch prevents a partner token and session request from being sent to the wrong service. Issue exposed by the revised fallback path3. The NVIDIA fallback button can still start a partner loginThis became visible after following the new empty-provider fallback through
When no provider is supplied,
If the first discovery request fails and the second succeeds with a partner first, the NVIDIA-labelled button starts that partner's login flow. The label and action no longer agree. Requested fix:
New observation from checking NVIDIA's live response4. Provider preference metadata is currently ignoredThis point is new. I had previously assumed that For the review request from Germany, NVIDIA returned:
The current decoder ignores
Reference: https://pcs.geforcenow.com/v1/serviceUrls This one is on me. The response contract should have been checked earlier. I do not want to make this an automatic merge blocker without agreeing on the intended product behavior. If the goal is to expose the complete provider catalog for users who travel, showing all providers may be intentional. If the goal is to match NVIDIA's regional login behavior, the preferred/default metadata should control the list and initial focus. Requested decision:
Non-blocking hardeningThese are worth cleaning up, but I would not hold the merge for them unless testing shows a user-facing failure.
What I consider blocking before approvalTo avoid moving the target, these are the three changes I would require before approval:
The provider-preference behavior needs a product decision. The focus, initial cache seed, and PR-description updates are follow-up hardening rather than blockers. |
… login Addresses the three blocking items from the second review. 1. Account identity isolation LastSessionRecord carried idpId but not userId, so two users of the same provider looked interchangeable: after user A signed out and user B signed in, the saved session was restored and could be claimed or stopped with user B's token. VPC and subscription caches had no identity at all, so the first catalog and library requests after a switch could carry the previous account's VPC. - Add accountCacheScope(idpId:userId:) for local cache keying. Kept separate from nvidiaAccountScope, which is the huId wire value and must stay user-only. - LastSessionRecord gains userId. Records predating the field decode to nil and are discarded rather than resumed, since they cannot be attributed. Discarding is local; the previous endpoint is never contacted with the new token. - Scope VPC and subscription behind a ScopedValueEnvelope carrying the writing identity; a mismatched or unscoped read returns nil. Values written before scoping fail to decode, which is the safe outcome. - Because resolveVpcIdCached already fetches synchronously on a nil cache, a mismatched identity now resolves a fresh VPC through the existing path. - Both accountScope construction sites fold in idpId so a refresh cannot write the library cache under a key the next load fails to read. 2. Partner fallback regression test Added the previously requested coverage: a partner base with skipNvidiaFallback returning 503 makes exactly one request, never reaches prod.cloudmatchbeta.nvidiagrid.net, and surfaces the partner error unchanged. Also covers identity isolation for the VPC and subscription caches. 3. NVIDIA fallback button The empty-provider button read "Sign in with NVIDIA" but called login() with no provider, which refetched and took providers.first — a partner if discovery returned one on the retry. It now passes LoginProvider.nvidiaDirect explicitly, so the action matches the label and "Try Again" retries NVIDIA rather than whatever discovery returns. AuthManager's inline fallback uses the same value. Provider preference metadata (defaultProvider / loginPreferredProviders) is left as-is pending a product decision on whether the picker should show every provider or only the regionally preferred ones.
fetchProviders ignored NVIDIA's defaultProvider and loginPreferredProviders and listed every endpoint by raw priority, so a regionally preferred provider could sort below one that does not apply to the caller. Preferred providers now sort first, with defaultProvider ahead of them so it takes initial focus (LoginView already focuses the first entry). Non-preferred endpoints stay listed rather than being filtered: a travelling user still needs their home provider, and a misdetected region must not lock anyone out. With no metadata present the order falls back to the advertised priority, unchanged. Both fields are optional and accepted at the response root as well as inside gfnServiceInfo, so a change in nesting degrades to "no preference" instead of failing the decode. Adds auth-providers-preferred.json covering preference-over-priority ordering and keeps auth-providers.json as the no-metadata fallback case. Also fixes the SwiftFormat unusedArguments failure from the previous commit: loadScoped bound a `type` parameter it never used, since the value type is inferred rather than passed to the decoder.
Summary
GeForce NOW is available via third-party partners in some regions — Jio in India, bro.game in Brazil, and potentially others. NVIDIA already exposes these via
https://pcs.geforcenow.com/v1/serviceUrls, the same endpoint we already call inNVIDIAAuthAPI.fetchProviders(). The auth plumbing (idpId,streamingServiceUrl,AuthSession.provider) was already wired end-to-end — the only missing piece was surfacing provider choice in the UI.LoginView: fetches providers on appear; shows one button per provider when multiple are returned (e.g. "NVIDIA", "Jio", "bro.game"), falling back to the existing single NVIDIA button if the fetch fails or only one provider is availableGamesViewModel: guardsmeasureTopZones()to skip PrintedWaste zone discovery for non-nvidiagrid.netproviders — prevents NVIDIA zone URLs from being passed to a partner session API; partner sessions use a nil zone hint and let the provider's own CloudMatch route the connectionNo changes to auth endpoints,
CloudMatchClient,GamesClient, or any streaming code — partneridpIdfederation happens server-side onlogin.nvidia.com.Test plan
streamingServiceUrlwith no zone hint