From 2a1d71da5c4c4cf172cc7edd149dedf28976f7fd Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 24 Mar 2025 12:04:58 +0900 Subject: [PATCH] Improved: show details of `TypeError` using Obsidian API. --- .../essentialObsidian/ModuleObsidianAPI.ts | 189 +++++++++++------- 1 file changed, 112 insertions(+), 77 deletions(-) diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index 1ab1532..0603ff5 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -1,5 +1,5 @@ import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts"; -import { LOG_LEVEL_DEBUG, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; +import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; import { type EntryDoc, type FilePathWithPrefix } from "../../lib/src/common/types.ts"; import { getPathFromTFile } from "../../common/utils.ts"; @@ -47,6 +47,58 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi return !this.last_successful_post; } + async fetchByAPI( + url: string, + localURL: string, + method: string, + authHeader: string, + opts?: RequestInit + ): Promise { + const body = opts?.body as string; + + const transformedHeaders = { ...(opts?.headers as Record) }; + if (authHeader != "") transformedHeaders["authorization"] = authHeader; + delete transformedHeaders["host"]; + delete transformedHeaders["Host"]; + delete transformedHeaders["content-length"]; + delete transformedHeaders["Content-Length"]; + const requestParam: RequestUrlParam = { + url, + method: opts?.method, + body: body, + headers: transformedHeaders, + contentType: "application/json", + // contentType: opts.headers, + }; + const size = body ? ` (${body.length})` : ""; + try { + this.plugin.requestCount.value = this.plugin.requestCount.value + 1; + const r = await fetchByAPI(requestParam); + if (method == "POST" || method == "PUT") { + this.last_successful_post = r.status - (r.status % 100) == 200; + } else { + this.last_successful_post = true; + } + this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG); + + return new Response(r.arrayBuffer, { + headers: r.headers, + status: r.status, + statusText: `${r.status}`, + }); + } catch (ex) { + this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); + // limit only in bulk_docs. + if (url.toString().indexOf("_bulk_docs") !== -1) { + this.last_successful_post = false; + } + this._log(ex); + throw ex; + } finally { + this.plugin.responseCount.value = this.plugin.responseCount.value + 1; + } + } + async $$connectRemoteCouchDB( uri: string, auth: { username: string; password: string }, @@ -87,88 +139,74 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi } size = ` (${opts_length})`; } - if (!disableRequestURI && typeof url == "string" && typeof (opts?.body ?? "") == "string") { - const body = opts?.body as string; - - const transformedHeaders = { ...(opts?.headers as Record) }; - if (authHeader != "") transformedHeaders["authorization"] = authHeader; - delete transformedHeaders["host"]; - delete transformedHeaders["Host"]; - delete transformedHeaders["content-length"]; - delete transformedHeaders["Content-Length"]; - const requestParam: RequestUrlParam = { - url, - method: opts?.method, - body: body, - headers: transformedHeaders, - contentType: "application/json", - // contentType: opts.headers, - }; + try { + if (!disableRequestURI && typeof url == "string" && typeof (opts?.body ?? "") == "string") { + return await this.fetchByAPI(url, localURL, method, authHeader, opts); + } + // --> native Fetch API. try { + if (this.settings.enableDebugTools) { + // Issue #407 + (opts!.headers as Headers).append("ngrok-skip-browser-warning", "123"); + } this.plugin.requestCount.value = this.plugin.requestCount.value + 1; - const r = await fetchByAPI(requestParam); + const response: Response = await fetch(url, opts); if (method == "POST" || method == "PUT") { - this.last_successful_post = r.status - (r.status % 100) == 200; + this.last_successful_post = response.ok; } else { this.last_successful_post = true; } - this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG); - - return new Response(r.arrayBuffer, { - headers: r.headers, - status: r.status, - statusText: `${r.status}`, - }); - } catch (ex) { - this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); - // limit only in bulk_docs. - if (url.toString().indexOf("_bulk_docs") !== -1) { - this.last_successful_post = false; - } - this._log(ex); - throw ex; - } finally { - this.plugin.responseCount.value = this.plugin.responseCount.value + 1; - } - } - - try { - if (this.settings.enableDebugTools) { - // Issue #407 - (opts!.headers as Headers).append("ngrok-skip-browser-warning", "123"); - } - this.plugin.requestCount.value = this.plugin.requestCount.value + 1; - const response: Response = await fetch(url, opts); - if (method == "POST" || method == "PUT") { - this.last_successful_post = response.ok; - } else { - this.last_successful_post = true; - } - this._log(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG); - if (Math.floor(response.status / 100) !== 2) { - if (method != "GET" && localURL.indexOf("/_local/") === -1 && !localURL.endsWith("/")) { - const r = response.clone(); - this._log( - `The request may have failed. The reason sent by the server: ${r.status}: ${r.statusText}` - ); - - try { - this._log(await (await r.blob()).text(), LOG_LEVEL_VERBOSE); - } catch (_) { - this._log("Cloud not parse response", LOG_LEVEL_VERBOSE); - this._log(_, LOG_LEVEL_VERBOSE); + this._log(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG); + if (Math.floor(response.status / 100) !== 2) { + if (response.status == 404) { + if (method === "GET" && localURL.indexOf("/_local/") === -1) { + this._log( + `Just checkpoint or some server information has been missing. The 404 error shown above is not an error.`, + LOG_LEVEL_VERBOSE + ); + } + } else { + const r = response.clone(); + this._log( + `The request may have failed. The reason sent by the server: ${r.status}: ${r.statusText}`, + LOG_LEVEL_NOTICE + ); + try { + const result = await r.text(); + this._log(result, LOG_LEVEL_VERBOSE); + } catch (_) { + this._log("Cloud not fetch response body", LOG_LEVEL_VERBOSE); + this._log(_, LOG_LEVEL_VERBOSE); + } } - } else { - this._log( - `Just checkpoint or some server information has been missing. The 404 error shown above is not an error.`, - LOG_LEVEL_VERBOSE - ); } + return response; + } catch (ex) { + if (ex instanceof TypeError) { + this._log( + "Failed to fetch by native fetch API. Trying to fetch by API to get more information." + ); + const resp2 = await this.fetchByAPI(url.toString(), localURL, method, authHeader, opts); + if (resp2.status / 100 == 2) { + this._log( + "The request was successful by API. But the native fetch API failed! Please check CORS settings on the remote database!. While this condition, you cannot enable LiveSync", + LOG_LEVEL_NOTICE + ); + return resp2; + } + const r2 = resp2.clone(); + const msg = await r2.text(); + this._log(`Failed to fetch by API. ${resp2.status}: ${msg}`, LOG_LEVEL_NOTICE); + return resp2; + } + throw ex; } - return response; - } catch (ex) { + } catch (ex: any) { this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); + const msg = ex instanceof Error ? `${ex?.name}:${ex?.message}` : ex?.toString(); + this._log(`Failed to fetch: ${msg}`, LOG_LEVEL_NOTICE); + this._log(ex, LOG_LEVEL_VERBOSE); // limit only in bulk_docs. if (url.toString().indexOf("_bulk_docs") !== -1) { this.last_successful_post = false; @@ -178,6 +216,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi } finally { this.plugin.responseCount.value = this.plugin.responseCount.value + 1; } + // return await fetch(url, opts); }, }; @@ -195,11 +234,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi const info = await db.info(); return { db: db, info: info }; } catch (ex: any) { - let msg = `${ex?.name}:${ex?.message}`; - if (ex?.name == "TypeError" && ex?.message == "Failed to fetch") { - msg += - "\n**Note** This error caused by many reasons. The only sure thing is you didn't touch the server.\nTo check details, open inspector."; - } + const msg = `${ex?.name}:${ex?.message}`; this._log(ex, LOG_LEVEL_VERBOSE); return msg; }