@kodelyth/nextcloud-talk 2026.5.42 → 2026.6.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 (58) hide show
  1. package/klaw.plugin.json +799 -2
  2. package/package.json +16 -4
  3. package/api.ts +0 -1
  4. package/channel-plugin-api.ts +0 -1
  5. package/contract-api.ts +0 -4
  6. package/doctor-contract-api.ts +0 -1
  7. package/index.ts +0 -20
  8. package/runtime-api.ts +0 -29
  9. package/secret-contract-api.ts +0 -5
  10. package/setup-entry.ts +0 -13
  11. package/src/accounts.test.ts +0 -31
  12. package/src/accounts.ts +0 -149
  13. package/src/api-credentials.ts +0 -31
  14. package/src/approval-auth.test.ts +0 -17
  15. package/src/approval-auth.ts +0 -27
  16. package/src/bot-preflight.test.ts +0 -135
  17. package/src/bot-preflight.ts +0 -183
  18. package/src/channel-api.ts +0 -5
  19. package/src/channel.adapters.ts +0 -52
  20. package/src/channel.core.test.ts +0 -75
  21. package/src/channel.lifecycle.test.ts +0 -91
  22. package/src/channel.status.test.ts +0 -28
  23. package/src/channel.ts +0 -225
  24. package/src/config-schema.ts +0 -79
  25. package/src/core.test.ts +0 -325
  26. package/src/doctor-contract.ts +0 -9
  27. package/src/doctor.test.ts +0 -87
  28. package/src/doctor.ts +0 -40
  29. package/src/gateway.ts +0 -109
  30. package/src/inbound.authz.test.ts +0 -146
  31. package/src/inbound.behavior.test.ts +0 -309
  32. package/src/inbound.ts +0 -392
  33. package/src/message-actions.test.ts +0 -270
  34. package/src/message-actions.ts +0 -82
  35. package/src/message-adapter.ts +0 -28
  36. package/src/monitor-runtime.ts +0 -138
  37. package/src/monitor.replay.test.ts +0 -276
  38. package/src/monitor.test-fixtures.ts +0 -30
  39. package/src/monitor.test-harness.ts +0 -59
  40. package/src/monitor.ts +0 -385
  41. package/src/normalize.ts +0 -44
  42. package/src/policy.ts +0 -111
  43. package/src/replay-guard.ts +0 -128
  44. package/src/room-info.test.ts +0 -160
  45. package/src/room-info.ts +0 -130
  46. package/src/runtime.ts +0 -9
  47. package/src/secret-contract.ts +0 -103
  48. package/src/secret-input.ts +0 -4
  49. package/src/send.cfg-threading.test.ts +0 -359
  50. package/src/send.runtime.ts +0 -8
  51. package/src/send.ts +0 -269
  52. package/src/session-route.ts +0 -40
  53. package/src/setup-core.ts +0 -250
  54. package/src/setup-surface.ts +0 -195
  55. package/src/setup.test.ts +0 -445
  56. package/src/signature.ts +0 -82
  57. package/src/types.ts +0 -195
  58. package/tsconfig.json +0 -16
