Files
wpide-server/src/site-callback/client.ts
T

80 lines
2.3 KiB
TypeScript

/**
* HMAC-signed POSTs to the plugin's /wp-json/wp-ide/v1/tool-exec
* endpoint. The plugin issued the secret when it called POST /v1/runs;
* we use it to sign every tool callback so the plugin can verify the
* caller is us.
*/
import { createHmac } from 'node:crypto';
import { logger } from '../lib/logger.js';
export interface ToolExecRequest {
call_id: string;
name: string;
arguments: Record<string, unknown>;
}
export interface ToolExecResponse {
ok: boolean;
call_id: string;
result?: unknown;
error?: string;
}
export async function runToolOnSite(
callbackUrl: string,
runId: string,
secret: string,
payload: ToolExecRequest,
timeoutMs = 60_000,
): Promise<ToolExecResponse> {
const body = JSON.stringify(payload);
const signature = createHmac('sha256', secret).update(body).digest('hex');
const url = callbackUrl;
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), timeoutMs);
const started = Date.now();
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'accept-encoding': 'identity',
'user-agent': 'wpide-server/0.2',
'x-wpide-run-id': runId,
'x-wpide-signature': signature,
},
body,
signal: controller.signal,
});
const ms = Date.now() - started;
const text = await res.text();
let parsed: ToolExecResponse;
try {
parsed = JSON.parse(text) as ToolExecResponse;
} catch {
const respHeaders: Record<string, string> = {};
for (const [k, v] of res.headers.entries()) respHeaders[k] = v;
logger.error({
url,
status: res.status,
ms,
bodyLength: text.length,
body: text.slice(0, 500),
headers: respHeaders,
}, 'tool-exec: non-JSON response');
return { ok: false, call_id: payload.call_id, error: `tool-exec non-JSON (HTTP ${res.status})` };
}
if (!res.ok) {
logger.warn({ status: res.status, ms, error: parsed.error }, 'tool-exec returned error status');
}
return parsed;
} catch (err) {
return { ok: false, call_id: payload.call_id, error: `tool-exec fetch failed: ${(err as Error).message}` };
} finally {
clearTimeout(t);
}
}