Fix Quest Platform SDK auth flow
- Use asyncInitialize with message pump for proper SDK callback delivery - Fall back to oculusId when numeric user ID unavailable (DUC pending) - Gracefully handle missing nonce - Auto-upgrade to full nonce validation when numeric ID becomes available
This commit is contained in:
@@ -14,7 +14,6 @@ import com.meta.horizon.platform.ovr.models.UserProof
|
|||||||
import com.meta.horizon.platform.ovr.requests.Request
|
import com.meta.horizon.platform.ovr.requests.Request
|
||||||
import com.meta.horizon.platform.ovr.requests.Users
|
import com.meta.horizon.platform.ovr.requests.Users
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -43,35 +42,43 @@ class LoginViewModel @Inject constructor(
|
|||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
_error.value = null
|
_error.value = null
|
||||||
try {
|
try {
|
||||||
// Initialize Platform SDK
|
// Initialize Platform SDK (async, wait for completion via message pump)
|
||||||
if (!Core.isInitialized()) {
|
if (!Core.isInitialized()) {
|
||||||
Log.d(TAG, "Initializing Platform SDK with appId=$QUEST_APP_ID")
|
Log.d(TAG, "Async initializing Platform SDK with appId=$QUEST_APP_ID")
|
||||||
val initResult = Core.initialize(QUEST_APP_ID, activity.applicationContext)
|
val initResult = awaitWithPump { Core.asyncInitialize(QUEST_APP_ID, activity.applicationContext) }
|
||||||
Log.d(TAG, "Core.initialize returned: $initResult")
|
Log.d(TAG, "Platform SDK initialized: $initResult")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Platform SDK already initialized")
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Core.isInitialized=${Core.isInitialized()}")
|
|
||||||
|
|
||||||
// Check cached user ID first (synchronous)
|
// Get logged-in user (async with message pump)
|
||||||
val cachedUserId = Core.getLoggedInUserID()
|
Log.d(TAG, "Requesting logged-in user...")
|
||||||
Log.d(TAG, "Core.getLoggedInUserID() = $cachedUserId")
|
val user = awaitWithPump { Users.getLoggedInUser() }
|
||||||
|
val numericId = user.getID().toString()
|
||||||
|
val oculusId = user.oculusID
|
||||||
|
Log.d(TAG, "User: id=$numericId displayName=${user.displayName} oculusId=$oculusId")
|
||||||
|
|
||||||
if (cachedUserId == 0L) {
|
// Use numeric ID if available (requires Data Use Checkup), fall back to oculusId
|
||||||
throw Exception("Platform SDK returned user ID 0. " +
|
val userId = if (user.getID() != 0L) numericId else oculusId
|
||||||
"Make sure the app is installed from the Horizon store (not sideloaded) " +
|
if (userId.isNullOrEmpty()) {
|
||||||
|
throw Exception("Platform SDK returned no user identifier. " +
|
||||||
|
"Make sure the app is installed from the Horizon store " +
|
||||||
"and your account is a test user for this app.")
|
"and your account is a test user for this app.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get full user via async call with manual message pump
|
// Get user proof (nonce) — may fail without DUC approval
|
||||||
val user = awaitWithPump { Users.getLoggedInUser() }
|
var nonce = ""
|
||||||
val userId = user.getID().toString()
|
try {
|
||||||
Log.d(TAG, "User: id=$userId displayName=${user.displayName}")
|
Log.d(TAG, "Requesting user proof...")
|
||||||
|
val proof = awaitWithPump { Users.getUserProof() }
|
||||||
// Get user proof (nonce)
|
nonce = proof.value
|
||||||
val proof = awaitWithPump { Users.getUserProof() }
|
Log.d(TAG, "UserProof nonce length=${nonce.length}")
|
||||||
val nonce = proof.value
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, "UserProof nonce=$nonce")
|
Log.w(TAG, "getUserProof failed (DUC pending?), proceeding without nonce", e)
|
||||||
|
}
|
||||||
|
|
||||||
// Send to backend for verification
|
// Send to backend for verification
|
||||||
|
Log.d(TAG, "Sending to backend: userId=$userId hasNonce=${nonce.isNotEmpty()}")
|
||||||
val response = apiService.metaCallback(
|
val response = apiService.metaCallback(
|
||||||
MetaCallbackRequest(
|
MetaCallbackRequest(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
@@ -82,6 +89,7 @@ class LoginViewModel @Inject constructor(
|
|||||||
|
|
||||||
// Save session tokens
|
// Save session tokens
|
||||||
tokenStore.saveSession(response.accessToken, response.refreshToken)
|
tokenStore.saveSession(response.accessToken, response.refreshToken)
|
||||||
|
Log.d(TAG, "Login successful!")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Quest login failed", e)
|
Log.e(TAG, "Quest login failed", e)
|
||||||
_error.value = e.message ?: "Login failed"
|
_error.value = e.message ?: "Login failed"
|
||||||
@@ -97,7 +105,7 @@ class LoginViewModel @Inject constructor(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Awaits a Platform SDK request by manually pumping the message queue.
|
* Awaits a Platform SDK request by manually pumping the message queue.
|
||||||
* The SDK requires Request.runCallbacks() to be called for callbacks to fire.
|
* The SDK delivers responses via Core.popSDKMessage() which must be polled.
|
||||||
*/
|
*/
|
||||||
private suspend fun <T> awaitWithPump(
|
private suspend fun <T> awaitWithPump(
|
||||||
block: () -> Request<T>,
|
block: () -> Request<T>,
|
||||||
@@ -105,25 +113,30 @@ class LoginViewModel @Inject constructor(
|
|||||||
var completed = false
|
var completed = false
|
||||||
block()
|
block()
|
||||||
.onSuccess { result: T ->
|
.onSuccess { result: T ->
|
||||||
|
Log.d(TAG, "SDK request succeeded: ${result?.javaClass?.simpleName}")
|
||||||
if (!completed) {
|
if (!completed) {
|
||||||
completed = true
|
completed = true
|
||||||
cont.resume(result)
|
cont.resume(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onError { error ->
|
.onError { error ->
|
||||||
|
Log.e(TAG, "SDK request failed: code=${error.code} http=${error.httpCode} msg=${error.message}")
|
||||||
if (!completed) {
|
if (!completed) {
|
||||||
completed = true
|
completed = true
|
||||||
cont.resumeWithException(Exception(error.message))
|
cont.resumeWithException(Exception("SDK error ${error.code}: ${error.message}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pump messages on a background thread
|
// Pump messages on a background thread
|
||||||
Thread {
|
Thread {
|
||||||
val timeout = System.currentTimeMillis() + 10_000 // 10s timeout
|
val timeout = System.currentTimeMillis() + 15_000 // 15s timeout
|
||||||
|
var msgCount = 0
|
||||||
while (!completed && System.currentTimeMillis() < timeout) {
|
while (!completed && System.currentTimeMillis() < timeout) {
|
||||||
try {
|
try {
|
||||||
val msg = Core.popSDKMessage()
|
val msg = Core.popSDKMessage()
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
|
msgCount++
|
||||||
|
Log.d(TAG, "Pumped message #$msgCount type=${msg.type} isError=${msg.isError}")
|
||||||
Request.handleMessage(msg)
|
Request.handleMessage(msg)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -131,9 +144,10 @@ class LoginViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Message pump done: completed=$completed msgs=$msgCount")
|
||||||
if (!completed) {
|
if (!completed) {
|
||||||
completed = true
|
completed = true
|
||||||
cont.resumeWithException(Exception("Platform SDK request timed out"))
|
cont.resumeWithException(Exception("Platform SDK request timed out after 15s (pumped $msgCount messages)"))
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user