package/src/setup-core.ts DELETED
@@ -1,250 +0,0 @@
1
- import type { ChannelSetupAdapter, ChannelSetupInput } from "klaw/plugin-sdk/channel-setup";
2
- import type { KlawConfig } from "klaw/plugin-sdk/config-contracts";
3
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "klaw/plugin-sdk/routing";
4
- import { applyAccountNameToChannelSection, patchScopedAccountConfig } from "klaw/plugin-sdk/setup";
5
- import {
6
- createSetupInputPresenceValidator,
7
- mergeAllowFromEntries,
8
- promptParsedAllowFromForAccount,
9
- resolveSetupAccountId,
10
- createSetupTranslator,
11
- type ChannelSetupDmPolicy,
12
- type WizardPrompter,
13
- } from "klaw/plugin-sdk/setup-runtime";
14
- import { formatDocsLink } from "klaw/plugin-sdk/setup-tools";
15
- import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
16
- import { resolveDefaultNextcloudTalkAccountId, resolveNextcloudTalkAccount } from "./accounts.js";
17
- import type { CoreConfig } from "./types.js";
18
-
19
- const t = createSetupTranslator();
20
-
21
- const channel = "nextcloud-talk" as const;
22
-
23
- type NextcloudSetupInput = ChannelSetupInput & {
24
- baseUrl?: string;
25
- secret?: string;
26
- secretFile?: string;
27
- };
28
- type NextcloudTalkSection = NonNullable<CoreConfig["channels"]>["nextcloud-talk"];
29
-
30
- function addWildcardAllowFrom(allowFrom?: Array<string | number> | null): string[] {
31
- return mergeAllowFromEntries(allowFrom, ["*"]);
32
- }
33
-
34
- export function normalizeNextcloudTalkBaseUrl(value: string | undefined): string {
35
- return value?.trim().replace(/\/+$/, "") ?? "";
36
- }
37
-
38
- export function validateNextcloudTalkBaseUrl(value: string): string | undefined {
39
- if (!value) {
40
- return "Required";
41
- }
42
- if (!value.startsWith("http://") && !value.startsWith("https://")) {
43
- return "URL must start with http:// or https://";
44
- }
45
- return undefined;
46
- }
47
-
48
- export function setNextcloudTalkAccountConfig(
49
- cfg: CoreConfig,
50
- accountId: string,
51
- updates: Record<string, unknown>,
52
- ): CoreConfig {
53
- return patchScopedAccountConfig({
54
- cfg,
55
- channelKey: channel,
56
- accountId,
57
- patch: updates,
58
- }) as CoreConfig;
59
- }
60
-
61
- export function clearNextcloudTalkAccountFields(
62
- cfg: CoreConfig,
63
- accountId: string,
64
- fields: string[],
65
- ): CoreConfig {
66
- const section = cfg.channels?.["nextcloud-talk"];
67
- if (!section) {
68
- return cfg;
69
- }
70
-
71
- if (accountId === DEFAULT_ACCOUNT_ID) {
72
- const nextSection = { ...section } as Record<string, unknown>;
73
- for (const field of fields) {
74
- delete nextSection[field];
75
- }
76
- return {
77
- ...cfg,
78
- channels: {
79
- ...cfg.channels,
80
- "nextcloud-talk": nextSection as NextcloudTalkSection,
81
- },
82
- } as CoreConfig;
83
- }
84
-
85
- const currentAccount = section.accounts?.[accountId];
86
- if (!currentAccount) {
87
- return cfg;
88
- }
89
-
90
- const nextAccount = { ...currentAccount } as Record<string, unknown>;
91
- for (const field of fields) {
92
- delete nextAccount[field];
93
- }
94
- return {
95
- ...cfg,
96
- channels: {
97
- ...cfg.channels,
98
- "nextcloud-talk": {
99
- ...section,
100
- accounts: {
101
- ...section.accounts,
102
- [accountId]: nextAccount as NonNullable<typeof section.accounts>[string],
103
- },
104
- },
105
- },
106
- } as CoreConfig;
107
- }
108
-
109
- async function promptNextcloudTalkAllowFrom(params: {
110
- cfg: CoreConfig;
111
- prompter: WizardPrompter;
112
- accountId: string;
113
- }): Promise<CoreConfig> {
114
- return await promptParsedAllowFromForAccount({
115
- cfg: params.cfg,
116
- accountId: params.accountId,
117
- defaultAccountId: params.accountId,
118
- prompter: params.prompter,
119
- noteTitle: t("wizard.nextcloudTalk.userIdTitle"),
120
- noteLines: [
121
- t("wizard.nextcloudTalk.userIdHelpAdmin"),
122
- t("wizard.nextcloudTalk.userIdHelpLogs"),
123
- t("wizard.nextcloudTalk.userIdHelpLowercase"),
124
- t("wizard.channels.docs", {
125
- link: formatDocsLink("/channels/nextcloud-talk", "nextcloud-talk"),
126
- }),
127
- ],
128
- message: t("wizard.nextcloudTalk.allowFromPrompt"),
129
- placeholder: "username",
130
- parseEntries: (raw) => ({
131
- entries: raw
132
- .split(/[\n,;]+/g)
133
- .map(normalizeLowercaseStringOrEmpty)
134
- .filter(Boolean),
135
- }),
136
- getExistingAllowFrom: ({ cfg, accountId }) =>
137
- resolveNextcloudTalkAccount({ cfg, accountId }).config.allowFrom ?? [],
138
- mergeEntries: ({ existing, parsed }) =>
139
- mergeAllowFromEntries(
140
- existing.map((value) => normalizeLowercaseStringOrEmpty(String(value))),
141
- parsed,
142
- ),
143
- applyAllowFrom: ({ cfg, accountId, allowFrom }) =>
144
- setNextcloudTalkAccountConfig(cfg, accountId, {
145
- dmPolicy: "allowlist",
146
- allowFrom,
147
- }),
148
- });
149
- }
150
-
151
- async function promptNextcloudTalkAllowFromForAccount(params: {
152
- cfg: KlawConfig;
153
- prompter: WizardPrompter;
154
- accountId?: string;
155
- }): Promise<KlawConfig> {
156
- const accountId = resolveSetupAccountId({
157
- accountId: params.accountId,
158
- defaultAccountId: resolveDefaultNextcloudTalkAccountId(params.cfg as CoreConfig),
159
- });
160
- return await promptNextcloudTalkAllowFrom({
161
- cfg: params.cfg as CoreConfig,
162
- prompter: params.prompter,
163
- accountId,
164
- });
165
- }
166
-
167
- export const nextcloudTalkDmPolicy: ChannelSetupDmPolicy = {
168
- label: "Nextcloud Talk",
169
- channel,
170
- policyKey: "channels.nextcloud-talk.dmPolicy",
171
- allowFromKey: "channels.nextcloud-talk.allowFrom",
172
- resolveConfigKeys: (cfg, accountId) =>
173
- (accountId ?? resolveDefaultNextcloudTalkAccountId(cfg as CoreConfig)) !== DEFAULT_ACCOUNT_ID
174
- ? {
175
- policyKey: `channels.nextcloud-talk.accounts.${accountId ?? resolveDefaultNextcloudTalkAccountId(cfg as CoreConfig)}.dmPolicy`,
176
- allowFromKey: `channels.nextcloud-talk.accounts.${accountId ?? resolveDefaultNextcloudTalkAccountId(cfg as CoreConfig)}.allowFrom`,
177
- }
178
- : {
179
- policyKey: "channels.nextcloud-talk.dmPolicy",
180
- allowFromKey: "channels.nextcloud-talk.allowFrom",
181
- },
182
- getCurrent: (cfg, accountId) =>
183
- resolveNextcloudTalkAccount({
184
- cfg: cfg as CoreConfig,
185
- accountId: accountId ?? resolveDefaultNextcloudTalkAccountId(cfg as CoreConfig),
186
- }).config.dmPolicy ?? "pairing",
187
- setPolicy: (cfg, policy, accountId) => {
188
- const resolvedAccountId = accountId ?? resolveDefaultNextcloudTalkAccountId(cfg as CoreConfig);
189
- const resolved = resolveNextcloudTalkAccount({
190
- cfg: cfg as CoreConfig,
191
- accountId: resolvedAccountId,
192
- });
193
- return setNextcloudTalkAccountConfig(cfg as CoreConfig, resolvedAccountId, {
194
- dmPolicy: policy,
195
- ...(policy === "open" ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } : {}),
196
- });
197
- },
198
- promptAllowFrom: promptNextcloudTalkAllowFromForAccount,
199
- };
200
-
201
- export const nextcloudTalkSetupAdapter: ChannelSetupAdapter = {
202
- resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
203
- applyAccountName: ({ cfg, accountId, name }) =>
204
- applyAccountNameToChannelSection({
205
- cfg,
206
- channelKey: channel,
207
- accountId,
208
- name,
209
- }),
210
- validateInput: createSetupInputPresenceValidator({
211
- defaultAccountOnlyEnvError:
212
- "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
213
- validate: ({ input }) => {
214
- const setupInput = input as NextcloudSetupInput;
215
- if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) {
216
- return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
217
- }
218
- if (!setupInput.baseUrl) {
219
- return "Nextcloud Talk requires --base-url.";
220
- }
221
- return null;
222
- },
223
- }),
224
- applyAccountConfig: ({ cfg, accountId, input }) => {
225
- const setupInput = input as NextcloudSetupInput;
226
- const namedConfig = applyAccountNameToChannelSection({
227
- cfg,
228
- channelKey: channel,
229
- accountId,
230
- name: setupInput.name,
231
- });
232
- const next = setupInput.useEnv
233
- ? clearNextcloudTalkAccountFields(namedConfig as CoreConfig, accountId, [
234
- "botSecret",
235
- "botSecretFile",
236
- ])
237
- : namedConfig;
238
- const patch = {
239
- baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
240
- ...(setupInput.useEnv
241
- ? {}
242
- : setupInput.secretFile
243
- ? { botSecretFile: setupInput.secretFile }
244
- : setupInput.secret
245
- ? { botSecret: setupInput.secret }
246
- : {}),
247
- };
248
- return setNextcloudTalkAccountConfig(next as CoreConfig, accountId, patch);
249
- },
250
- };
@@ -1,195 +0,0 @@
1
- import { DEFAULT_ACCOUNT_ID } from "klaw/plugin-sdk/routing";
2
- import { hasConfiguredSecretInput } from "klaw/plugin-sdk/secret-input";
3
- import {
4
- createStandardChannelSetupStatus,
5
- formatDocsLink,
6
- setSetupChannelEnabled,
7
- createSetupTranslator,
8
- type ChannelSetupWizard,
9
- } from "klaw/plugin-sdk/setup";
10
- import { normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
11
- import { resolveNextcloudTalkAccount } from "./accounts.js";
12
- import {
13
- clearNextcloudTalkAccountFields,
14
- nextcloudTalkDmPolicy,
15
- normalizeNextcloudTalkBaseUrl,
16
- setNextcloudTalkAccountConfig,
17
- validateNextcloudTalkBaseUrl,
18
- } from "./setup-core.js";
19
- import type { CoreConfig } from "./types.js";
20
-
21
- const t = createSetupTranslator();
22
-
23
- const channel = "nextcloud-talk" as const;
24
- const CONFIGURE_API_FLAG = "__nextcloudTalkConfigureApiCredentials";
25
-
26
- export const nextcloudTalkSetupWizard: ChannelSetupWizard = {
27
- channel,
28
- stepOrder: "text-first",
29
- status: createStandardChannelSetupStatus({
30
- channelLabel: "Nextcloud Talk",
31
- configuredLabel: t("wizard.channels.statusConfigured"),
32
- unconfiguredLabel: t("wizard.channels.statusNeedsSetup"),
33
- configuredHint: t("wizard.channels.statusConfigured"),
34
- unconfiguredHint: t("wizard.channels.statusSelfHostedChat"),
35
- configuredScore: 1,
36
- unconfiguredScore: 5,
37
- resolveConfigured: ({ cfg, accountId }) => {
38
- const account = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId });
39
- return Boolean(account.secret && account.baseUrl);
40
- },
41
- }),
42
- introNote: {
43
- title: t("wizard.nextcloudTalk.setupTitle"),
44
- lines: [
45
- t("wizard.nextcloudTalk.helpSsh"),
46
- t("wizard.nextcloudTalk.helpInstallCommand"),
47
- t("wizard.nextcloudTalk.helpCopySecret"),
48
- t("wizard.nextcloudTalk.helpEnableRoom"),
49
- t("wizard.nextcloudTalk.helpEnvTip"),
50
- t("wizard.channels.docs", {
51
- link: formatDocsLink("/channels/nextcloud-talk", "channels/nextcloud-talk"),
52
- }),
53
- ],
54
- shouldShow: ({ cfg, accountId }) => {
55
- const account = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId });
56
- return !account.secret || !account.baseUrl;
57
- },
58
- },
59
- prepare: async ({ cfg, accountId, credentialValues, prompter }) => {
60
- const resolvedAccount = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId });
61
- const hasApiCredentials = Boolean(
62
- resolvedAccount.config.apiUser?.trim() &&
63
- (hasConfiguredSecretInput(resolvedAccount.config.apiPassword) ||
64
- resolvedAccount.config.apiPasswordFile),
65
- );
66
- const configureApiCredentials = await prompter.confirm({
67
- message: t("wizard.nextcloudTalk.configureApiCredentials"),
68
- initialValue: hasApiCredentials,
69
- });
70
- if (!configureApiCredentials) {
71
- return undefined;
72
- }
73
- return {
74
- credentialValues: {
75
- ...credentialValues,
76
- [CONFIGURE_API_FLAG]: "1",
77
- },
78
- };
79
- },
80
- credentials: [
81
- {
82
- inputKey: "token",
83
- providerHint: channel,
84
- credentialLabel: t("wizard.nextcloudTalk.botSecret"),
85
- preferredEnvVar: "NEXTCLOUD_TALK_BOT_SECRET",
86
- envPrompt: t("wizard.nextcloudTalk.botSecretEnvPrompt"),
87
- keepPrompt: t("wizard.nextcloudTalk.botSecretKeep"),
88
- inputPrompt: t("wizard.nextcloudTalk.botSecretInput"),
89
- allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID,
90
- inspect: ({ cfg, accountId }) => {
91
- const resolvedAccount = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId });
92
- return {
93
- accountConfigured: Boolean(resolvedAccount.secret && resolvedAccount.baseUrl),
94
- hasConfiguredValue: Boolean(
95
- hasConfiguredSecretInput(resolvedAccount.config.botSecret) ||
96
- resolvedAccount.config.botSecretFile,
97
- ),
98
- resolvedValue: resolvedAccount.secret || undefined,
99
- envValue:
100
- accountId === DEFAULT_ACCOUNT_ID
101
- ? normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET)
102
- : undefined,
103
- };
104
- },
105
- applyUseEnv: async (params) => {
106
- const resolvedAccount = resolveNextcloudTalkAccount({
107
- cfg: params.cfg as CoreConfig,
108
- accountId: params.accountId,
109
- });
110
- const cleared = clearNextcloudTalkAccountFields(
111
- params.cfg as CoreConfig,
112
- params.accountId,
113
- ["botSecret", "botSecretFile"],
114
- );
115
- return setNextcloudTalkAccountConfig(cleared, params.accountId, {
116
- baseUrl: resolvedAccount.baseUrl,
117
- });
118
- },
119
- applySet: async (params) =>
120
- setNextcloudTalkAccountConfig(
121
- clearNextcloudTalkAccountFields(params.cfg as CoreConfig, params.accountId, [
122
- "botSecret",
123
- "botSecretFile",
124
- ]),
125
- params.accountId,
126
- {
127
- botSecret: params.value,
128
- },
129
- ),
130
- },
131
- {
132
- inputKey: "password",
133
- providerHint: "nextcloud-talk-api",
134
- credentialLabel: t("wizard.nextcloudTalk.apiPassword"),
135
- preferredEnvVar: "NEXTCLOUD_TALK_API_PASSWORD",
136
- envPrompt: "",
137
- keepPrompt: t("wizard.nextcloudTalk.apiPasswordKeep"),
138
- inputPrompt: t("wizard.nextcloudTalk.apiPasswordInput"),
139
- inspect: ({ cfg, accountId }) => {
140
- const resolvedAccount = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId });
141
- const apiUser = resolvedAccount.config.apiUser?.trim();
142
- const apiPasswordConfigured = Boolean(
143
- hasConfiguredSecretInput(resolvedAccount.config.apiPassword) ||
144
- resolvedAccount.config.apiPasswordFile,
145
- );
146
- return {
147
- accountConfigured: Boolean(apiUser && apiPasswordConfigured),
148
- hasConfiguredValue: apiPasswordConfigured,
149
- };
150
- },
151
- shouldPrompt: ({ credentialValues }) => credentialValues[CONFIGURE_API_FLAG] === "1",
152
- applySet: async (params) =>
153
- setNextcloudTalkAccountConfig(
154
- clearNextcloudTalkAccountFields(params.cfg as CoreConfig, params.accountId, [
155
- "apiPassword",
156
- "apiPasswordFile",
157
- ]),
158
- params.accountId,
159
- {
160
- apiPassword: params.value,
161
- },
162
- ),
163
- },
164
- ],
165
- textInputs: [
166
- {
167
- inputKey: "httpUrl",
168
- message: t("wizard.nextcloudTalk.instanceUrlPrompt"),
169
- currentValue: ({ cfg, accountId }) =>
170
- resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).baseUrl || undefined,
171
- shouldPrompt: ({ currentValue }) => !currentValue,
172
- validate: ({ value }) => validateNextcloudTalkBaseUrl(value),
173
- normalizeValue: ({ value }) => normalizeNextcloudTalkBaseUrl(value),
174
- applySet: async (params) =>
175
- setNextcloudTalkAccountConfig(params.cfg as CoreConfig, params.accountId, {
176
- baseUrl: params.value,
177
- }),
178
- },
179
- {
180
- inputKey: "userId",
181
- message: t("wizard.nextcloudTalk.apiUserPrompt"),
182
- currentValue: ({ cfg, accountId }) =>
183
- resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).config.apiUser?.trim() ||
184
- undefined,
185
- shouldPrompt: ({ credentialValues }) => credentialValues[CONFIGURE_API_FLAG] === "1",
186
- validate: ({ value }) => (value ? undefined : t("common.required")),
187
- applySet: async (params) =>
188
- setNextcloudTalkAccountConfig(params.cfg as CoreConfig, params.accountId, {
189
- apiUser: params.value,
190
- }),
191
- },
192
- ],
193
- dmPolicy: nextcloudTalkDmPolicy,
194
- disable: (cfg) => setSetupChannelEnabled(cfg, channel, false),
195
- };