dsh-connect 0.7.2 → 0.8.0
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/README.md +68 -32
- package/README.zh.md +67 -31
- package/client/client.js +387 -0
- package/client/client.js.map +7 -0
- package/client/settings-client.mjs +177 -0
- package/examples/minimal.config.json +16 -0
- package/lib/channels/dingtalk/adapter.d.ts +59 -0
- package/lib/channels/dingtalk/adapter.d.ts.map +1 -0
- package/lib/channels/dingtalk/adapter.js +123 -0
- package/lib/channels/dingtalk/adapter.js.map +1 -0
- package/lib/channels/dingtalk/i18n.d.ts +14 -0
- package/lib/channels/dingtalk/i18n.d.ts.map +1 -0
- package/lib/channels/dingtalk/i18n.js +16 -0
- package/lib/channels/dingtalk/i18n.js.map +1 -0
- package/lib/channels/dingtalk/index.d.ts +127 -0
- package/lib/channels/dingtalk/index.d.ts.map +1 -0
- package/lib/channels/dingtalk/index.js +144 -0
- package/lib/channels/dingtalk/index.js.map +1 -0
- package/lib/channels/dingtalk/message.d.ts +49 -0
- package/lib/channels/dingtalk/message.d.ts.map +1 -0
- package/lib/channels/dingtalk/message.js +43 -0
- package/lib/channels/dingtalk/message.js.map +1 -0
- package/lib/channels/dingtalk/stomp.d.ts +36 -0
- package/lib/channels/dingtalk/stomp.d.ts.map +1 -0
- package/lib/channels/dingtalk/stomp.js +107 -0
- package/lib/channels/dingtalk/stomp.js.map +1 -0
- package/lib/channels/dingtalk/stream.d.ts +53 -0
- package/lib/channels/dingtalk/stream.d.ts.map +1 -0
- package/lib/channels/dingtalk/stream.js +199 -0
- package/lib/channels/dingtalk/stream.js.map +1 -0
- package/lib/channels/dingtalk/webhook.d.ts +84 -0
- package/lib/channels/dingtalk/webhook.d.ts.map +1 -0
- package/lib/channels/dingtalk/webhook.js +143 -0
- package/lib/channels/dingtalk/webhook.js.map +1 -0
- package/lib/channels/feishu/adapter.d.ts +139 -0
- package/lib/channels/feishu/adapter.d.ts.map +1 -0
- package/lib/channels/feishu/adapter.js +677 -0
- package/lib/channels/feishu/adapter.js.map +1 -0
- package/lib/channels/feishu/i18n.d.ts +28 -0
- package/lib/channels/feishu/i18n.d.ts.map +1 -0
- package/lib/channels/feishu/i18n.js +40 -0
- package/lib/channels/feishu/i18n.js.map +1 -0
- package/lib/channels/feishu/index.d.ts +61 -0
- package/lib/channels/feishu/index.d.ts.map +1 -0
- package/lib/channels/feishu/index.js +77 -0
- package/lib/channels/feishu/index.js.map +1 -0
- package/lib/channels/feishu/onboard.d.ts +16 -0
- package/lib/channels/feishu/onboard.d.ts.map +1 -0
- package/lib/channels/feishu/onboard.js +86 -0
- package/lib/channels/feishu/onboard.js.map +1 -0
- package/lib/channels/telegram/adapter.d.ts +70 -0
- package/lib/channels/telegram/adapter.d.ts.map +1 -0
- package/lib/channels/telegram/adapter.js +454 -0
- package/lib/channels/telegram/adapter.js.map +1 -0
- package/lib/channels/telegram/client.d.ts +115 -0
- package/lib/channels/telegram/client.d.ts.map +1 -0
- package/lib/channels/telegram/client.js +198 -0
- package/lib/channels/telegram/client.js.map +1 -0
- package/lib/channels/telegram/i18n.d.ts +14 -0
- package/lib/channels/telegram/i18n.d.ts.map +1 -0
- package/lib/channels/telegram/i18n.js +18 -0
- package/lib/channels/telegram/i18n.js.map +1 -0
- package/lib/channels/telegram/index.d.ts +46 -0
- package/lib/channels/telegram/index.d.ts.map +1 -0
- package/lib/channels/telegram/index.js +54 -0
- package/lib/channels/telegram/index.js.map +1 -0
- package/lib/channels/web/adapter.d.ts +103 -0
- package/lib/channels/web/adapter.d.ts.map +1 -0
- package/lib/channels/web/adapter.js +161 -0
- package/lib/channels/web/adapter.js.map +1 -0
- package/lib/channels/web/index.d.ts +47 -0
- package/lib/channels/web/index.d.ts.map +1 -0
- package/lib/channels/web/index.js +59 -0
- package/lib/channels/web/index.js.map +1 -0
- package/lib/index.d.ts +68 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +141 -8
- package/lib/index.js.map +1 -1
- package/lib/runner.d.ts +1 -2
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js.map +1 -1
- package/lib/service.d.ts +1 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +1 -1
- package/lib/service.js.map +1 -1
- package/lib/settings/channels.d.ts +51 -0
- package/lib/settings/channels.d.ts.map +1 -0
- package/lib/settings/channels.js +101 -0
- package/lib/settings/channels.js.map +1 -0
- package/lib/settings/credential-store.d.ts +39 -0
- package/lib/settings/credential-store.d.ts.map +1 -0
- package/lib/settings/credential-store.js +68 -0
- package/lib/settings/credential-store.js.map +1 -0
- package/lib/settings/index.d.ts +14 -0
- package/lib/settings/index.d.ts.map +1 -0
- package/lib/settings/index.js +14 -0
- package/lib/settings/index.js.map +1 -0
- package/lib/settings/rpc-client.d.ts +36 -0
- package/lib/settings/rpc-client.d.ts.map +1 -0
- package/lib/settings/rpc-client.js +45 -0
- package/lib/settings/rpc-client.js.map +1 -0
- package/lib/settings/settings-model.d.ts +107 -0
- package/lib/settings/settings-model.d.ts.map +1 -0
- package/lib/settings/settings-model.js +110 -0
- package/lib/settings/settings-model.js.map +1 -0
- package/lib/settings/settings-rpc.d.ts +62 -0
- package/lib/settings/settings-rpc.d.ts.map +1 -0
- package/lib/settings/settings-rpc.js +90 -0
- package/lib/settings/settings-rpc.js.map +1 -0
- package/lib/settings/settings-service.d.ts +27 -0
- package/lib/settings/settings-service.d.ts.map +1 -0
- package/lib/settings/settings-service.js +103 -0
- package/lib/settings/settings-service.js.map +1 -0
- package/package.json +48 -6
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel orchestration for dsh-connect: a small, side-effect-free selector
|
|
3
|
+
* that activates a requested subset of channel adapters. Kept separate from the
|
|
4
|
+
* cordis entry so it is unit-testable without pulling any channel SDK.
|
|
5
|
+
* @module dsh-connect/settings/channels
|
|
6
|
+
*/
|
|
7
|
+
/** Built-in channels, in a stable order (for docs and the default selector). */
|
|
8
|
+
export const CHANNELS = ["feishu", "telegram", "dingtalk", "web"];
|
|
9
|
+
/**
|
|
10
|
+
* Activate the requested adapters into `ctx` (the `connect` service is
|
|
11
|
+
* expected to be present via the caller's `inject`). `channels` maps a channel
|
|
12
|
+
* name to its plugin `apply`. Unknown names are skipped with a warning; a
|
|
13
|
+
* channel whose `apply` throws is caught so one broken adapter never takes the
|
|
14
|
+
* others down.
|
|
15
|
+
*
|
|
16
|
+
* @returns the channel names that were started.
|
|
17
|
+
*/
|
|
18
|
+
export function activateChannels(ctx, config, channels) {
|
|
19
|
+
const cfg = config ?? {};
|
|
20
|
+
const defaults = cfg.channelDefaults ?? {};
|
|
21
|
+
const wanted = cfg.channels ?? CHANNELS;
|
|
22
|
+
const started = [];
|
|
23
|
+
for (const name of wanted) {
|
|
24
|
+
const apply = channels[name];
|
|
25
|
+
if (apply === undefined) {
|
|
26
|
+
ctx.logger?.warn?.(`connect: unknown channel "${name}"; skipped`);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const overrides = cfg[name] ?? {};
|
|
31
|
+
apply(ctx, { ...defaults, ...overrides });
|
|
32
|
+
started.push(name);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
ctx.logger?.warn?.(`connect: channel "${name}" failed to start: ${String(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return started;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Secret config-keys that live under a *dotted path* rather than at the channel
|
|
42
|
+
* config root, because the adapter reads them from a sub-object (e.g. dingtalk's
|
|
43
|
+
* bidirectional stream credentials read from `config.stream.clientId`). Keys
|
|
44
|
+
* absent from this map are injected flat. Used by `injectSecrets`.
|
|
45
|
+
*/
|
|
46
|
+
const NESTED_SECRET_KEYS = {
|
|
47
|
+
feishu: {},
|
|
48
|
+
telegram: {},
|
|
49
|
+
dingtalk: { clientId: "stream.clientId", clientSecret: "stream.clientSecret" },
|
|
50
|
+
web: {},
|
|
51
|
+
};
|
|
52
|
+
/** Copy `secrets` into `target`, writing nested keys at their dotted path. */
|
|
53
|
+
function mergeSecrets(target, secrets, nested) {
|
|
54
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
55
|
+
const path = nested[key];
|
|
56
|
+
if (path === undefined) {
|
|
57
|
+
target[key] = value;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const segments = path.split(".");
|
|
61
|
+
let node = target;
|
|
62
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
63
|
+
const segment = segments[i];
|
|
64
|
+
const next = node[segment];
|
|
65
|
+
if (next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
66
|
+
node[segment] = {};
|
|
67
|
+
}
|
|
68
|
+
node = node[segment];
|
|
69
|
+
}
|
|
70
|
+
node[segments[segments.length - 1]] = value;
|
|
71
|
+
}
|
|
72
|
+
return target;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Return a shallow-cloned config whose enabled channels are enriched with the
|
|
76
|
+
* secrets returned by `getSecrets` (stored secrets win over channel config).
|
|
77
|
+
* Used by `apply` to feed store-backed credentials into each adapter while
|
|
78
|
+
* leaving the caller's config object untouched. Secrets are placed at the slot
|
|
79
|
+
* each adapter expects — flat keys at the channel root, nested keys (dingtalk's
|
|
80
|
+
* `stream.clientId`/`stream.clientSecret`) under their sub-object.
|
|
81
|
+
*/
|
|
82
|
+
export async function injectSecrets(config, enabled, getSecrets) {
|
|
83
|
+
const out = { ...config };
|
|
84
|
+
for (const name of enabled) {
|
|
85
|
+
try {
|
|
86
|
+
const secrets = await getSecrets(name);
|
|
87
|
+
if (Object.keys(secrets).length > 0) {
|
|
88
|
+
// Deep-clone the channel config so we never mutate the caller's nested
|
|
89
|
+
// objects (e.g. `dingtalk.stream`) when writing nested secret keys.
|
|
90
|
+
const base = out[name] ?? {};
|
|
91
|
+
const cloned = structuredClone(base);
|
|
92
|
+
out[name] = mergeSecrets(cloned, secrets, NESTED_SECRET_KEYS[name]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// A channel's secret resolution failing should never block activation.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=channels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/settings/channels.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side credential store for dsh-connect channels. Adapts the DSH
|
|
3
|
+
* `credentials` provider (`resolve/describe/set/unset`, env-style refs) into a
|
|
4
|
+
* per-channel interface, so secrets live in the DSH credential store instead of
|
|
5
|
+
* the plugin config (mirroring dsh-im's `credential-store.mjs`). The web
|
|
6
|
+
* settings pane only ever sees `configured` booleans, never secret values.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-connect/settings/credential-store
|
|
9
|
+
*/
|
|
10
|
+
import { type ChannelName } from "./channels.js";
|
|
11
|
+
/** The seam the DSH host provides; `describe` reports source metadata. */
|
|
12
|
+
export interface CredentialsProvider {
|
|
13
|
+
resolve(ref: string): Promise<string | null | undefined>;
|
|
14
|
+
describe(ref: string): Promise<{
|
|
15
|
+
configured: boolean;
|
|
16
|
+
[k: string]: unknown;
|
|
17
|
+
}>;
|
|
18
|
+
set(ref: string, value: string): Promise<void>;
|
|
19
|
+
unset(ref: string): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/** Credential references owned by each channel (alphabetical, stable). */
|
|
22
|
+
export declare const CREDENTIAL_REFS: Record<ChannelName, readonly string[]>;
|
|
23
|
+
/** Maps each channel's secret *config key* to its credential ref, so the
|
|
24
|
+
* settings service and activation can enrich a channel config from the store. */
|
|
25
|
+
export declare const CHANNEL_SECRET_KEYS: Record<ChannelName, Record<string, string>>;
|
|
26
|
+
/** A channel credential store bound to a DSH provider. */
|
|
27
|
+
export interface CredentialStore {
|
|
28
|
+
/** Whether every ref for the channel is configured (secrets present). */
|
|
29
|
+
configured(channel: ChannelName): Promise<boolean>;
|
|
30
|
+
/** Persist a channel secret under its ref. */
|
|
31
|
+
save(channel: ChannelName, values: Record<string, string>): Promise<void>;
|
|
32
|
+
/** Read the channel's stored secrets as { configKey: value }. */
|
|
33
|
+
get(channel: ChannelName): Promise<Record<string, string>>;
|
|
34
|
+
/** Remove every ref for the channel. */
|
|
35
|
+
clear(channel: ChannelName): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/** Build a per-channel credential store over a DSH `credentials` provider. */
|
|
38
|
+
export declare function createCredentialStore(provider: CredentialsProvider): CredentialStore;
|
|
39
|
+
//# sourceMappingURL=credential-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../src/settings/credential-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAE3D,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IAC9E,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,0EAA0E;AAC1E,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAKvB,CAAC;AAE7C;iFACiF;AACjF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E,CAAC;AAEH,0DAA0D;AAC1D,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,8CAA8C;IAC9C,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,iEAAiE;IACjE,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D,wCAAwC;IACxC,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAUD,8EAA8E;AAC9E,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,eAAe,CAqCpF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Credential references owned by each channel (alphabetical, stable). */
|
|
2
|
+
export const CREDENTIAL_REFS = Object.freeze({
|
|
3
|
+
feishu: ["DSH_CONNECT_FEISHU_APP_ID", "DSH_CONNECT_FEISHU_APP_SECRET"],
|
|
4
|
+
telegram: ["DSH_CONNECT_TELEGRAM_BOT_TOKEN"],
|
|
5
|
+
dingtalk: ["DSH_CONNECT_DINGTALK_WEBHOOK_URL", "DSH_CONNECT_DINGTALK_SECRET"],
|
|
6
|
+
web: [],
|
|
7
|
+
});
|
|
8
|
+
/** Maps each channel's secret *config key* to its credential ref, so the
|
|
9
|
+
* settings service and activation can enrich a channel config from the store. */
|
|
10
|
+
export const CHANNEL_SECRET_KEYS = Object.freeze({
|
|
11
|
+
feishu: { appId: "DSH_CONNECT_FEISHU_APP_ID", appSecret: "DSH_CONNECT_FEISHU_APP_SECRET" },
|
|
12
|
+
telegram: { botToken: "DSH_CONNECT_TELEGRAM_BOT_TOKEN" },
|
|
13
|
+
dingtalk: {
|
|
14
|
+
webhookUrl: "DSH_CONNECT_DINGTALK_WEBHOOK_URL",
|
|
15
|
+
secret: "DSH_CONNECT_DINGTALK_SECRET",
|
|
16
|
+
clientId: "DSH_CONNECT_DINGTALK_CLIENT_ID",
|
|
17
|
+
clientSecret: "DSH_CONNECT_DINGTALK_CLIENT_SECRET",
|
|
18
|
+
},
|
|
19
|
+
web: {},
|
|
20
|
+
});
|
|
21
|
+
function assertProvider(provider) {
|
|
22
|
+
for (const method of ["resolve", "describe", "set", "unset"]) {
|
|
23
|
+
if (typeof provider[method] !== "function") {
|
|
24
|
+
throw new TypeError(`A DSH credential provider with ${method}() is required`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Build a per-channel credential store over a DSH `credentials` provider. */
|
|
29
|
+
export function createCredentialStore(provider) {
|
|
30
|
+
assertProvider(provider);
|
|
31
|
+
const refs = (channel) => CREDENTIAL_REFS[channel] ?? [];
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
async configured(channel) {
|
|
34
|
+
const list = refs(channel);
|
|
35
|
+
if (list.length === 0)
|
|
36
|
+
return false;
|
|
37
|
+
const described = await Promise.all(list.map((ref) => provider.describe(ref)));
|
|
38
|
+
return described.every((d) => d.configured === true);
|
|
39
|
+
},
|
|
40
|
+
async save(channel, values) {
|
|
41
|
+
for (const ref of refs(channel)) {
|
|
42
|
+
const value = values[ref];
|
|
43
|
+
if (value !== undefined)
|
|
44
|
+
await provider.set(ref, value);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
async get(channel) {
|
|
48
|
+
const map = CHANNEL_SECRET_KEYS[channel] ?? {};
|
|
49
|
+
const entries = await Promise.all(Object.entries(map).map(async ([configKey, ref]) => {
|
|
50
|
+
const value = await provider.resolve(ref);
|
|
51
|
+
return [configKey, value];
|
|
52
|
+
}));
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [configKey, value] of entries) {
|
|
55
|
+
if (typeof value === "string" && value.length > 0)
|
|
56
|
+
out[configKey] = value;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
},
|
|
60
|
+
async clear(channel) {
|
|
61
|
+
const outcomes = await Promise.allSettled(refs(channel).map((ref) => provider.unset(ref)));
|
|
62
|
+
if (outcomes.some((o) => o.status === "rejected")) {
|
|
63
|
+
throw new Error(`Unable to clear ${channel} credentials.`);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/settings/credential-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel for the dsh-connect web-settings stack: channel orchestration, the DSH
|
|
3
|
+
* credential-store adapter, the host RPC facade, the persistence service, and
|
|
4
|
+
* the client-side model/RPC helpers. Imported by the merged entry; also the
|
|
5
|
+
* `dsh-connect/settings` subpath export.
|
|
6
|
+
* @module dsh-connect/settings
|
|
7
|
+
*/
|
|
8
|
+
export * from "./channels.js";
|
|
9
|
+
export * from "./credential-store.js";
|
|
10
|
+
export * from "./settings-rpc.js";
|
|
11
|
+
export * from "./settings-service.js";
|
|
12
|
+
export * from "./settings-model.js";
|
|
13
|
+
export * from "./rpc-client.js";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel for the dsh-connect web-settings stack: channel orchestration, the DSH
|
|
3
|
+
* credential-store adapter, the host RPC facade, the persistence service, and
|
|
4
|
+
* the client-side model/RPC helpers. Imported by the merged entry; also the
|
|
5
|
+
* `dsh-connect/settings` subpath export.
|
|
6
|
+
* @module dsh-connect/settings
|
|
7
|
+
*/
|
|
8
|
+
export * from "./channels.js";
|
|
9
|
+
export * from "./credential-store.js";
|
|
10
|
+
export * from "./settings-rpc.js";
|
|
11
|
+
export * from "./settings-service.js";
|
|
12
|
+
export * from "./settings-model.js";
|
|
13
|
+
export * from "./rpc-client.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side RPC helper for the dsh-connect web settings pane.
|
|
3
|
+
*
|
|
4
|
+
* The frontend talks to the host over the `/dsh-connect` channel; the channel
|
|
5
|
+
* returns `{ ok, value }`/`{ ok:false, error }` envelopes (see `settings-rpc.js`).
|
|
6
|
+
* This helper unwraps the envelope and throws a typed error on failure, so the
|
|
7
|
+
* UI can catch it and render a message. Pure + dependency-free for easy testing.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-connect/settings/rpc-client
|
|
10
|
+
*/
|
|
11
|
+
import type { SettingsSnapshot } from "./settings-rpc.js";
|
|
12
|
+
/** The `ctx.connection.rpc.call` signature the settings pane uses. */
|
|
13
|
+
export type RpcCall = (endpoint: string, payload?: unknown, signal?: AbortSignal) => Promise<{
|
|
14
|
+
ok: boolean;
|
|
15
|
+
value?: unknown;
|
|
16
|
+
error?: {
|
|
17
|
+
code: string;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
/** A typed RPC failure, surfaced to the settings UI. */
|
|
22
|
+
export declare class RpcError extends Error {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
constructor(code: string, message: string);
|
|
25
|
+
}
|
|
26
|
+
/** Invoke one endpoint and unwrap the envelope (throws `RpcError` on failure). */
|
|
27
|
+
export declare function callRpc<T = unknown>(rpcCall: RpcCall, endpoint: string, payload?: unknown, signal?: AbortSignal): Promise<T>;
|
|
28
|
+
/** Load the current settings snapshot via `settings.get`. */
|
|
29
|
+
export declare function loadSettings(rpcCall: RpcCall): Promise<SettingsSnapshot>;
|
|
30
|
+
/** Persist the settings via `settings.save`. */
|
|
31
|
+
export declare function saveSettings(rpcCall: RpcCall, config: Record<string, unknown>): Promise<SettingsSnapshot>;
|
|
32
|
+
/** Persist channel secrets to the credential store via `credentials.save`. */
|
|
33
|
+
export declare function saveCredentials(rpcCall: RpcCall, channel: string, values: Record<string, string>): Promise<SettingsSnapshot>;
|
|
34
|
+
/** Ask the host for a fresh status snapshot via `settings.status`. */
|
|
35
|
+
export declare function loadStatus(rpcCall: RpcCall): Promise<SettingsSnapshot>;
|
|
36
|
+
//# sourceMappingURL=rpc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/settings/rpc-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,CACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC;AAE1F,wDAAwD;AACxD,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,CAAC,GAAG,OAAO,EACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAExE;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEzG;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side RPC helper for the dsh-connect web settings pane.
|
|
3
|
+
*
|
|
4
|
+
* The frontend talks to the host over the `/dsh-connect` channel; the channel
|
|
5
|
+
* returns `{ ok, value }`/`{ ok:false, error }` envelopes (see `settings-rpc.js`).
|
|
6
|
+
* This helper unwraps the envelope and throws a typed error on failure, so the
|
|
7
|
+
* UI can catch it and render a message. Pure + dependency-free for easy testing.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-connect/settings/rpc-client
|
|
10
|
+
*/
|
|
11
|
+
/** A typed RPC failure, surfaced to the settings UI. */
|
|
12
|
+
export class RpcError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "RpcError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Invoke one endpoint and unwrap the envelope (throws `RpcError` on failure). */
|
|
21
|
+
export function callRpc(rpcCall, endpoint, payload, signal) {
|
|
22
|
+
return rpcCall(endpoint, payload, signal).then((res) => {
|
|
23
|
+
if (res.ok)
|
|
24
|
+
return res.value;
|
|
25
|
+
const code = res.error?.code ?? "settings-failed";
|
|
26
|
+
throw new RpcError(code, res.error?.message ?? code);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Load the current settings snapshot via `settings.get`. */
|
|
30
|
+
export function loadSettings(rpcCall) {
|
|
31
|
+
return callRpc(rpcCall, "settings.get", {});
|
|
32
|
+
}
|
|
33
|
+
/** Persist the settings via `settings.save`. */
|
|
34
|
+
export function saveSettings(rpcCall, config) {
|
|
35
|
+
return callRpc(rpcCall, "settings.save", config);
|
|
36
|
+
}
|
|
37
|
+
/** Persist channel secrets to the credential store via `credentials.save`. */
|
|
38
|
+
export function saveCredentials(rpcCall, channel, values) {
|
|
39
|
+
return callRpc(rpcCall, "credentials.save", { channel, values });
|
|
40
|
+
}
|
|
41
|
+
/** Ask the host for a fresh status snapshot via `settings.status`. */
|
|
42
|
+
export function loadStatus(rpcCall) {
|
|
43
|
+
return callRpc(rpcCall, "settings.status", {});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=rpc-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../../src/settings/rpc-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,wDAAwD;AACxD,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAS;IACtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,kFAAkF;AAClF,MAAM,UAAU,OAAO,CACrB,OAAgB,EAChB,QAAgB,EAChB,OAAiB,EACjB,MAAoB;IAEpB,OAAO,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACrD,IAAI,GAAG,CAAC,EAAE;YAAE,OAAO,GAAG,CAAC,KAAU,CAAC;QAClC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,IAAI,iBAAiB,CAAC;QAClD,MAAM,IAAI,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,OAAO,OAAO,CAAmB,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,MAA+B;IAC5E,OAAO,OAAO,CAAmB,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC;AACrE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAC7B,OAAgB,EAChB,OAAe,EACf,MAA8B;IAE9B,OAAO,OAAO,CAAmB,OAAO,EAAE,kBAAkB,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,OAAO,OAAO,CAAmB,OAAO,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { ChannelName } from "./channels.js";
|
|
2
|
+
import type { SettingsSnapshot } from "./settings-rpc.js";
|
|
3
|
+
/** Secret config-keys per channel, for rendering secret inputs. */
|
|
4
|
+
export declare const CHANNEL_SECRET_FIELDS: Record<ChannelName, string[]>;
|
|
5
|
+
/**
|
|
6
|
+
* A non-secret, user-editable per-channel config field. `kind` drives how the
|
|
7
|
+
* pane renders the control and how `coerceConfigValue` normalizes the raw input
|
|
8
|
+
* (text → string, number → finite number, boolean → false on empty, select →
|
|
9
|
+
* one of `options`).
|
|
10
|
+
*/
|
|
11
|
+
export interface ConfigField {
|
|
12
|
+
key: string;
|
|
13
|
+
kind: 'text' | 'number' | 'boolean' | 'select';
|
|
14
|
+
options?: string[];
|
|
15
|
+
/** Human label; defaults to the key if absent. */
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Non-secret per-channel fields the pane exposes, mirroring the keys used by
|
|
20
|
+
* each channel adapter config (see `examples/profile-cordis.patch.yml`). Order
|
|
21
|
+
* is the render order; keep secrets separate (`CHANNEL_SECRET_FIELDS`).
|
|
22
|
+
*/
|
|
23
|
+
export declare const CHANNEL_CONFIG_FIELDS: {
|
|
24
|
+
readonly feishu: readonly [{
|
|
25
|
+
readonly key: "transport";
|
|
26
|
+
readonly kind: "select";
|
|
27
|
+
readonly options: ["websocket", "webhook"];
|
|
28
|
+
readonly label: "transport";
|
|
29
|
+
}, {
|
|
30
|
+
readonly key: "requireMention";
|
|
31
|
+
readonly kind: "boolean";
|
|
32
|
+
readonly label: "requireMention";
|
|
33
|
+
}, {
|
|
34
|
+
readonly key: "dmMode";
|
|
35
|
+
readonly kind: "select";
|
|
36
|
+
readonly options: ["open", "allowlist", "pair", "disabled"];
|
|
37
|
+
readonly label: "dmMode";
|
|
38
|
+
}, {
|
|
39
|
+
readonly key: "language";
|
|
40
|
+
readonly kind: "select";
|
|
41
|
+
readonly options: ["zh", "en"];
|
|
42
|
+
readonly label: "language";
|
|
43
|
+
}, {
|
|
44
|
+
readonly key: "webhookPort";
|
|
45
|
+
readonly kind: "number";
|
|
46
|
+
readonly label: "webhookPort";
|
|
47
|
+
}, {
|
|
48
|
+
readonly key: "webhookPath";
|
|
49
|
+
readonly kind: "text";
|
|
50
|
+
readonly label: "webhookPath";
|
|
51
|
+
}];
|
|
52
|
+
readonly telegram: readonly [{
|
|
53
|
+
readonly key: "requireMention";
|
|
54
|
+
readonly kind: "boolean";
|
|
55
|
+
readonly label: "requireMention";
|
|
56
|
+
}, {
|
|
57
|
+
readonly key: "language";
|
|
58
|
+
readonly kind: "select";
|
|
59
|
+
readonly options: ["zh", "en"];
|
|
60
|
+
readonly label: "language";
|
|
61
|
+
}, {
|
|
62
|
+
readonly key: "pollingTimeoutSeconds";
|
|
63
|
+
readonly kind: "number";
|
|
64
|
+
readonly label: "pollingTimeoutSeconds";
|
|
65
|
+
}, {
|
|
66
|
+
readonly key: "baseUrl";
|
|
67
|
+
readonly kind: "text";
|
|
68
|
+
readonly label: "baseUrl";
|
|
69
|
+
}];
|
|
70
|
+
readonly dingtalk: readonly [{
|
|
71
|
+
readonly key: "language";
|
|
72
|
+
readonly kind: "select";
|
|
73
|
+
readonly options: ["zh", "en"];
|
|
74
|
+
readonly label: "language";
|
|
75
|
+
}, {
|
|
76
|
+
readonly key: "defaultAt";
|
|
77
|
+
readonly kind: "text";
|
|
78
|
+
readonly label: "defaultAt";
|
|
79
|
+
}];
|
|
80
|
+
readonly web: readonly [{
|
|
81
|
+
readonly key: "pollIntervalMs";
|
|
82
|
+
readonly kind: "number";
|
|
83
|
+
readonly label: "pollIntervalMs";
|
|
84
|
+
}];
|
|
85
|
+
};
|
|
86
|
+
/** Channel-agnostic keys applied to every channel that doesn't set its own. */
|
|
87
|
+
export declare const CHANNEL_DEFAULT_FIELDS: readonly ConfigField[];
|
|
88
|
+
/** Normalize a raw form input for a field `kind` (empty → undefined = not saved). */
|
|
89
|
+
export declare function coerceConfigValue(kind: ConfigField['kind'], raw: unknown): unknown;
|
|
90
|
+
/** The edit form the pane works on. */
|
|
91
|
+
export interface SettingsForm {
|
|
92
|
+
channels: ChannelName[];
|
|
93
|
+
channelDefaults: Record<string, unknown>;
|
|
94
|
+
channelConfigs: Record<string, Record<string, unknown>>;
|
|
95
|
+
secrets: Record<string, Record<string, string>>;
|
|
96
|
+
settingsStatePath?: string;
|
|
97
|
+
}
|
|
98
|
+
/** Build the initial form from a snapshot. */
|
|
99
|
+
export declare function snapshotToForm(snapshot: SettingsSnapshot): SettingsForm;
|
|
100
|
+
/** Build the non-secret config to persist via `settings.save`. */
|
|
101
|
+
export declare function buildConfigSave(form: SettingsForm): Record<string, unknown>;
|
|
102
|
+
/** Build the `credentials.save` payloads for every channel with secret values. */
|
|
103
|
+
export declare function buildCredentialSaves(form: SettingsForm): {
|
|
104
|
+
channel: string;
|
|
105
|
+
values: Record<string, string>;
|
|
106
|
+
}[];
|
|
107
|
+
//# sourceMappingURL=settings-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-model.d.ts","sourceRoot":"","sources":["../../src/settings/settings-model.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,EAE7B,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsB8B,CAAC;AAEjE,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAE,SAAS,WAAW,EAGxD,CAAC;AAEF,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAclF;AAED,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,YAAY,CAYvE;AASD,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAU3E;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAAE,CAM9G"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side settings model: pure, dependency-free helpers that map between the
|
|
3
|
+
* host snapshot and the edit form, and build the save payloads. Kept separate from
|
|
4
|
+
* the React component so the client settings logic is unit-testable anywhere.
|
|
5
|
+
* The host-only secret-ref mapping lives on the server; the client only carries
|
|
6
|
+
* field names.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-connect/settings/settings-model
|
|
9
|
+
*/
|
|
10
|
+
import { CHANNEL_SECRET_KEYS } from "./credential-store.js";
|
|
11
|
+
/** Secret config-keys per channel, for rendering secret inputs. */
|
|
12
|
+
export const CHANNEL_SECRET_FIELDS = Object.fromEntries(Object.entries(CHANNEL_SECRET_KEYS).map(([ch, map]) => [ch, Object.keys(map ?? {})]));
|
|
13
|
+
/**
|
|
14
|
+
* Non-secret per-channel fields the pane exposes, mirroring the keys used by
|
|
15
|
+
* each channel adapter config (see `examples/profile-cordis.patch.yml`). Order
|
|
16
|
+
* is the render order; keep secrets separate (`CHANNEL_SECRET_FIELDS`).
|
|
17
|
+
*/
|
|
18
|
+
export const CHANNEL_CONFIG_FIELDS = {
|
|
19
|
+
feishu: [
|
|
20
|
+
{ key: 'transport', kind: 'select', options: ['websocket', 'webhook'], label: 'transport' },
|
|
21
|
+
{ key: 'requireMention', kind: 'boolean', label: 'requireMention' },
|
|
22
|
+
{ key: 'dmMode', kind: 'select', options: ['open', 'allowlist', 'pair', 'disabled'], label: 'dmMode' },
|
|
23
|
+
{ key: 'language', kind: 'select', options: ['zh', 'en'], label: 'language' },
|
|
24
|
+
{ key: 'webhookPort', kind: 'number', label: 'webhookPort' },
|
|
25
|
+
{ key: 'webhookPath', kind: 'text', label: 'webhookPath' },
|
|
26
|
+
],
|
|
27
|
+
telegram: [
|
|
28
|
+
{ key: 'requireMention', kind: 'boolean', label: 'requireMention' },
|
|
29
|
+
{ key: 'language', kind: 'select', options: ['zh', 'en'], label: 'language' },
|
|
30
|
+
{ key: 'pollingTimeoutSeconds', kind: 'number', label: 'pollingTimeoutSeconds' },
|
|
31
|
+
{ key: 'baseUrl', kind: 'text', label: 'baseUrl' },
|
|
32
|
+
],
|
|
33
|
+
dingtalk: [
|
|
34
|
+
{ key: 'language', kind: 'select', options: ['zh', 'en'], label: 'language' },
|
|
35
|
+
{ key: 'defaultAt', kind: 'text', label: 'defaultAt' },
|
|
36
|
+
],
|
|
37
|
+
web: [
|
|
38
|
+
{ key: 'pollIntervalMs', kind: 'number', label: 'pollIntervalMs' },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
/** Channel-agnostic keys applied to every channel that doesn't set its own. */
|
|
42
|
+
export const CHANNEL_DEFAULT_FIELDS = [
|
|
43
|
+
{ key: 'language', kind: 'select', options: ['zh', 'en'], label: 'language' },
|
|
44
|
+
{ key: 'notifyLevel', kind: 'select', options: ['full', 'important', 'result'], label: 'notifyLevel' },
|
|
45
|
+
];
|
|
46
|
+
/** Normalize a raw form input for a field `kind` (empty → undefined = not saved). */
|
|
47
|
+
export function coerceConfigValue(kind, raw) {
|
|
48
|
+
if (raw === undefined || raw === null || raw === '')
|
|
49
|
+
return undefined;
|
|
50
|
+
switch (kind) {
|
|
51
|
+
case 'number': {
|
|
52
|
+
const n = Number(raw);
|
|
53
|
+
return Number.isFinite(n) ? n : undefined;
|
|
54
|
+
}
|
|
55
|
+
case 'boolean':
|
|
56
|
+
return typeof raw === 'boolean' ? raw : raw === true || raw === 'true';
|
|
57
|
+
case 'select':
|
|
58
|
+
return String(raw);
|
|
59
|
+
default:
|
|
60
|
+
return String(raw);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Build the initial form from a snapshot. */
|
|
64
|
+
export function snapshotToForm(snapshot) {
|
|
65
|
+
const config = snapshot.config ?? {};
|
|
66
|
+
const channels = (snapshot.enabled ?? []);
|
|
67
|
+
const channelConfigs = {};
|
|
68
|
+
for (const ch of channels)
|
|
69
|
+
channelConfigs[ch] = (config[ch] ?? {});
|
|
70
|
+
return {
|
|
71
|
+
channels,
|
|
72
|
+
channelDefaults: (config.channelDefaults ?? {}),
|
|
73
|
+
channelConfigs,
|
|
74
|
+
secrets: {},
|
|
75
|
+
settingsStatePath: config.settingsStatePath,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Drop keys whose value is `undefined`/`null` so cleared inputs aren't persisted. */
|
|
79
|
+
function stripEmpty(obj) {
|
|
80
|
+
const out = {};
|
|
81
|
+
for (const [k, v] of Object.entries(obj))
|
|
82
|
+
if (v !== undefined && v !== null)
|
|
83
|
+
out[k] = v;
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/** Build the non-secret config to persist via `settings.save`. */
|
|
87
|
+
export function buildConfigSave(form) {
|
|
88
|
+
const config = { channels: form.channels };
|
|
89
|
+
const defaults = stripEmpty(form.channelDefaults ?? {});
|
|
90
|
+
if (Object.keys(defaults).length > 0)
|
|
91
|
+
config.channelDefaults = defaults;
|
|
92
|
+
for (const [ch, cfg] of Object.entries(form.channelConfigs ?? {})) {
|
|
93
|
+
const clean = stripEmpty(cfg ?? {});
|
|
94
|
+
if (Object.keys(clean).length > 0)
|
|
95
|
+
config[ch] = clean;
|
|
96
|
+
}
|
|
97
|
+
if (form.settingsStatePath)
|
|
98
|
+
config.settingsStatePath = form.settingsStatePath;
|
|
99
|
+
return config;
|
|
100
|
+
}
|
|
101
|
+
/** Build the `credentials.save` payloads for every channel with secret values. */
|
|
102
|
+
export function buildCredentialSaves(form) {
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const [ch, values] of Object.entries(form.secrets ?? {})) {
|
|
105
|
+
if (values && Object.keys(values).length > 0)
|
|
106
|
+
out.push({ channel: ch, values });
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=settings-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-model.js","sourceRoot":"","sources":["../../src/settings/settings-model.ts"],"names":[],"mappings":"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"}
|