@openclaw/nextcloud-talk 2026.3.13 → 2026.5.2-beta.1

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.
Files changed (59) hide show
  1. package/api.ts +1 -0
  2. package/channel-plugin-api.ts +1 -0
  3. package/contract-api.ts +4 -0
  4. package/doctor-contract-api.ts +1 -0
  5. package/index.ts +15 -12
  6. package/openclaw.plugin.json +804 -1
  7. package/package.json +31 -4
  8. package/runtime-api.ts +33 -0
  9. package/secret-contract-api.ts +5 -0
  10. package/setup-entry.ts +13 -0
  11. package/src/accounts.ts +25 -42
  12. package/src/approval-auth.test.ts +17 -0
  13. package/src/approval-auth.ts +27 -0
  14. package/src/channel-api.ts +5 -0
  15. package/src/channel.adapters.ts +52 -0
  16. package/src/channel.core.test.ts +75 -0
  17. package/src/{channel.startup.test.ts → channel.lifecycle.test.ts} +29 -27
  18. package/src/channel.ts +158 -385
  19. package/src/config-schema.ts +24 -18
  20. package/src/core.test.ts +397 -0
  21. package/src/doctor-contract.ts +9 -0
  22. package/src/doctor.test.ts +40 -0
  23. package/src/doctor.ts +10 -0
  24. package/src/gateway.ts +109 -0
  25. package/src/inbound.authz.test.ts +87 -22
  26. package/src/inbound.behavior.test.ts +202 -0
  27. package/src/inbound.ts +28 -26
  28. package/src/monitor-runtime.ts +138 -0
  29. package/src/monitor.replay.test.ts +238 -0
  30. package/src/monitor.test-harness.ts +2 -2
  31. package/src/monitor.ts +125 -153
  32. package/src/policy.ts +23 -31
  33. package/src/replay-guard.ts +74 -11
  34. package/src/room-info.test.ts +116 -0
  35. package/src/room-info.ts +11 -3
  36. package/src/runtime.ts +6 -3
  37. package/src/secret-contract.ts +103 -0
  38. package/src/secret-input.ts +1 -10
  39. package/src/send.cfg-threading.test.ts +153 -0
  40. package/src/send.runtime.ts +8 -0
  41. package/src/send.ts +109 -77
  42. package/src/session-route.ts +40 -0
  43. package/src/setup-core.ts +248 -0
  44. package/src/setup-surface.ts +190 -0
  45. package/src/setup.test.ts +422 -0
  46. package/src/signature.ts +20 -10
  47. package/src/types.ts +19 -16
  48. package/tsconfig.json +16 -0
  49. package/src/accounts.test.ts +0 -30
  50. package/src/config-schema.test.ts +0 -36
  51. package/src/format.ts +0 -79
  52. package/src/monitor.auth-order.test.ts +0 -28
  53. package/src/monitor.backend.test.ts +0 -27
  54. package/src/monitor.read-body.test.ts +0 -16
  55. package/src/normalize.test.ts +0 -28
  56. package/src/onboarding.ts +0 -302
  57. package/src/policy.test.ts +0 -138
  58. package/src/replay-guard.test.ts +0 -70
  59. package/src/send.test.ts +0 -98
