mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:28:38 +00:00
refactor(extensions): dedupe channel config, onboarding, and monitors
This commit is contained in:
@@ -189,6 +189,16 @@ const voiceCallPlugin = {
|
||||
respond(false, { error: err instanceof Error ? err.message : String(err) });
|
||||
};
|
||||
|
||||
const resolveCallMessageRequest = async (params: GatewayRequestHandlerOptions["params"]) => {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
return { error: "callId and message required" } as const;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
return { rt, callId, message } as const;
|
||||
};
|
||||
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.initiate",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
@@ -228,14 +238,12 @@ const voiceCallPlugin = {
|
||||
"voicecall.continue",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
respond(false, { error: "callId and message required" });
|
||||
const request = await resolveCallMessageRequest(params);
|
||||
if ("error" in request) {
|
||||
respond(false, { error: request.error });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.continueCall(callId, message);
|
||||
const result = await request.rt.manager.continueCall(request.callId, request.message);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "continue failed" });
|
||||
return;
|
||||
@@ -251,14 +259,12 @@ const voiceCallPlugin = {
|
||||
"voicecall.speak",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
respond(false, { error: "callId and message required" });
|
||||
const request = await resolveCallMessageRequest(params);
|
||||
if ("error" in request) {
|
||||
respond(false, { error: request.error });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.speak(callId, message);
|
||||
const result = await request.rt.manager.speak(request.callId, request.message);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "speak failed" });
|
||||
return;
|
||||
|
||||
@@ -86,6 +86,18 @@ function twilioSignature(params: { authToken: string; url: string; postBody: str
|
||||
return crypto.createHmac("sha1", params.authToken).update(dataToSign).digest("base64");
|
||||
}
|
||||
|
||||
function expectReplayResultPair(
|
||||
first: { ok: boolean; isReplay?: boolean; verifiedRequestKey?: string },
|
||||
second: { ok: boolean; isReplay?: boolean; verifiedRequestKey?: string },
|
||||
) {
|
||||
expect(first.ok).toBe(true);
|
||||
expect(first.isReplay).toBeFalsy();
|
||||
expect(first.verifiedRequestKey).toBeTruthy();
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.isReplay).toBe(true);
|
||||
expect(second.verifiedRequestKey).toBe(first.verifiedRequestKey);
|
||||
}
|
||||
|
||||
describe("verifyPlivoWebhook", () => {
|
||||
it("accepts valid V2 signature", () => {
|
||||
const authToken = "test-auth-token";
|
||||
@@ -196,12 +208,7 @@ describe("verifyPlivoWebhook", () => {
|
||||
const first = verifyPlivoWebhook(ctx, authToken);
|
||||
const second = verifyPlivoWebhook(ctx, authToken);
|
||||
|
||||
expect(first.ok).toBe(true);
|
||||
expect(first.isReplay).toBeFalsy();
|
||||
expect(first.verifiedRequestKey).toBeTruthy();
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.isReplay).toBe(true);
|
||||
expect(second.verifiedRequestKey).toBe(first.verifiedRequestKey);
|
||||
expectReplayResultPair(first, second);
|
||||
});
|
||||
|
||||
it("returns a stable request key when verification is skipped", () => {
|
||||
@@ -245,12 +252,7 @@ describe("verifyTelnyxWebhook", () => {
|
||||
const first = verifyTelnyxWebhook(ctx, pemPublicKey);
|
||||
const second = verifyTelnyxWebhook(ctx, pemPublicKey);
|
||||
|
||||
expect(first.ok).toBe(true);
|
||||
expect(first.isReplay).toBeFalsy();
|
||||
expect(first.verifiedRequestKey).toBeTruthy();
|
||||
expect(second.ok).toBe(true);
|
||||
expect(second.isReplay).toBe(true);
|
||||
expect(second.verifiedRequestKey).toBe(first.verifiedRequestKey);
|
||||
expectReplayResultPair(first, second);
|
||||
});
|
||||
|
||||
it("returns a stable request key when verification is skipped", () => {
|
||||
|
||||
@@ -55,6 +55,21 @@ const createManager = (calls: CallRecord[]) => {
|
||||
return { manager, endCall, processEvent };
|
||||
};
|
||||
|
||||
async function postWebhookForm(server: VoiceCallWebhookServer, baseUrl: string, body: string) {
|
||||
const address = (
|
||||
server as unknown as { server?: { address?: () => unknown } }
|
||||
).server?.address?.();
|
||||
const requestUrl = new URL(baseUrl);
|
||||
if (address && typeof address === "object" && "port" in address && address.port) {
|
||||
requestUrl.port = String(address.port);
|
||||
}
|
||||
return await fetch(requestUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
describe("VoiceCallWebhookServer stale call reaper", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
@@ -146,18 +161,7 @@ describe("VoiceCallWebhookServer replay handling", () => {
|
||||
|
||||
try {
|
||||
const baseUrl = await server.start();
|
||||
const address = (
|
||||
server as unknown as { server?: { address?: () => unknown } }
|
||||
).server?.address?.();
|
||||
const requestUrl = new URL(baseUrl);
|
||||
if (address && typeof address === "object" && "port" in address && address.port) {
|
||||
requestUrl.port = String(address.port);
|
||||
}
|
||||
const response = await fetch(requestUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body: "CallSid=CA123&SpeechResult=hello",
|
||||
});
|
||||
const response = await postWebhookForm(server, baseUrl, "CallSid=CA123&SpeechResult=hello");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(processEvent).not.toHaveBeenCalled();
|
||||
@@ -193,18 +197,7 @@ describe("VoiceCallWebhookServer replay handling", () => {
|
||||
|
||||
try {
|
||||
const baseUrl = await server.start();
|
||||
const address = (
|
||||
server as unknown as { server?: { address?: () => unknown } }
|
||||
).server?.address?.();
|
||||
const requestUrl = new URL(baseUrl);
|
||||
if (address && typeof address === "object" && "port" in address && address.port) {
|
||||
requestUrl.port = String(address.port);
|
||||
}
|
||||
const response = await fetch(requestUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body: "CallSid=CA123&SpeechResult=hello",
|
||||
});
|
||||
const response = await postWebhookForm(server, baseUrl, "CallSid=CA123&SpeechResult=hello");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(parseWebhookEvent).toHaveBeenCalledTimes(1);
|
||||
@@ -231,18 +224,7 @@ describe("VoiceCallWebhookServer replay handling", () => {
|
||||
|
||||
try {
|
||||
const baseUrl = await server.start();
|
||||
const address = (
|
||||
server as unknown as { server?: { address?: () => unknown } }
|
||||
).server?.address?.();
|
||||
const requestUrl = new URL(baseUrl);
|
||||
if (address && typeof address === "object" && "port" in address && address.port) {
|
||||
requestUrl.port = String(address.port);
|
||||
}
|
||||
const response = await fetch(requestUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body: "CallSid=CA123&SpeechResult=hello",
|
||||
});
|
||||
const response = await postWebhookForm(server, baseUrl, "CallSid=CA123&SpeechResult=hello");
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
expect(parseWebhookEvent).not.toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user