@openclaw/nextcloud-talk 2026.2.9 → 2026.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.2.9",
3
+ "version": "2026.2.13",
4
4
  "description": "OpenClaw Nextcloud Talk channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
package/src/accounts.ts CHANGED
@@ -1,16 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
2
+ import { DEFAULT_ACCOUNT_ID, isTruthyEnvValue, normalizeAccountId } from "openclaw/plugin-sdk";
3
3
  import type { CoreConfig, NextcloudTalkAccountConfig } from "./types.js";
4
4
 
5
- const TRUTHY_ENV = new Set(["true", "1", "yes", "on"]);
6
-
7
- function isTruthyEnvValue(value?: string): boolean {
8
- if (!value) {
9
- return false;
10
- }
11
- return TRUTHY_ENV.has(value.trim().toLowerCase());
12
- }
13
-
14
5
  const debugAccounts = (...args: unknown[]) => {
15
6
  if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_NEXTCLOUD_TALK_ACCOUNTS)) {
16
7
  console.warn("[nextcloud-talk:accounts]", ...args);
package/src/inbound.ts CHANGED
@@ -263,6 +263,7 @@ export async function handleNextcloudTalkInbound(params: {
263
263
 
264
264
  const ctxPayload = core.channel.reply.finalizeInboundContext({
265
265
  Body: body,
266
+ BodyForAgent: rawBody,
266
267
  RawBody: rawBody,
267
268
  CommandBody: rawBody,
268
269
  From: isGroup ? `nextcloud-talk:room:${roomToken}` : `nextcloud-talk:${senderId}`,
@@ -0,0 +1,38 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import { EventEmitter } from "node:events";
3
+ import { describe, expect, it } from "vitest";
4
+ import { readNextcloudTalkWebhookBody } from "./monitor.js";
5
+
6
+ function createMockRequest(chunks: string[]): IncomingMessage {
7
+ const req = new EventEmitter() as IncomingMessage & { destroyed?: boolean; destroy: () => void };
8
+ req.destroyed = false;
9
+ req.headers = {};
10
+ req.destroy = () => {
11
+ req.destroyed = true;
12
+ };
13
+
14
+ void Promise.resolve().then(() => {
15
+ for (const chunk of chunks) {
16
+ req.emit("data", Buffer.from(chunk, "utf-8"));
17
+ if (req.destroyed) {
18
+ return;
19
+ }
20
+ }
21
+ req.emit("end");
22
+ });
23
+
24
+ return req;
25
+ }
26
+
27
+ describe("readNextcloudTalkWebhookBody", () => {
28
+ it("reads valid body within max bytes", async () => {
29
+ const req = createMockRequest(['{"type":"Create"}']);
30
+ const body = await readNextcloudTalkWebhookBody(req, 1024);
31
+ expect(body).toBe('{"type":"Create"}');
32
+ });
33
+
34
+ it("rejects when payload exceeds max bytes", async () => {
35
+ const req = createMockRequest(["x".repeat(300)]);
36
+ await expect(readNextcloudTalkWebhookBody(req, 128)).rejects.toThrow("PayloadTooLarge");
37
+ });
38
+ });
package/src/monitor.ts CHANGED
@@ -1,5 +1,10 @@
1
- import type { RuntimeEnv } from "openclaw/plugin-sdk";
2
1
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
+ import {
3
+ type RuntimeEnv,
4
+ isRequestBodyLimitError,
5
+ readRequestBodyWithLimit,
6
+ requestBodyErrorToText,
7
+ } from "openclaw/plugin-sdk";
3
8
  import type {
4
9
  CoreConfig,
5
10
  NextcloudTalkInboundMessage,
@@ -14,6 +19,8 @@ import { extractNextcloudTalkHeaders, verifyNextcloudTalkSignature } from "./sig
14
19
  const DEFAULT_WEBHOOK_PORT = 8788;
15
20
  const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
16
21
  const DEFAULT_WEBHOOK_PATH = "/nextcloud-talk-webhook";
22
+ const DEFAULT_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
23
+ const DEFAULT_WEBHOOK_BODY_TIMEOUT_MS = 30_000;
17
24
  const HEALTH_PATH = "/healthz";
18
25
 
19
26
  function formatError(err: unknown): string {
@@ -62,12 +69,13 @@ function payloadToInboundMessage(
62
69
  };
63
70
  }
64
71
 
65
- function readBody(req: IncomingMessage): Promise<string> {
66
- return new Promise((resolve, reject) => {
67
- const chunks: Buffer[] = [];
68
- req.on("data", (chunk: Buffer) => chunks.push(chunk));
69
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
70
- req.on("error", reject);
72
+ export function readNextcloudTalkWebhookBody(
73
+ req: IncomingMessage,
74
+ maxBodyBytes: number,
75
+ ): Promise<string> {
76
+ return readRequestBodyWithLimit(req, {
77
+ maxBytes: maxBodyBytes,
78
+ timeoutMs: DEFAULT_WEBHOOK_BODY_TIMEOUT_MS,
71
79
  });
72
80
  }
73
81
 
@@ -77,6 +85,12 @@ export function createNextcloudTalkWebhookServer(opts: NextcloudTalkWebhookServe
77
85
  stop: () => void;
78
86
  } {
79
87
  const { port, host, path, secret, onMessage, onError, abortSignal } = opts;
88
+ const maxBodyBytes =
89
+ typeof opts.maxBodyBytes === "number" &&
90
+ Number.isFinite(opts.maxBodyBytes) &&
91
+ opts.maxBodyBytes > 0
92
+ ? Math.floor(opts.maxBodyBytes)
93
+ : DEFAULT_WEBHOOK_MAX_BODY_BYTES;
80
94
 
81
95
  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
82
96
  if (req.url === HEALTH_PATH) {
@@ -92,7 +106,7 @@ export function createNextcloudTalkWebhookServer(opts: NextcloudTalkWebhookServe
92
106
  }
93
107
 
94
108
  try {
95
- const body = await readBody(req);
109
+ const body = await readNextcloudTalkWebhookBody(req, maxBodyBytes);
96
110
 
97
111
  const headers = extractNextcloudTalkHeaders(
98
112
  req.headers as Record<string, string | string[] | undefined>,
@@ -140,6 +154,20 @@ export function createNextcloudTalkWebhookServer(opts: NextcloudTalkWebhookServe
140
154
  onError?.(err instanceof Error ? err : new Error(formatError(err)));
141
155
  }
142
156
  } catch (err) {
157
+ if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
158
+ if (!res.headersSent) {
159
+ res.writeHead(413, { "Content-Type": "application/json" });
160
+ res.end(JSON.stringify({ error: "Payload too large" }));
161
+ }
162
+ return;
163
+ }
164
+ if (isRequestBodyLimitError(err, "REQUEST_BODY_TIMEOUT")) {
165
+ if (!res.headersSent) {
166
+ res.writeHead(408, { "Content-Type": "application/json" });
167
+ res.end(JSON.stringify({ error: requestBodyErrorToText("REQUEST_BODY_TIMEOUT") }));
168
+ }
169
+ return;
170
+ }
143
171
  const error = err instanceof Error ? err : new Error(formatError(err));
144
172
  onError?.(error);
145
173
  if (!res.headersSent) {
package/src/types.ts CHANGED
@@ -168,6 +168,7 @@ export type NextcloudTalkWebhookServerOptions = {
168
168
  host: string;
169
169
  path: string;
170
170
  secret: string;
171
+ maxBodyBytes?: number;
171
172
  onMessage: (message: NextcloudTalkInboundMessage) => void | Promise<void>;
172
173
  onError?: (error: Error) => void;
173
174
  abortSignal?: AbortSignal;