fix: Host API port conflict crashing startup on Windows (#743)
This commit is contained in:
committed by
GitHub
Unverified
parent
06266cb4d2
commit
7e2c4d3835
@@ -43,7 +43,7 @@ const routeHandlers: RouteHandler[] = [
|
|||||||
* Per-session secret token used to authenticate Host API requests.
|
* Per-session secret token used to authenticate Host API requests.
|
||||||
* Generated once at server start and shared with the renderer via IPC.
|
* Generated once at server start and shared with the renderer via IPC.
|
||||||
* This prevents cross-origin attackers from reading sensitive data even
|
* This prevents cross-origin attackers from reading sensitive data even
|
||||||
* if they can reach 127.0.0.1:3210 (the CORS wildcard alone is not
|
* if they can reach 127.0.0.1:13210 (the CORS wildcard alone is not
|
||||||
* sufficient because browsers attach the Origin header but not a secret).
|
* sufficient because browsers attach the Origin header but not a secret).
|
||||||
*/
|
*/
|
||||||
let hostApiToken: string = '';
|
let hostApiToken: string = '';
|
||||||
@@ -106,6 +106,18 @@ export function startHostApiServer(ctx: HostApiContext, port = getPort('CLAWX_HO
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on('error', (error: NodeJS.ErrnoException) => {
|
||||||
|
if (error.code === 'EACCES' || error.code === 'EADDRINUSE') {
|
||||||
|
logger.error(
|
||||||
|
`Host API server failed to bind port ${port}: ${error.message}. ` +
|
||||||
|
'On Windows this is often caused by Hyper-V reserving the port range. ' +
|
||||||
|
`Set CLAWX_PORT_CLAWX_HOST_API env var to override the default port.`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.error('Host API server error:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.listen(port, '127.0.0.1', () => {
|
server.listen(port, '127.0.0.1', () => {
|
||||||
logger.info(`Host API server listening on http://127.0.0.1:${port}`);
|
logger.info(`Host API server listening on http://127.0.0.1:${port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const PORTS = {
|
|||||||
CLAWX_GUI: 23333,
|
CLAWX_GUI: 23333,
|
||||||
|
|
||||||
/** Local host API server port */
|
/** Local host API server port */
|
||||||
CLAWX_HOST_API: 3210,
|
CLAWX_HOST_API: 13210,
|
||||||
|
|
||||||
/** OpenClaw Gateway port */
|
/** OpenClaw Gateway port */
|
||||||
OPENCLAW_GATEWAY: 18789,
|
OPENCLAW_GATEWAY: 18789,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { invokeIpc } from '@/lib/api-client';
|
|||||||
import { trackUiEvent } from './telemetry';
|
import { trackUiEvent } from './telemetry';
|
||||||
import { normalizeAppError } from './error-model';
|
import { normalizeAppError } from './error-model';
|
||||||
|
|
||||||
const HOST_API_PORT = 3210;
|
const HOST_API_PORT = 13210;
|
||||||
const HOST_API_BASE = `http://127.0.0.1:${HOST_API_PORT}`;
|
const HOST_API_BASE = `http://127.0.0.1:${HOST_API_PORT}`;
|
||||||
|
|
||||||
/** Cached Host API auth token, fetched once from the main process via IPC. */
|
/** Cached Host API auth token, fetched once from the main process via IPC. */
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ describe('handleAppRoutes', () => {
|
|||||||
const handled = await handleAppRoutes(
|
const handled = await handleAppRoutes(
|
||||||
{ method: 'POST' } as IncomingMessage,
|
{ method: 'POST' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/app/openclaw-doctor'),
|
new URL('http://127.0.0.1:13210/api/app/openclaw-doctor'),
|
||||||
{} as never,
|
{} as never,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ describe('handleAppRoutes', () => {
|
|||||||
const handled = await handleAppRoutes(
|
const handled = await handleAppRoutes(
|
||||||
{ method: 'POST' } as IncomingMessage,
|
{ method: 'POST' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/app/openclaw-doctor'),
|
new URL('http://127.0.0.1:13210/api/app/openclaw-doctor'),
|
||||||
{} as never,
|
{} as never,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
const handled = await handleChannelRoutes(
|
const handled = await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/accounts'),
|
new URL('http://127.0.0.1:13210/api/channels/accounts'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc,
|
rpc,
|
||||||
@@ -241,7 +241,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
await handleChannelRoutes(
|
await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/accounts'),
|
new URL('http://127.0.0.1:13210/api/channels/accounts'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc,
|
rpc,
|
||||||
@@ -296,7 +296,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
const handled = await handleChannelRoutes(
|
const handled = await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/targets?channelType=qqbot&accountId=default'),
|
new URL('http://127.0.0.1:13210/api/channels/targets?channelType=qqbot&accountId=default'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc: vi.fn(),
|
rpc: vi.fn(),
|
||||||
@@ -410,7 +410,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
const handled = await handleChannelRoutes(
|
const handled = await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/targets?channelType=feishu&accountId=default'),
|
new URL('http://127.0.0.1:13210/api/channels/targets?channelType=feishu&accountId=default'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc: vi.fn(),
|
rpc: vi.fn(),
|
||||||
@@ -469,7 +469,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
const handled = await handleChannelRoutes(
|
const handled = await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/targets?channelType=wecom&accountId=default'),
|
new URL('http://127.0.0.1:13210/api/channels/targets?channelType=wecom&accountId=default'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc: vi.fn(),
|
rpc: vi.fn(),
|
||||||
@@ -519,7 +519,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
const handled = await handleChannelRoutes(
|
const handled = await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/targets?channelType=dingtalk&accountId=default'),
|
new URL('http://127.0.0.1:13210/api/channels/targets?channelType=dingtalk&accountId=default'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc: vi.fn(),
|
rpc: vi.fn(),
|
||||||
@@ -571,7 +571,7 @@ describe('handleChannelRoutes', () => {
|
|||||||
const handled = await handleChannelRoutes(
|
const handled = await handleChannelRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/channels/targets?channelType=wechat&accountId=wechat-bot'),
|
new URL('http://127.0.0.1:13210/api/channels/targets?channelType=wechat&accountId=wechat-bot'),
|
||||||
{
|
{
|
||||||
gatewayManager: {
|
gatewayManager: {
|
||||||
rpc: vi.fn(),
|
rpc: vi.fn(),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe('handleCronRoutes', () => {
|
|||||||
const handled = await handleCronRoutes(
|
const handled = await handleCronRoutes(
|
||||||
{ method: 'POST' } as IncomingMessage,
|
{ method: 'POST' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/cron/jobs'),
|
new URL('http://127.0.0.1:13210/api/cron/jobs'),
|
||||||
{
|
{
|
||||||
gatewayManager: { rpc },
|
gatewayManager: { rpc },
|
||||||
} as never,
|
} as never,
|
||||||
@@ -89,7 +89,7 @@ describe('handleCronRoutes', () => {
|
|||||||
await handleCronRoutes(
|
await handleCronRoutes(
|
||||||
{ method: 'PUT' } as IncomingMessage,
|
{ method: 'PUT' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/cron/jobs/job-2'),
|
new URL('http://127.0.0.1:13210/api/cron/jobs/job-2'),
|
||||||
{
|
{
|
||||||
gatewayManager: { rpc },
|
gatewayManager: { rpc },
|
||||||
} as never,
|
} as never,
|
||||||
@@ -139,7 +139,7 @@ describe('handleCronRoutes', () => {
|
|||||||
await handleCronRoutes(
|
await handleCronRoutes(
|
||||||
{ method: 'PUT' } as IncomingMessage,
|
{ method: 'PUT' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/cron/jobs/job-account'),
|
new URL('http://127.0.0.1:13210/api/cron/jobs/job-account'),
|
||||||
{
|
{
|
||||||
gatewayManager: { rpc },
|
gatewayManager: { rpc },
|
||||||
} as never,
|
} as never,
|
||||||
@@ -178,7 +178,7 @@ describe('handleCronRoutes', () => {
|
|||||||
const handled = await handleCronRoutes(
|
const handled = await handleCronRoutes(
|
||||||
{ method: 'POST' } as IncomingMessage,
|
{ method: 'POST' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/cron/jobs'),
|
new URL('http://127.0.0.1:13210/api/cron/jobs'),
|
||||||
{
|
{
|
||||||
gatewayManager: { rpc },
|
gatewayManager: { rpc },
|
||||||
} as never,
|
} as never,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ describe('host-api', () => {
|
|||||||
|
|
||||||
expect(result.fallback).toBe(true);
|
expect(result.fallback).toBe(true);
|
||||||
expect(fetchMock).toHaveBeenCalledWith(
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
'http://127.0.0.1:3210/api/test',
|
'http://127.0.0.1:13210/api/test',
|
||||||
expect.objectContaining({ headers: expect.any(Object) }),
|
expect.objectContaining({ headers: expect.any(Object) }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -97,7 +97,7 @@ describe('host-api', () => {
|
|||||||
|
|
||||||
expect(result.fallback).toBe(true);
|
expect(result.fallback).toBe(true);
|
||||||
expect(fetchMock).toHaveBeenCalledWith(
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
'http://127.0.0.1:3210/api/test',
|
'http://127.0.0.1:13210/api/test',
|
||||||
expect.objectContaining({ headers: expect.any(Object) }),
|
expect.objectContaining({ headers: expect.any(Object) }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('handleUsageRoutes', () => {
|
|||||||
const handled = await handleUsageRoutes(
|
const handled = await handleUsageRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/usage/recent-token-history'),
|
new URL('http://127.0.0.1:13210/api/usage/recent-token-history'),
|
||||||
{} as never,
|
{} as never,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ describe('handleUsageRoutes', () => {
|
|||||||
await handleUsageRoutes(
|
await handleUsageRoutes(
|
||||||
{ method: 'GET' } as IncomingMessage,
|
{ method: 'GET' } as IncomingMessage,
|
||||||
{} as ServerResponse,
|
{} as ServerResponse,
|
||||||
new URL('http://127.0.0.1:3210/api/usage/recent-token-history?limit=50.9'),
|
new URL('http://127.0.0.1:13210/api/usage/recent-token-history?limit=50.9'),
|
||||||
{} as never,
|
{} as never,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user