diff --git a/CHANGELOG.md b/CHANGELOG.md index 26f534b6..077a8a0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,16 @@ All notable user-visible changes are recorded here. Versions follow [Semantic Ve the oldest written. - Starting a session on a machine that cannot receive a password is refused, instead of producing a session nobody can open. +- The README's Homebrew command taps this repository and includes the one-time + `brew trust` step Homebrew 6 asks for. It used to name a tap that does not + exist. +- A request that cannot reach the service says so plainly, instead of "Could + not reach the accounts service at . Is it running?", and an outage page from + the edge no longer surfaces as a JSON parse error. +- The `shell login` approval page says what a linked machine shares with + your team: the full command line, machine name, session name and timings, + and what is typed from a browser. It used to say only the command name and + timing were published. ## [0.11.3] — 2026-09-11 diff --git a/README.md b/README.md index 3c16f9cc..703b60f2 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,17 @@ Windows PowerShell: irm https://shell.online/install.ps1 | iex ``` -Homebrew: +Homebrew (the tap lives in this repository): ```sh -brew install TeoSlayer/shell-online/shell-online +brew tap teoslayer/shell-online https://github.com/TeoSlayer/shell.online +brew trust --tap teoslayer/shell-online +brew install shell-online ``` +Homebrew 6 asks you to trust a third-party tap once. Older versions have no +`brew trust` and can skip that line. + Installers verify checksums. Release binaries and `SHA256SUMS` are available on the [releases page](https://github.com/TeoSlayer/shell.online/releases). diff --git a/app/src/lib/api.test.ts b/app/src/lib/api.test.ts new file mode 100644 index 00000000..c017a0cc --- /dev/null +++ b/app/src/lib/api.test.ts @@ -0,0 +1,52 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +vi.mock("./firebase", () => ({ + auth: { currentUser: { getIdToken: async () => "id-token" } }, +})); + +import { fetchDevices, NETWORK_FAILURE, SERVER_FAILURE } from "./api"; + +function respond(status: number, text: string) { + vi.stubGlobal("fetch", vi.fn(async () => new Response(text, { status }))); +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("request errors", () => { + it("says shell.online could not be reached, naming no URL", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => { + throw new TypeError("Failed to fetch"); + }), + ); + await expect(fetchDevices()).rejects.toThrow(NETWORK_FAILURE); + }); + + it("does not pass on a parser error when the edge answers with HTML", async () => { + respond(502, "
Bad gateway"); + await expect(fetchDevices()).rejects.toThrow(SERVER_FAILURE); + }); + + it("treats an unreadable success as a failure rather than returning it", async () => { + respond(200, ""); + await expect(fetchDevices()).rejects.toThrow(SERVER_FAILURE); + }); + + it("passes the service's own message through", async () => { + respond(404, JSON.stringify({ error: "no such machine" })); + await expect(fetchDevices()).rejects.toThrow("no such machine"); + }); + + it("falls back to a sentence when a failure carries no message", async () => { + respond(500, ""); + await expect(fetchDevices()).rejects.toThrow(SERVER_FAILURE); + }); + + it("returns the body of a success", async () => { + respond(200, JSON.stringify({ devices: [] })); + await expect(fetchDevices()).resolves.toEqual({ devices: [] }); + }); +}); diff --git a/app/src/lib/api.ts b/app/src/lib/api.ts index 92141dae..02e9cee9 100644 --- a/app/src/lib/api.ts +++ b/app/src/lib/api.ts @@ -235,6 +235,13 @@ export interface SessionRecord { class ApiError extends Error {} +/* + * Written for the person reading them. BASE is empty in every deployment, so + * a message that names it reads "at ." -- and it was never theirs to fix. + */ +export const NETWORK_FAILURE = "Could not reach shell.online. Check your connection and try again."; +export const SERVER_FAILURE = "Something went wrong on our side. Try again."; + async function request
A terminal on this computer is asking to sign in as{" "}
{email}. Once linked, sessions you start with{" "}
- shell show up in your account.
+ shell show up in your account, and everyone on your
+ team can see them.