mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
fix: surface a clear error when requestUrl() itself rejects with HTML content
parseJson() already handles the case where a response resolves normally but its .json getter throws on an HTML body (e.g. a login/SSO redirect or proxy error page). But some Obsidian versions eagerly parse the response body as JSON inside requestUrl() itself, so the same failure can instead reject the whole call with a raw SyntaxError — landing in safeRequest's outer catch, before there's even a response object to inspect. That catch just rethrew the error verbatim, surfacing the literal "Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON" message to the user (as seen in the reported "Failed to refresh" case, against a self-hosted GitLab instance). safeRequest's outer catch now recognizes this error's known V8 phrasings and throws the same friendly "received an HTML page" message parseJson() already produces for the other code path. Closes #31
This commit is contained in:
parent
ef238cea59
commit
a86721752a
2 changed files with 48 additions and 1 deletions
|
|
@ -78,8 +78,20 @@ export abstract class BaseGitService {
|
||||||
|
|
||||||
response = await requestUrl(options);
|
response = await requestUrl(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Network-level failure (DNS, offline, TLS, etc.)
|
// Network-level failure (DNS, offline, TLS, etc.) — but some Obsidian
|
||||||
|
// versions eagerly parse the response body as JSON inside requestUrl()
|
||||||
|
// itself, before `throw: false` or our own status check ever run. If a
|
||||||
|
// proxy/login page returns HTML instead of JSON, that eager parse
|
||||||
|
// throws here as a raw "Unexpected token '<' ... is not valid JSON"
|
||||||
|
// error rather than surfacing as a normal response we could inspect.
|
||||||
if (!silent) logger.error('Git Service Request Failed:', error);
|
if (!silent) logger.error('Git Service Request Failed:', error);
|
||||||
|
if (this.looksLikeJsonParseOfHtmlError(error)) {
|
||||||
|
throw new Error(
|
||||||
|
'Expected a JSON response from the Git server but received an HTML page ' +
|
||||||
|
'(likely a login, SSO redirect, or proxy/error page). ' +
|
||||||
|
'Please check the server URL, your access token, and any network proxy or firewall.'
|
||||||
|
);
|
||||||
|
}
|
||||||
if (error instanceof Error) throw error;
|
if (error instanceof Error) throw error;
|
||||||
throw new Error(`Network error or unexpected failure: ${String(error)}`);
|
throw new Error(`Network error or unexpected failure: ${String(error)}`);
|
||||||
}
|
}
|
||||||
|
|
@ -102,6 +114,17 @@ export abstract class BaseGitService {
|
||||||
|
|
||||||
protected abstract addAuthHeader(headers: Record<string, string>): void;
|
protected abstract addAuthHeader(headers: Record<string, string>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects V8's JSON.parse error for a response starting with '<' (an HTML
|
||||||
|
* page), across its known message phrasings, e.g.:
|
||||||
|
* "Unexpected token '<', "<!DOCTYPE "... is not valid JSON"
|
||||||
|
* "Unexpected token < in JSON at position 0"
|
||||||
|
*/
|
||||||
|
private looksLikeJsonParseOfHtmlError(error: unknown): boolean {
|
||||||
|
if (!(error instanceof Error)) return false;
|
||||||
|
return /unexpected token/i.test(error.message) && /json/i.test(error.message) && error.message.includes('<');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safely parses a response body as JSON.
|
* Safely parses a response body as JSON.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,30 @@ describe('BaseGitService', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('safeRequest when requestUrl() itself throws a JSON-parse-of-HTML error', () => {
|
||||||
|
// Some Obsidian versions eagerly parse the response body as JSON inside
|
||||||
|
// requestUrl() itself, so a proxy/login HTML page can make the whole
|
||||||
|
// call reject with a raw SyntaxError — before `throw: false` or our own
|
||||||
|
// status/content-type checks ever get a response object to inspect.
|
||||||
|
it('wraps the raw "Unexpected token \'<\'... is not valid JSON" rejection with a clear message', async () => {
|
||||||
|
vi.mocked(requestUrl).mockRejectedValue(
|
||||||
|
new SyntaxError('Unexpected token \'<\', "<!DOCTYPE "... is not valid JSON')
|
||||||
|
);
|
||||||
|
await expect(service.listFiles('main')).rejects.toThrow(/received an HTML page/);
|
||||||
|
await expect(service.listFiles('main')).rejects.not.toThrow(/Unexpected token/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wraps the older V8 phrasing ("Unexpected token < in JSON at position 0") too', async () => {
|
||||||
|
vi.mocked(requestUrl).mockRejectedValue(new SyntaxError('Unexpected token < in JSON at position 0'));
|
||||||
|
await expect(service.listFiles('main')).rejects.toThrow(/received an HTML page/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not misclassify an unrelated JSON syntax error as an HTML response', async () => {
|
||||||
|
vi.mocked(requestUrl).mockRejectedValue(new SyntaxError('Unexpected end of JSON input'));
|
||||||
|
await expect(service.listFiles('main')).rejects.toThrow('Unexpected end of JSON input');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('parseJson with non-JSON (HTML) responses', () => {
|
describe('parseJson with non-JSON (HTML) responses', () => {
|
||||||
// Simulates Obsidian's real RequestUrlResponse.json getter, which throws
|
// Simulates Obsidian's real RequestUrlResponse.json getter, which throws
|
||||||
// SyntaxError when the body is not valid JSON (e.g. an HTML page).
|
// SyntaxError when the body is not valid JSON (e.g. an HTML page).
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue