Skip to content
Merged
Show file tree
Hide file tree
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
24 changes: 23 additions & 1 deletion apps/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { DeleteChannel } from "./delete-channel";
import { Github, GithubSidebar } from "./github";
import { useGithub, useGithubRefresh } from "./github-cache";
import { desktop, here, titlebar } from "./desktop";
import { host } from "./host";
import { host, refused } from "./host";
import { type Invite, parse } from "./invite";
import { Rename } from "./layout/rename";
import { MoveChannel } from "./move-channel";
Expand Down Expand Up @@ -125,9 +125,31 @@ function useAnswers(): boolean | undefined {
return answers;
}

/** Asked again while disconnected: a helper that was down can come back with a new token. */
function useRefused(): boolean {
const [value, setValue] = useState(false);
useEffect(() => {
if (deployed || desktop) return;
const check = () => void refused().then(setValue);
check();
const interval = setInterval(check, 5000);
return () => clearInterval(interval);
}, []);
return value;
}

function Disconnected() {
const answers = useAnswers();
const signedOut = useRefused();
if (desktop) return <>Disconnected from Ace Helper. Open Settings → This Mac to start it.</>;
if (signedOut) {
return (
<>
This browser isn't signed in to your host. Run <code>ace open</code>{" "}
on that Mac, or open its sign-in link again.
</>
);
}
if (!deployed) return <>Disconnected from your host. Reconnecting…</>;
return (
<>
Expand Down
17 changes: 17 additions & 0 deletions apps/app/src/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ if (fragment.has("token")) {
// main.tsx loads the app only once there is a gateway to reach.
export const host = new GatewayClient(gateway!, token);

// A sign-in link opened in a tab already on this origin changes only the hash, so nothing reads it.
window.addEventListener("hashchange", () => {
if (new URLSearchParams(location.hash.slice(1)).has("token")) location.reload();
});

/**
* Whether the host refuses this page's credentials, as after Ace Helper restarts with a new token.
* A browser hides a WebSocket's 401, so this asks over HTTP with what the socket sends.
*/
export async function refused(): Promise<boolean> {
const response = await fetch(gateway!.replace(/^ws/, "http"), {
cache: "no-store",
...(token ? { headers: { authorization: `Bearer ${token}` } } : {}),
}).catch(() => undefined);
return response?.status === 401;
}

// Phones suspend backgrounded pages; reconnect as soon as the person comes back.
for (const event of ["visibilitychange", "online", "pageshow"]) {
window.addEventListener(event, () => {
Expand Down