Skip to content

Commit a3473fa

Browse files
committed
better handling of version
1 parent 600d749 commit a3473fa

File tree

4 files changed

+80
-5
lines changed

4 files changed

+80
-5
lines changed

src/common/EnvConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface EnvConfig {
3535
HEALTH_CHECK_PATH: string;
3636
/** [requester] Optional health check Host header override */
3737
HEALTH_CHECK_HOST: string;
38+
/** [requester] Provider retry interval in seconds when version check fails (default: 600 = 10 minutes) */
39+
PROVIDER_RETRY_INTERVAL: number;
3840
}
3941

4042
/**
@@ -70,6 +72,7 @@ export const config: EnvConfig = {
7072
ROUTE_REFRESH_INTERVAL: parseInt(process.env.ROUTE_REFRESH_INTERVAL || '300', 10),
7173
HEALTH_CHECK_PATH: process.env.HEALTH_CHECK_PATH || '',
7274
HEALTH_CHECK_HOST: process.env.HEALTH_CHECK_HOST || '',
75+
PROVIDER_RETRY_INTERVAL: parseInt(process.env.PROVIDER_RETRY_INTERVAL || '600', 10),
7376
};
7477

7578
/**

src/requester/ProviderTools.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,36 @@ export async function waitForProvider(providerURL: string, retryIntervalSeconds:
7979
}
8080

8181
throw new Error(`Provider ${providerURL} not available after ${maxRetries} attempts`);
82+
}
83+
84+
export interface ProviderVersionInfo {
85+
compatible: boolean;
86+
version: number;
87+
error?: string;
88+
}
89+
90+
/**
91+
* Checks the provider backend API version for compatibility.
92+
* Returns version info indicating if the backend is compatible (v2+).
93+
* Used for graceful migration - clients wait if backend is outdated.
94+
*/
95+
export async function checkProviderVersion(providerURL: string): Promise<ProviderVersionInfo> {
96+
try {
97+
const curlCommand = `curl -s -k --max-time 10 ${providerURL}/router/api/version`;
98+
const { stdout } = await execPromise(curlCommand);
99+
const data = JSON.parse(stdout);
100+
101+
if (typeof data.version !== 'number') {
102+
return { compatible: false, version: 0, error: 'Invalid version response' };
103+
}
104+
105+
const compatible = data.version >= 2;
106+
return { compatible, version: data.version };
107+
} catch (error) {
108+
return {
109+
compatible: false,
110+
version: 0,
111+
error: error instanceof Error ? error.message : 'Version check failed'
112+
};
113+
}
82114
}

src/requester/Requester.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {promisify} from 'util';
66
import {Config} from "./RequesterConfig.js";
77
import {HandshakesWatcher} from './HandshakesWatcher.js';
88
import {getConfigPath} from "./WireGuard.js";
9-
import {ProviderTools, registerProvider, waitForProvider} from "./ProviderTools.js";
9+
import {ProviderTools, registerProvider, waitForProvider, checkProviderVersion} from "./ProviderTools.js";
10+
import {config} from "../common/EnvConfig.js";
1011
import {getOrGenerateKeyPair} from "./KeyPair.js";
1112
import {registerTunnelRoute, startRouteRefreshLoop, stopRouteRefreshLoop} from "./RouteRegistrar.js";
1213

@@ -103,10 +104,24 @@ async function stopRequester(providerString: string) {
103104
}
104105

105106
async function startRequester(provider:ProviderTools) {
107+
const [providerURL, userId = '', signature = ''] = provider.provider.split(',');
108+
console.log(`Starting requester for ${providerURL}`);
109+
110+
// Check version FIRST (before waitForProvider)
111+
console.log('Checking provider version...');
112+
const versionInfo = await checkProviderVersion(providerURL);
113+
114+
if (!versionInfo.compatible) {
115+
console.warn(`Provider version incompatible (v${versionInfo.version}). Requires v2+. ${versionInfo.error || ''}`);
116+
console.warn(`Will retry in ${config.PROVIDER_RETRY_INTERVAL}s...`);
117+
118+
// Schedule retry
119+
setTimeout(() => startRequester(provider), config.PROVIDER_RETRY_INTERVAL * 1000);
120+
return;
121+
}
122+
console.log(`Provider version: v${versionInfo.version} (compatible)`);
106123

107124
try {
108-
const [providerURL, userId = '', signature = ''] = provider.provider.split(',');
109-
console.log(`Starting requester for ${providerURL}`);
110125
// Wait for provider to become available
111126
await waitForProvider(providerURL, RETRY_INTERVAL_SECONDS);
112127

src/requester/RouteRegistrar.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,35 @@ export async function registerTunnelRoute(
8585

8686
const url = `${backendUrl}/router/api/routes/${encodeURIComponent(userId)}/${encodeURIComponent(signature)}`;
8787
const jsonData = JSON.stringify({ routes: [route] }).replace(/"/g, '\\"');
88-
const curlCommand = `curl -s -X POST -H "Content-Type: application/json" -d "${jsonData}" "${url}"`;
88+
const curlCommand = `curl -s -w "\\n%{http_code}" -X POST -H "Content-Type: application/json" -d "${jsonData}" "${url}"`;
8989

9090
const { stdout } = await exec(curlCommand);
91-
const response = JSON.parse(stdout);
91+
92+
// Parse response and HTTP status code
93+
const lines = stdout.trim().split('\n');
94+
const httpCode = parseInt(lines.pop() || '0', 10);
95+
const body = lines.join('\n');
96+
97+
// Check for HTTP errors
98+
if (httpCode >= 400 || httpCode === 0) {
99+
return {
100+
success: false,
101+
message: 'Route registration failed',
102+
error: `HTTP ${httpCode}: Server returned an error (endpoint may not exist on this backend version)`,
103+
};
104+
}
105+
106+
// Parse JSON response
107+
let response;
108+
try {
109+
response = JSON.parse(body);
110+
} catch {
111+
return {
112+
success: false,
113+
message: 'Route registration failed',
114+
error: `Invalid JSON response from server (backend may not support routes API)`,
115+
};
116+
}
92117

93118
if (response.error) {
94119
return {

0 commit comments

Comments
 (0)