fix(nextcloud-talk): wrap malformed api json · openclaw/openclaw@376a792
vincentkoc
·
2026-05-15
·
via Recent Commits to openclaw:main
File tree
extensions/nextcloud-talk/src
| Original file line number | Diff line number | Diff line change |
|---|
@@ -84,6 +84,7 @@ Docs: https://docs.openclaw.ai
|
84 | 84 | - DeepInfra video: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures. |
85 | 85 | - Brave Search: report malformed web and LLM-context API JSON with provider-owned errors instead of leaking raw parser failures. |
86 | 86 | - xAI tools: report malformed web search, X search, and code execution JSON with provider-owned errors instead of leaking raw parser failures. |
| 87 | +- Nextcloud Talk: report malformed room-info and bot-admin JSON with channel-owned errors instead of leaking raw parser failures. |
87 | 88 | - Twilio voice-call: report malformed successful API JSON responses with provider-owned errors instead of leaking raw parser failures. |
88 | 89 | - Voice-call provider APIs: report malformed successful guarded JSON responses with provider-prefixed errors instead of leaking raw parser failures. |
89 | 90 | - Realtime transcription: report malformed provider websocket JSON frames with owned parser errors instead of leaking raw `SyntaxError` objects. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -94,6 +94,24 @@ describe("probeNextcloudTalkBotResponseFeature", () => {
|
94 | 94 | }); |
95 | 95 | }); |
96 | 96 | |
| 97 | +it("reports malformed bot admin JSON with a stable channel error", async () => { |
| 98 | +hoisted.fetchWithSsrFGuard.mockResolvedValueOnce({ |
| 99 | +response: new Response("{ nope", { |
| 100 | +status: 200, |
| 101 | +headers: { "content-type": "application/json" }, |
| 102 | +}), |
| 103 | +release: async () => {}, |
| 104 | +finalUrl: "https://cloud.example.com/ocs/v2.php/apps/spreed/api/v1/bot/admin", |
| 105 | +}); |
| 106 | + |
| 107 | +await expect(probeNextcloudTalkBotResponseFeature({ account: account() })).resolves.toEqual({ |
| 108 | +ok: false, |
| 109 | +code: "request_failed", |
| 110 | +message: |
| 111 | +"Nextcloud Talk bot response feature probe failed: Nextcloud Talk bot response feature probe failed: malformed JSON response", |
| 112 | +}); |
| 113 | +}); |
| 114 | + |
97 | 115 | it("skips when API credentials are absent", async () => { |
98 | 116 | await expect( |
99 | 117 | probeNextcloudTalkBotResponseFeature({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; |
| 2 | +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; |
2 | 3 | import { fetchWithSsrFGuard } from "../runtime-api.js"; |
3 | 4 | import type { ResolvedNextcloudTalkAccount } from "./accounts.js"; |
4 | 5 | import { resolveNextcloudTalkApiCredentials } from "./api-credentials.js"; |
@@ -135,9 +136,9 @@ export async function probeNextcloudTalkBotResponseFeature(params: {
|
135 | 136 | }; |
136 | 137 | } |
137 | 138 | |
138 | | -const payload = (await response.json()) as { |
| 139 | +const payload = await readProviderJsonResponse<{ |
139 | 140 | ocs?: { data?: NextcloudTalkBotAdminEntry[] }; |
140 | | -}; |
| 141 | +}>(response, "Nextcloud Talk bot response feature probe failed"); |
141 | 142 | const bots = Array.isArray(payload.ocs?.data) ? payload.ocs.data : []; |
142 | 143 | const bot = bots.find((entry) => normalizeUrlForMatch(entry.url) === webhookUrl); |
143 | 144 | if (!bot) { |
@@ -172,10 +173,11 @@ export async function probeNextcloudTalkBotResponseFeature(params: {
|
172 | 173 | await release(); |
173 | 174 | } |
174 | 175 | } catch (error) { |
| 176 | +const detail = error instanceof Error ? error.message : formatErrorMessage(error); |
175 | 177 | return { |
176 | 178 | ok: false, |
177 | 179 | code: "request_failed", |
178 | | -message: `Nextcloud Talk bot response feature probe failed: ${formatErrorMessage(error)}`, |
| 180 | +message: `Nextcloud Talk bot response feature probe failed: ${detail}`, |
179 | 181 | }; |
180 | 182 | } |
181 | 183 | } |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -110,6 +110,39 @@ describe("nextcloud talk room info", () => {
|
110 | 110 | expect(release).toHaveBeenCalledTimes(1); |
111 | 111 | }); |
112 | 112 | |
| 113 | +it("reports malformed room info JSON with a stable channel error", async () => { |
| 114 | +const release = vi.fn(async () => {}); |
| 115 | +const log = vi.fn(); |
| 116 | +const error = vi.fn(); |
| 117 | +const exit = vi.fn(); |
| 118 | +fetchWithSsrFGuard.mockResolvedValue({ |
| 119 | +response: new Response("{ nope", { |
| 120 | +status: 200, |
| 121 | +headers: { "content-type": "application/json" }, |
| 122 | +}), |
| 123 | + release, |
| 124 | +}); |
| 125 | + |
| 126 | +const kind = await resolveNextcloudTalkRoomKind({ |
| 127 | +account: { |
| 128 | +accountId: "acct-malformed", |
| 129 | +baseUrl: "https://nc.example.com", |
| 130 | +config: { |
| 131 | +apiUser: "bot", |
| 132 | +apiPassword: "secret", |
| 133 | +}, |
| 134 | +} as never, |
| 135 | +roomToken: "room-malformed", |
| 136 | +runtime: { log, error, exit }, |
| 137 | +}); |
| 138 | + |
| 139 | +expect(kind).toBeUndefined(); |
| 140 | +expect(error).toHaveBeenCalledWith( |
| 141 | +"nextcloud-talk: room lookup error: Error: Nextcloud Talk room info failed: malformed JSON response", |
| 142 | +); |
| 143 | +expect(release).toHaveBeenCalledTimes(1); |
| 144 | +}); |
| 145 | + |
113 | 146 | it("returns undefined from room info without credentials or base url", async () => { |
114 | 147 | await expect( |
115 | 148 | resolveNextcloudTalkRoomKind({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; |
| 2 | +import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http"; |
2 | 3 | import { ssrfPolicyFromPrivateNetworkOptIn } from "openclaw/plugin-sdk/ssrf-runtime"; |
3 | 4 | import { fetchWithSsrFGuard, type RuntimeEnv } from "../runtime-api.js"; |
4 | 5 | import type { ResolvedNextcloudTalkAccount } from "./accounts.js"; |
@@ -107,9 +108,9 @@ export async function resolveNextcloudTalkRoomKind(params: {
|
107 | 108 | return undefined; |
108 | 109 | } |
109 | 110 | |
110 | | -const payload = (await response.json()) as { |
| 111 | +const payload = await readProviderJsonResponse<{ |
111 | 112 | ocs?: { data?: { type?: number | string } }; |
112 | | -}; |
| 113 | +}>(response, "Nextcloud Talk room info failed"); |
113 | 114 | const type = coerceRoomType(payload.ocs?.data?.type); |
114 | 115 | const kind = resolveRoomKindFromType(type); |
115 | 116 | roomCache.set(key, { fetchedAt: Date.now(), kind }); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。