Skip to content

Commit fd5e0c9

Browse files
authored
fix(login): default apiUrl to https://usetimebook.com (no api. subdomain exists) (#4)
The default `apiUrl` was `https://api.usetimebook.com`, but the production deploy on DigitalOcean App Platform only has the bare `usetimebook.com` domain — both the React app and the JSON API are served from the same origin (`/api/*`). DNS for `api.usetimebook.com` returns nothing, so `verifyToken` after the browser callback always threw a bare `TypeError: fetch failed` and the CLI bailed before writing the config — making login appear to succeed (the loopback callback printed) but never persist a token. Also wrap the `fetch` call in `lib/api.ts` to surface the underlying cause (DNS, ECONNREFUSED, cert error, …) instead of the bare `fetch failed`. This would have made the misconfiguration above trivial to diagnose. Users with an already-saved bad apiUrl can re-run with `TIMEBOOK_API_URL=https://usetimebook.com timebook login` once; the corrected value will then be persisted.
1 parent dfd92d5 commit fd5e0c9

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

src/lib/api.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,28 @@ async function rawRequest<T>(
4141
if (opts.body !== undefined) headers['Content-Type'] = 'application/json';
4242
if (token) headers.Authorization = `Bearer ${token}`;
4343

44-
const res = await fetch(url, {
45-
method: opts.method ?? 'GET',
46-
headers,
47-
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
48-
});
44+
let res: Response;
45+
try {
46+
res = await fetch(url, {
47+
method: opts.method ?? 'GET',
48+
headers,
49+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
50+
});
51+
} catch (err) {
52+
// Node's undici throws a bare `TypeError: fetch failed` and stashes the
53+
// real reason (DNS, ECONNREFUSED, cert errors, …) on `cause`. Surface
54+
// both so users can tell a misconfigured API URL from a real outage.
55+
const cause = (err as { cause?: unknown }).cause;
56+
const reason =
57+
cause && typeof cause === 'object'
58+
? ((cause as { code?: string }).code ?? (cause as Error).message ?? '')
59+
: '';
60+
throw new Error(
61+
`Network error reaching ${url.origin}${reason ? ` (${reason})` : ''}. ` +
62+
`Check TIMEBOOK_API_URL or your connection.`,
63+
{ cause: err },
64+
);
65+
}
4966

5067
const text = await res.text();
5168
let data: unknown = undefined;

src/lib/config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ export interface StoredConfig {
2121
createdAt?: string;
2222
}
2323

24+
// Production serves both the React app and the JSON API from the same
25+
// origin (`usetimebook.com/api/*`). There is no `api.usetimebook.com`
26+
// subdomain, so the API URL defaults to the same host as the web URL.
2427
const DEFAULT_CONFIG: StoredConfig = {
25-
apiUrl: process.env.TIMEBOOK_API_URL ?? 'https://api.usetimebook.com',
28+
apiUrl: process.env.TIMEBOOK_API_URL ?? 'https://usetimebook.com',
2629
webUrl: process.env.TIMEBOOK_WEB_URL ?? 'https://usetimebook.com',
2730
};
2831

0 commit comments

Comments
 (0)