@@ -1,138 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { resolveNextcloudTalkAllowlistMatch, resolveNextcloudTalkGroupAllow } from "./policy.js";
3
-
4
- describe("nextcloud-talk policy", () => {
5
- describe("resolveNextcloudTalkAllowlistMatch", () => {
6
- it("allows wildcard", () => {
7
- expect(
8
- resolveNextcloudTalkAllowlistMatch({
9
- allowFrom: ["*"],
10
- senderId: "user-id",
11
- }).allowed,
12
- ).toBe(true);
13
- });
14
-
15
- it("allows sender id match with normalization", () => {
16
- expect(
17
- resolveNextcloudTalkAllowlistMatch({
18
- allowFrom: ["nc:User-Id"],
19
- senderId: "user-id",
20
- }),
21
- ).toEqual({ allowed: true, matchKey: "user-id", matchSource: "id" });
22
- });
23
-
24
- it("blocks when sender id does not match", () => {
25
- expect(
26
- resolveNextcloudTalkAllowlistMatch({
27
- allowFrom: ["allowed"],
28
- senderId: "other",
29
- }).allowed,
30
- ).toBe(false);
31
- });
32
- });
33
-
34
- describe("resolveNextcloudTalkGroupAllow", () => {
35
- it("blocks disabled policy", () => {
36
- expect(
37
- resolveNextcloudTalkGroupAllow({
38
- groupPolicy: "disabled",
39
- outerAllowFrom: ["owner"],
40
- innerAllowFrom: ["room-user"],
41
- senderId: "owner",
42
- }),
43
- ).toEqual({
44
- allowed: false,
45
- outerMatch: { allowed: false },
46
- innerMatch: { allowed: false },
47
- });
48
- });
49
-
50
- it("allows open policy", () => {
51
- expect(
52
- resolveNextcloudTalkGroupAllow({
53
- groupPolicy: "open",
54
- outerAllowFrom: [],
55
- innerAllowFrom: [],
56
- senderId: "owner",
57
- }),
58
- ).toEqual({
59
- allowed: true,
60
- outerMatch: { allowed: true },
61
- innerMatch: { allowed: true },
62
- });
63
- });
64
-
65
- it("blocks allowlist mode when both outer and inner allowlists are empty", () => {
66
- expect(
67
- resolveNextcloudTalkGroupAllow({
68
- groupPolicy: "allowlist",
69
- outerAllowFrom: [],
70
- innerAllowFrom: [],
71
- senderId: "owner",
72
- }),
73
- ).toEqual({
74
- allowed: false,
75
- outerMatch: { allowed: false },
76
- innerMatch: { allowed: false },
77
- });
78
- });
79
-
80
- it("requires inner match when only room-specific allowlist is configured", () => {
81
- expect(
82
- resolveNextcloudTalkGroupAllow({
83
- groupPolicy: "allowlist",
84
- outerAllowFrom: [],
85
- innerAllowFrom: ["room-user"],
86
- senderId: "room-user",
87
- }),
88
- ).toEqual({
89
- allowed: true,
90
- outerMatch: { allowed: false },
91
- innerMatch: { allowed: true, matchKey: "room-user", matchSource: "id" },
92
- });
93
- });
94
-
95
- it("blocks when outer allowlist misses even if inner allowlist matches", () => {
96
- expect(
97
- resolveNextcloudTalkGroupAllow({
98
- groupPolicy: "allowlist",
99
- outerAllowFrom: ["team-owner"],
100
- innerAllowFrom: ["room-user"],
101
- senderId: "room-user",
102
- }),
103
- ).toEqual({
104
- allowed: false,
105
- outerMatch: { allowed: false },
106
- innerMatch: { allowed: true, matchKey: "room-user", matchSource: "id" },
107
- });
108
- });
109
-
110
- it("allows when both outer and inner allowlists match", () => {
111
- expect(
112
- resolveNextcloudTalkGroupAllow({
113
- groupPolicy: "allowlist",
114
- outerAllowFrom: ["team-owner"],
115
- innerAllowFrom: ["room-user"],
116
- senderId: "team-owner",
117
- }),
118
- ).toEqual({
119
- allowed: false,
120
- outerMatch: { allowed: true, matchKey: "team-owner", matchSource: "id" },
121
- innerMatch: { allowed: false },
122
- });
123
-
124
- expect(
125
- resolveNextcloudTalkGroupAllow({
126
- groupPolicy: "allowlist",
127
- outerAllowFrom: ["shared-user"],
128
- innerAllowFrom: ["shared-user"],
129
- senderId: "shared-user",
130
- }),
131
- ).toEqual({
132
- allowed: true,
133
- outerMatch: { allowed: true, matchKey: "shared-user", matchSource: "id" },
134
- innerMatch: { allowed: true, matchKey: "shared-user", matchSource: "id" },
135
- });
136
- });
137
- });
138
- });
@@ -1,70 +0,0 @@
1
- import { mkdtemp, rm } from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { createNextcloudTalkReplayGuard } from "./replay-guard.js";
6
-
7
- const tempDirs: string[] = [];
8
-
9
- afterEach(async () => {
10
- while (tempDirs.length > 0) {
11
- const dir = tempDirs.pop();
12
- if (dir) {
13
- await rm(dir, { recursive: true, force: true });
14
- }
15
- }
16
- });
17
-
18
- async function makeTempDir(): Promise<string> {
19
- const dir = await mkdtemp(path.join(os.tmpdir(), "nextcloud-talk-replay-"));
20
- tempDirs.push(dir);
21
- return dir;
22
- }
23
-
24
- describe("createNextcloudTalkReplayGuard", () => {
25
- it("persists replay decisions across guard instances", async () => {
26
- const stateDir = await makeTempDir();
27
-
28
- const firstGuard = createNextcloudTalkReplayGuard({ stateDir });
29
- const firstAttempt = await firstGuard.shouldProcessMessage({
30
- accountId: "account-a",
31
- roomToken: "room-1",
32
- messageId: "msg-1",
33
- });
34
- const replayAttempt = await firstGuard.shouldProcessMessage({
35
- accountId: "account-a",
36
- roomToken: "room-1",
37
- messageId: "msg-1",
38
- });
39
-
40
- const secondGuard = createNextcloudTalkReplayGuard({ stateDir });
41
- const restartReplayAttempt = await secondGuard.shouldProcessMessage({
42
- accountId: "account-a",
43
- roomToken: "room-1",
44
- messageId: "msg-1",
45
- });
46
-
47
- expect(firstAttempt).toBe(true);
48
- expect(replayAttempt).toBe(false);
49
- expect(restartReplayAttempt).toBe(false);
50
- });
51
-
52
- it("scopes replay state by account namespace", async () => {
53
- const stateDir = await makeTempDir();
54
- const guard = createNextcloudTalkReplayGuard({ stateDir });
55
-
56
- const accountAFirst = await guard.shouldProcessMessage({
57
- accountId: "account-a",
58
- roomToken: "room-1",
59
- messageId: "msg-9",
60
- });
61
- const accountBFirst = await guard.shouldProcessMessage({
62
- accountId: "account-b",
63
- roomToken: "room-1",
64
- messageId: "msg-9",
65
- });
66
-
67
- expect(accountAFirst).toBe(true);
68
- expect(accountBFirst).toBe(true);
69
- });
70
- });
package/src/send.test.ts DELETED
@@ -1,98 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import {
3
- createSendCfgThreadingRuntime,
4
- expectProvidedCfgSkipsRuntimeLoad,
5
- expectRuntimeCfgFallback,
6
- } from "../../test-utils/send-config.js";
7
-
8
- const hoisted = vi.hoisted(() => ({
9
- loadConfig: vi.fn(),
10
- resolveMarkdownTableMode: vi.fn(() => "preserve"),
11
- convertMarkdownTables: vi.fn((text: string) => text),
12
- record: vi.fn(),
13
- resolveNextcloudTalkAccount: vi.fn(() => ({
14
- accountId: "default",
15
- baseUrl: "https://nextcloud.example.com",
16
- secret: "secret-value", // pragma: allowlist secret
17
- })),
18
- generateNextcloudTalkSignature: vi.fn(() => ({
19
- random: "r",
20
- signature: "s",
21
- })),
22
- }));
23
-
24
- vi.mock("./runtime.js", () => ({
25
- getNextcloudTalkRuntime: () => createSendCfgThreadingRuntime(hoisted),
26
- }));
27
-
28
- vi.mock("./accounts.js", () => ({
29
- resolveNextcloudTalkAccount: hoisted.resolveNextcloudTalkAccount,
30
- }));
31
-
32
- vi.mock("./signature.js", () => ({
33
- generateNextcloudTalkSignature: hoisted.generateNextcloudTalkSignature,
34
- }));
35
-
36
- import { sendMessageNextcloudTalk, sendReactionNextcloudTalk } from "./send.js";
37
-
38
- describe("nextcloud-talk send cfg threading", () => {
39
- const fetchMock = vi.fn<typeof fetch>();
40
-
41
- beforeEach(() => {
42
- vi.clearAllMocks();
43
- fetchMock.mockReset();
44
- vi.stubGlobal("fetch", fetchMock);
45
- });
46
-
47
- afterEach(() => {
48
- vi.unstubAllGlobals();
49
- });
50
-
51
- it("uses provided cfg for sendMessage and skips runtime loadConfig", async () => {
52
- const cfg = { source: "provided" } as const;
53
- fetchMock.mockResolvedValueOnce(
54
- new Response(
55
- JSON.stringify({
56
- ocs: { data: { id: 12345, timestamp: 1_706_000_000 } },
57
- }),
58
- { status: 200, headers: { "content-type": "application/json" } },
59
- ),
60
- );
61
-
62
- const result = await sendMessageNextcloudTalk("room:abc123", "hello", {
63
- cfg,
64
- accountId: "work",
65
- });
66
-
67
- expectProvidedCfgSkipsRuntimeLoad({
68
- loadConfig: hoisted.loadConfig,
69
- resolveAccount: hoisted.resolveNextcloudTalkAccount,
70
- cfg,
71
- accountId: "work",
72
- });
73
- expect(fetchMock).toHaveBeenCalledTimes(1);
74
- expect(result).toEqual({
75
- messageId: "12345",
76
- roomToken: "abc123",
77
- timestamp: 1_706_000_000,
78
- });
79
- });
80
-
81
- it("falls back to runtime cfg for sendReaction when cfg is omitted", async () => {
82
- const runtimeCfg = { source: "runtime" } as const;
83
- hoisted.loadConfig.mockReturnValueOnce(runtimeCfg);
84
- fetchMock.mockResolvedValueOnce(new Response("{}", { status: 200 }));
85
-
86
- const result = await sendReactionNextcloudTalk("room:ops", "m-1", "👍", {
87
- accountId: "default",
88
- });
89
-
90
- expect(result).toEqual({ ok: true });
91
- expectRuntimeCfgFallback({
92
- loadConfig: hoisted.loadConfig,
93
- resolveAccount: hoisted.resolveNextcloudTalkAccount,
94
- cfg: runtimeCfg,
95
- accountId: "default",
96
- });
97
- });
98
- });