feat: implement telemetry system for application usage tracking

- Added telemetry utility to capture application events and metrics.
- Integrated PostHog for event tracking with distinct user identification.
- Implemented telemetry initialization, event capturing, and shutdown procedures.

feat: add UV environment setup for Python management

- Created utilities to manage Python installation and configuration.
- Implemented network optimization checks for Python installation mirrors.
- Added functions to set up managed Python environments with error handling.

feat: enhance host API communication with token management

- Introduced host API token retrieval and management for secure requests.
- Updated host API fetch functions to include token in headers.
- Added support for creating event sources with authentication.

test: add comprehensive tests for gateway protocol and startup helpers

- Implemented unit tests for gateway protocol helpers, event dispatching, and state management.
- Added tests for startup recovery strategies and process policies.
- Ensured coverage for connection monitoring and restart governance logic.
This commit is contained in:
DEV_DSW
2026-04-23 17:21:57 +08:00
parent 655e7c51d2
commit 71bcc3b3c5
39 changed files with 5504 additions and 313 deletions

158
electron/api/server.ts Normal file
View File

@@ -0,0 +1,158 @@
import { randomBytes } from 'node:crypto';
import { createServer, type Server } from 'node:http';
import log from 'electron-log';
import type { HostApiContext } from './context';
import type { HostApiRequest } from './route-utils';
import {
parseRawJsonBody,
requireJsonContentType,
sendJsonResponse,
setCorsHeaders,
} from './route-utils';
const DEFAULT_HOST_API_PORT = 13210;
type StartHostApiServerOptions = {
ctx: HostApiContext;
dispatchRequest: (request: HostApiRequest) => Promise<unknown | null>;
fallbackRequest?: (request: HostApiRequest) => Promise<unknown>;
port?: number;
};
let hostApiToken = '';
export function getHostApiPort(): number {
const raw = process.env['ZN_AI_HOST_API_PORT'];
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_HOST_API_PORT;
}
export function getHostApiToken(): string {
return hostApiToken;
}
export function getHostApiBase(): string {
return `http://127.0.0.1:${getHostApiPort()}`;
}
export function startHostApiServer(options: StartHostApiServerOptions): Server {
const port = options.port ?? getHostApiPort();
hostApiToken = randomBytes(32).toString('hex');
const server = createServer(async (req, res) => {
try {
const requestUrl = new URL(req.url || '/', `http://127.0.0.1:${port}`);
setCorsHeaders(res, req.headers.origin);
if (req.method === 'OPTIONS') {
res.statusCode = 204;
res.end();
return;
}
const bearerHeader = req.headers.authorization || '';
const bearerToken = bearerHeader.startsWith('Bearer ')
? bearerHeader.slice('Bearer '.length)
: '';
const token = (
req.headers['x-host-api-token']
|| requestUrl.searchParams.get('token')
|| bearerToken
);
if (token !== hostApiToken) {
sendJsonResponse(res, 401, {
success: false,
ok: false,
error: 'Unauthorized',
});
return;
}
if (requestUrl.pathname === '/api/events' && req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'text/event-stream; charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
});
res.write(': connected\n\n');
options.ctx.eventBus.addSseClient(res);
res.write(`event: gateway:status\ndata: ${JSON.stringify(options.ctx.gatewayManager.getStatus())}\n\n`);
return;
}
if (!requireJsonContentType(req)) {
sendJsonResponse(res, 415, {
success: false,
ok: false,
error: 'Content-Type must be application/json',
});
return;
}
const body = req.method && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)
? await parseRawJsonBody<unknown>(req)
: null;
const forwardedHeaders: Record<string, string> = {};
Object.entries(req.headers).forEach(([key, value]) => {
if (typeof value === 'string') {
forwardedHeaders[key] = value;
}
});
delete forwardedHeaders['x-host-api-token'];
const request: HostApiRequest = {
path: `${requestUrl.pathname}${requestUrl.search}`,
method: req.method,
headers: forwardedHeaders,
body,
};
const localResult = await options.dispatchRequest(request);
const response = localResult ?? (
options.fallbackRequest
? await options.fallbackRequest(request)
: null
);
if (response == null) {
sendJsonResponse(res, 404, {
success: false,
ok: false,
error: `No route for ${req.method} ${requestUrl.pathname}`,
});
return;
}
const result = response as {
status?: number;
data?: unknown;
json?: unknown;
success?: boolean;
ok?: boolean;
error?: string;
text?: string;
};
sendJsonResponse(res, result.status ?? 200, response);
} catch (error) {
sendJsonResponse(res, 500, {
success: false,
ok: false,
error: error instanceof Error ? error.message : String(error),
});
}
});
server.on('error', (error) => {
log.error('Host API server failed:', error);
});
server.on('close', () => {
hostApiToken = '';
});
server.listen(port, '127.0.0.1');
return server;
}