diff --git a/src/services/git-service-base.ts b/src/services/git-service-base.ts index 022d4ca..8eefab4 100644 --- a/src/services/git-service-base.ts +++ b/src/services/git-service-base.ts @@ -78,8 +78,20 @@ export abstract class BaseGitService { response = await requestUrl(options); } 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 (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; throw new Error(`Network error or unexpected failure: ${String(error)}`); } @@ -102,6 +114,17 @@ export abstract class BaseGitService { protected abstract addAuthHeader(headers: Record): void; + /** + * Detects V8's JSON.parse error for a response starting with '<' (an HTML + * page), across its known message phrasings, e.g.: + * "Unexpected token '<', " { }); }); + 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 \'<\', " { + 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', () => { // Simulates Obsidian's real RequestUrlResponse.json getter, which throws // SyntaxError when the body is not valid JSON (e.g. an HTML page).