diff --git a/httpd-client/index.ts b/httpd-client/index.ts index f2328f436..7cf0e39aa 100644 --- a/httpd-client/index.ts +++ b/httpd-client/index.ts @@ -103,6 +103,7 @@ const nodeInfoSchema = object({ message: string(), service: string(), version: string(), + apiVersion: string(), nid: string(), path: string(), links: array( diff --git a/httpd-client/lib/fetcher.ts b/httpd-client/lib/fetcher.ts index 7c8157065..82ef422d5 100644 --- a/httpd-client/lib/fetcher.ts +++ b/httpd-client/lib/fetcher.ts @@ -3,6 +3,9 @@ import type { ZodIssue, ZodType, TypeOf } from "zod"; +import { config } from "@app/lib/config"; +import { compare } from "compare-versions"; + export interface BaseUrl { hostname: string; port: number; @@ -40,16 +43,41 @@ export class ResponseError extends Error { export class ResponseParseError extends Error { public method: string; public body: unknown; + public description: string; + public apiVersion: string | undefined; public zodIssues: ZodIssue[]; public path?: string; public constructor( method: string, body: unknown, + apiVersion: string | undefined, zodIssues: ZodIssue[], path?: string, ) { super("Failed to parse response body"); + + let description: string; + if ( + apiVersion === undefined || + compare(apiVersion, config.nodes.apiVersion, "<") + ) { + description = `The node you are fetching from seems to be outdated, make sure the httpd API version is at least ${config.nodes.apiVersion} currently ${apiVersion ?? "unknown"}.`; + } else if ( + config.nodes.apiVersion === undefined || + compare(apiVersion, config.nodes.apiVersion, ">") + ) { + description = `The web client you are using is outdated, make sure it supports at least ${apiVersion} to interact with this node currently ${config.nodes.apiVersion ?? "unknown"}.`; + } else { + description = + "This is usually due to a version mismatch between the seed and the web interface."; + } + this.apiVersion = apiVersion; + this.description = + "The response received from the seed does not match the expected schema.
".concat( + description, + ); + this.method = method; this.path = path; this.body = body; @@ -93,8 +121,7 @@ export class Fetcher { ): Promise> { const response = await this.fetch({ ...params, - // eslint-disable-next-line @typescript-eslint/naming-convention - query: { ...params.query, bust_cache: "1" }, + query: { ...params.query, v: config.nodes.apiVersion }, }); if (!response.ok) { @@ -112,9 +139,12 @@ export class Fetcher { if (result.success) { return result.data; } else { + const response = await this.fetch({ method: "GET" }); + const info = await response.json(); throw new ResponseParseError( params.method, responseBody, + info.apiVersion, result.error.errors, params.path, ); diff --git a/package-lock.json b/package-lock.json index 21aab0072..f702e4987 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "baconjs": "^3.0.17", "bs58": "^5.0.0", "buffer": "^6.0.3", + "compare-versions": "^6.1.0", "dompurify": "^3.0.11", "hast-util-to-dom": "^4.0.0", "hast-util-to-html": "^9.0.0", @@ -1880,6 +1881,11 @@ "node": ">= 12" } }, + "node_modules/compare-versions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index 7fe2611bf..2bfd66f67 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "baconjs": "^3.0.17", "bs58": "^5.0.0", "buffer": "^6.0.3", + "compare-versions": "^6.1.0", "dompurify": "^3.0.11", "hast-util-to-dom": "^4.0.0", "hast-util-to-html": "^9.0.0", diff --git a/src/config.json b/src/config.json index 046bb09fe..e03c1bec9 100644 --- a/src/config.json +++ b/src/config.json @@ -1,6 +1,7 @@ { "nodes": { "fallbackPublicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path", + "apiVersion": "0.1.0", "defaultHttpdPort": 443, "defaultLocalHttpdPort": 8080, "defaultHttpdHostname": "seed.radicle.garden", diff --git a/src/lib/config.ts b/src/lib/config.ts index f86c5b0dd..b6564656e 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -4,6 +4,7 @@ import configJson from "@app/config.json"; export interface Config { nodes: { + apiVersion: string; fallbackPublicExplorer: string; defaultHttpdPort: number; defaultHttpdHostname: string; @@ -21,6 +22,7 @@ function getConfig(): Config { return { nodes: { fallbackPublicExplorer: "https://app.radicle.xyz/nodes/$host/$rid$path", + apiVersion: "0.1.0", defaultHttpdHostname: "127.0.0.1", defaultHttpdPort: 8081, defaultLocalHttpdPort: 8081, diff --git a/src/views/home/error.ts b/src/views/home/error.ts index 5a6af1e2f..d14fd7899 100644 --- a/src/views/home/error.ts +++ b/src/views/home/error.ts @@ -12,8 +12,7 @@ export function handleError( return { error, title: "Could not parse the request", - description: - "The response received from the seed does not match the expected schema, this is usually due to a version mismatch between the seed and the web interface.", + description: error.description, }; } else if (error instanceof ResponseError) { return { diff --git a/src/views/nodes/error.ts b/src/views/nodes/error.ts index 0489cb90b..81b6ce789 100644 --- a/src/views/nodes/error.ts +++ b/src/views/nodes/error.ts @@ -11,8 +11,7 @@ export function handleError( params: { error, title: "Could not parse the request", - description: - "The response received from the seed does not match the expected schema, this is usually due to a version mismatch between the seed and the web interface.", + description: error.description, }, }; } else if (error instanceof ResponseError) { diff --git a/src/views/projects/error.ts b/src/views/projects/error.ts index 95c1e81ea..fae5df875 100644 --- a/src/views/projects/error.ts +++ b/src/views/projects/error.ts @@ -43,8 +43,7 @@ export function handleError( params: { error, title: "Could not parse the request", - description: - "The response received from the seed does not match the expected schema, this is usually due to a version mismatch between the seed and the web interface.", + description: error.description, }, }; } else if ( diff --git a/tests/support/fixtures.ts b/tests/support/fixtures.ts index bbd890e24..bf4d76441 100644 --- a/tests/support/fixtures.ts +++ b/tests/support/fixtures.ts @@ -77,6 +77,7 @@ export const test = base.extend<{ nodes: { fallbackPublicExplorer: "https://app.radicle.xyz/nodes/$host/$rid$path", + apiVersion: "0.1.0", defaultHttpdPort: 8081, defaultHttpdHostname: "127.0.0.1", defaultLocalHttpdPort: 8081, @@ -214,6 +215,7 @@ export function appConfigWithFixture(defaultLocalHttpdPort = 8081) { window.APP_CONFIG = { nodes: { fallbackPublicExplorer: "https://app.radicle.xyz/nodes/$host/$rid$path", + apiVersion: "0.1.0", defaultHttpdPort: 8081, defaultHttpdHostname: "127.0.0.1", defaultLocalHttpdPort,