`isAnthropicSignInUrl` made the container the default action for every Anthropic sign-in link, justified by "the host has nothing to catch it with". That was wrong in both directions. The host does have something -- the auth bridge -- and the container side is not a general browser at all but Playwright's dashboard, whose packages and chromium are deliberately not baked into the image. So the default pointed at the one path that is uninstalled on a fresh project, on every platform, while the path that works sat behind a switch. The decision now lives in `useSignInOpenTarget`: a live auth bridge picks the host, otherwise a container that can actually launch a browser picks the container, otherwise the host. It resolves at mount rather than when a URL arrives, so the buttons do not swap under a moving mouse, and it re-decides on `auth-bridge-changed` so flipping the switch during a hanging login takes effect. A bridge with port conflicts reads as not live; an empty `active_ports` does not, since there is nothing to bridge until the CLI binds its listener and that races the URL. Both buttons still render either way -- this changes which one leads. `sanitizeRelayUrl` is byte-for-byte unchanged, so the embedded copy in web_terminal/terminal.html needs no matching edit. The host "Open" path also failed silently: `dismissUrlPrompt()` ran before `openUrl`, so the toast vanished and a rejected promise reached only the devtools console. Dismissal now happens on success only, leaving "In container" one click away after a failure, and the error surfaces through the same toast the container path already used. On Linux this catch will not fire for the common case -- `xdg-open` routinely exits 0 having done nothing -- so it complements the AppImage environment fix rather than replacing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
54 lines
2.7 KiB
TypeScript
54 lines
2.7 KiB
TypeScript
/**
|
|
* What a container has to have before anything can be opened *inside* it.
|
|
*
|
|
* The Browser tab asks this to decide what to offer; the terminal's URL toast
|
|
* asks it to decide which of its two buttons should lead. Both need the same
|
|
* answer, so the predicates live here rather than beside either caller — the
|
|
* failure this avoids is the toast steering a user at a container-side browser
|
|
* that the Browser tab is, on the very same screen, offering to install.
|
|
*
|
|
* The important thing to know about `PlaywrightDetection` is that browsers are
|
|
* deliberately **not** baked into the image: the libraries they link against
|
|
* are, the binaries are a user-pressed install. So "Playwright is present" and
|
|
* "a page can actually be opened" are two different questions, and a fresh
|
|
* project answers yes to neither.
|
|
*/
|
|
|
|
import type { PlaywrightDetection } from "./types";
|
|
|
|
/**
|
|
* Mirrors Rust `PlaywrightDetection::is_usable` — the packages the live
|
|
* dashboard needs. Says nothing about whether a browser exists to show in it.
|
|
*/
|
|
export function isBrowserViewUsable(d: PlaywrightDetection | null): boolean {
|
|
return d !== null && d.playwright_version !== null && d.has_bind && d.cli_entry !== null;
|
|
}
|
|
|
|
/**
|
|
* Whether `openPageInContainerBrowser` has a browser to launch.
|
|
*
|
|
* Stricter than {@link isBrowserViewUsable} on purpose: the packages can be
|
|
* installed with `~/.cache/ms-playwright` still empty, which is exactly the
|
|
* state a `playwright install` step exists to leave behind, and launching into
|
|
* it fails several seconds after the click.
|
|
*
|
|
* Unknown reads as "no". A probe that could not run (stopped container, an
|
|
* image predating these fields) leaves the executable fields absent, and the
|
|
* caller's fallback — the host browser — is the one that at least reports its
|
|
* own failure. Over-refusing costs a user one extra click on a button that is
|
|
* still right there; over-accepting costs them a sign-in that goes nowhere.
|
|
*/
|
|
export function canOpenPageInContainerBrowser(d: PlaywrightDetection | null): boolean {
|
|
if (!isBrowserViewUsable(d) || !d) return false;
|
|
// The viewer's own Chromium, confirmed on disk by the probe.
|
|
if (d.chromium_executable_exists) return true;
|
|
// Google Chrome is an apt package, so it is never in `browsers` and has no
|
|
// revision to skew against.
|
|
if (d.chrome_channel !== null) return true;
|
|
// `== null`, not `=== null`: a probe from a container predating the
|
|
// executable fields omits them entirely, and `undefined` there means "didn't
|
|
// answer", not "missing". In that case a non-empty bundle list is the only
|
|
// evidence available, and it is better than nothing.
|
|
return d.chromium_executable == null && d.browsers.length > 0;
|
|
}
|