Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions apps/mobile/src/lib/auth/use-device-auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as WebBrowser from 'expo-web-browser';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Platform } from 'react-native';

import { API_BASE_URL, WEB_BASE_URL } from '@/lib/config';

Expand All @@ -21,6 +22,16 @@ type DeviceAuthResult = DeviceAuthState & {

const POLL_INTERVAL_MS = 3000;

// Android has no native auth session; expo-web-browser's polyfill keeps
// module-level state that can get stuck and reject every future call
// (KILO-APP-22). We poll the server for approval instead of relying on a
// redirect, so a plain browser open is all Android needs.
async function openAuthBrowser(url: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Android now opens the browser with openBrowserAsync, but approval still dismisses via dismissAuthSession()

WebBrowser.dismissAuthSession() only affects sessions started with openAuthSessionAsync; it has no effect on a browser opened with openBrowserAsync. Since Android now goes through openBrowserAsync in both start() and openBrowser(), the tab opened on Android will no longer auto-close when polling detects approval (the case 200: branch in poll() still calls WebBrowser.dismissAuthSession()). The user will have to manually switch back to the app after approving sign-in on Android. Consider calling WebBrowser.dismissBrowser() on Android in that success path as well.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked against expo-web-browser 55.0.16 source: the Android native module (WebBrowserModule.kt) implements no dismissal at all — no dismissBrowser, no dismissAuthSession. Custom tabs can't be closed programmatically; return-to-app is handled by BrowserProxyActivity when the user closes the tab. dismissAuthSession() on Android has always taken the JS fallback branch targeting ExponentWebBrowser.dismissBrowser, which doesn't exist there — so the tab never auto-closed on Android before this PR either, and dismissBrowser() (ExponentWebBrowser.dismissBrowser?.()) would be a no-op. No regression.

The valid part: with Android no longer opening an auth session, the unconditional dismissAuthSession() in poll() is pointless there and a latent UnavailabilityError throw site inside the poll's try block. Follow-up guards it to iOS: #4377

await (Platform.OS === 'android'
? WebBrowser.openBrowserAsync(url)
: WebBrowser.openAuthSessionAsync(url));
}

export function useDeviceAuth(): DeviceAuthResult {
const [state, setState] = useState<DeviceAuthState>({
status: 'idle',
Expand Down Expand Up @@ -174,7 +185,7 @@ export function useDeviceAuth(): DeviceAuthResult {
abortReference.current = abort;
poll(data.code, abort);

await WebBrowser.openAuthSessionAsync(browserUrl);
await openAuthBrowser(browserUrl);
} catch {
setState({
status: 'error',
Expand All @@ -201,7 +212,17 @@ export function useDeviceAuth(): DeviceAuthResult {

const openBrowser = useCallback(async () => {
if (state.verificationUrl) {
await WebBrowser.openAuthSessionAsync(state.verificationUrl);
try {
await openAuthBrowser(state.verificationUrl);
} catch {
setState(previous => ({
status: 'error',
code: previous.code,
token: undefined,
error: 'Could not open browser. Please try again.',
verificationUrl: previous.verificationUrl,
}));
}
}
}, [state.verificationUrl]);

Expand Down