dsh-connect 0.8.0 → 0.9.2

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 (117) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +236 -18
  3. package/README.zh.md +185 -18
  4. package/client/client.js +517 -84
  5. package/client/client.js.map +4 -4
  6. package/client/locale.mjs +227 -0
  7. package/client/panel-state.mjs +54 -0
  8. package/client/settings-client.mjs +282 -61
  9. package/docs/images/settings-advanced-en.png +0 -0
  10. package/docs/images/settings-advanced-zh.png +0 -0
  11. package/docs/images/settings-defaults-en.png +0 -0
  12. package/docs/images/settings-defaults-zh.png +0 -0
  13. package/docs/images/settings-overview-en.png +0 -0
  14. package/docs/images/settings-overview-zh.png +0 -0
  15. package/lib/binding.d.ts.map +1 -1
  16. package/lib/binding.js +2 -2
  17. package/lib/binding.js.map +1 -1
  18. package/lib/channels/dingtalk/index.d.ts +49 -49
  19. package/lib/channels/dingtalk/index.d.ts.map +1 -1
  20. package/lib/channels/dingtalk/message.d.ts.map +1 -1
  21. package/lib/channels/dingtalk/message.js +7 -0
  22. package/lib/channels/dingtalk/message.js.map +1 -1
  23. package/lib/channels/feishu/adapter.d.ts +14 -13
  24. package/lib/channels/feishu/adapter.d.ts.map +1 -1
  25. package/lib/channels/feishu/adapter.js +122 -56
  26. package/lib/channels/feishu/adapter.js.map +1 -1
  27. package/lib/channels/feishu/i18n.d.ts +2 -0
  28. package/lib/channels/feishu/i18n.d.ts.map +1 -1
  29. package/lib/channels/feishu/i18n.js +2 -0
  30. package/lib/channels/feishu/i18n.js.map +1 -1
  31. package/lib/channels/feishu/index.d.ts +48 -27
  32. package/lib/channels/feishu/index.d.ts.map +1 -1
  33. package/lib/channels/feishu/index.js +32 -6
  34. package/lib/channels/feishu/index.js.map +1 -1
  35. package/lib/channels/telegram/adapter.d.ts.map +1 -1
  36. package/lib/channels/telegram/adapter.js +7 -1
  37. package/lib/channels/telegram/adapter.js.map +1 -1
  38. package/lib/channels/telegram/index.d.ts +13 -13
  39. package/lib/channels/telegram/index.d.ts.map +1 -1
  40. package/lib/channels/web/adapter.d.ts +3 -1
  41. package/lib/channels/web/adapter.d.ts.map +1 -1
  42. package/lib/channels/web/adapter.js +3 -1
  43. package/lib/channels/web/adapter.js.map +1 -1
  44. package/lib/channels/web/index.d.ts +5 -5
  45. package/lib/channels/web/index.d.ts.map +1 -1
  46. package/lib/chat-key.d.ts +29 -0
  47. package/lib/chat-key.d.ts.map +1 -0
  48. package/lib/chat-key.js +38 -0
  49. package/lib/chat-key.js.map +1 -0
  50. package/lib/index.d.ts +57 -63
  51. package/lib/index.d.ts.map +1 -1
  52. package/lib/index.js +330 -55
  53. package/lib/index.js.map +1 -1
  54. package/lib/interaction.d.ts +90 -50
  55. package/lib/interaction.d.ts.map +1 -1
  56. package/lib/interaction.js +233 -272
  57. package/lib/interaction.js.map +1 -1
  58. package/lib/retry.d.ts.map +1 -1
  59. package/lib/retry.js +7 -1
  60. package/lib/retry.js.map +1 -1
  61. package/lib/runner.d.ts +54 -0
  62. package/lib/runner.d.ts.map +1 -1
  63. package/lib/runner.js +158 -23
  64. package/lib/runner.js.map +1 -1
  65. package/lib/scheduler.d.ts.map +1 -1
  66. package/lib/scheduler.js +2 -2
  67. package/lib/scheduler.js.map +1 -1
  68. package/lib/service.d.ts +16 -0
  69. package/lib/service.d.ts.map +1 -1
  70. package/lib/service.js +46 -4
  71. package/lib/service.js.map +1 -1
  72. package/lib/settings/channel-runtime.d.ts +75 -0
  73. package/lib/settings/channel-runtime.d.ts.map +1 -0
  74. package/lib/settings/channel-runtime.js +165 -0
  75. package/lib/settings/channel-runtime.js.map +1 -0
  76. package/lib/settings/channels.d.ts +11 -0
  77. package/lib/settings/channels.d.ts.map +1 -1
  78. package/lib/settings/channels.js +31 -0
  79. package/lib/settings/channels.js.map +1 -1
  80. package/lib/settings/credential-store.d.ts +52 -6
  81. package/lib/settings/credential-store.d.ts.map +1 -1
  82. package/lib/settings/credential-store.js +92 -18
  83. package/lib/settings/credential-store.js.map +1 -1
  84. package/lib/settings/legacy-import.d.ts +190 -0
  85. package/lib/settings/legacy-import.d.ts.map +1 -0
  86. package/lib/settings/legacy-import.js +373 -0
  87. package/lib/settings/legacy-import.js.map +1 -0
  88. package/lib/settings/namespace.d.ts +263 -0
  89. package/lib/settings/namespace.d.ts.map +1 -0
  90. package/lib/settings/namespace.js +287 -0
  91. package/lib/settings/namespace.js.map +1 -0
  92. package/lib/settings/secret-disclosure.d.ts +50 -0
  93. package/lib/settings/secret-disclosure.d.ts.map +1 -0
  94. package/lib/settings/secret-disclosure.js +132 -0
  95. package/lib/settings/secret-disclosure.js.map +1 -0
  96. package/lib/settings/settings-model.d.ts +17 -0
  97. package/lib/settings/settings-model.d.ts.map +1 -1
  98. package/lib/settings/settings-model.js +18 -1
  99. package/lib/settings/settings-model.js.map +1 -1
  100. package/lib/settings/settings-rpc.d.ts +71 -3
  101. package/lib/settings/settings-rpc.d.ts.map +1 -1
  102. package/lib/settings/settings-rpc.js +167 -24
  103. package/lib/settings/settings-rpc.js.map +1 -1
  104. package/lib/settings/settings-service.d.ts +22 -1
  105. package/lib/settings/settings-service.d.ts.map +1 -1
  106. package/lib/settings/settings-service.js +145 -8
  107. package/lib/settings/settings-service.js.map +1 -1
  108. package/lib/state-dir.d.ts +28 -0
  109. package/lib/state-dir.d.ts.map +1 -0
  110. package/lib/state-dir.js +33 -0
  111. package/lib/state-dir.js.map +1 -0
  112. package/lib/stream.d.ts.map +1 -1
  113. package/lib/stream.js +3 -2
  114. package/lib/stream.js.map +1 -1
  115. package/lib/types.d.ts +14 -1
  116. package/lib/types.d.ts.map +1 -1
  117. package/package.json +13 -10
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Which channel cards the settings pane shows open, and which fields hide behind
3
+ * each card's "advanced" fold.
4
+ *
5
+ * Dependency-free and separate from the React component for the same reason
6
+ * `locale.mjs` is: these rules are the interesting part, they are pure, and a
7
+ * test can pin them without a DOM. The bundle test in particular cannot — it
8
+ * renders the component with pre-supplied hook values, so it can never observe
9
+ * what the component *initializes* to. Only this module can answer that.
10
+ */
11
+
12
+ /**
13
+ * Fields that are set once and then left alone, grouped by channel.
14
+ *
15
+ * These are what makes the pane tall: with every channel expanded the pane
16
+ * renders 20 field blocks, each a label + input + preview + hint. Folding the
17
+ * rarely-touched ones behind a second-level disclosure keeps the common case —
18
+ * credentials plus the handful of behavioural switches — on one screen.
19
+ * `test/panel-state.test.mjs` asserts every key here really exists in
20
+ * `CHANNEL_CONFIG_FIELDS`, because a typo would silently drop a field from the
21
+ * UI rather than fail anything.
22
+ */
23
+ export const ADVANCED_KEYS = {
24
+ feishu: ['webhookPort', 'webhookPath'],
25
+ telegram: ['pollingTimeoutSeconds', 'baseUrl'],
26
+ dingtalk: ['defaultAt'],
27
+ web: ['pollIntervalMs'],
28
+ };
29
+
30
+ /** True when `key` is one of `channel`'s low-frequency fields. */
31
+ export function isAdvanced(channel, key) {
32
+ return (ADVANCED_KEYS[channel] ?? []).includes(key);
33
+ }
34
+
35
+ /**
36
+ * The channels whose card starts open: the enabled ones, or — when nothing is
37
+ * enabled at all — the first channel, so the pane never opens as a wall of
38
+ * collapsed headers with no contents in sight.
39
+ *
40
+ * Unknown names are filtered out rather than trusted: `enabled` comes from the
41
+ * host and would otherwise open a card that does not exist.
42
+ */
43
+ export function initialOpenChannels(enabled, all) {
44
+ const open = (enabled ?? []).filter((ch) => all.includes(ch));
45
+ return new Set(open.length > 0 ? open : all.slice(0, 1));
46
+ }
47
+
48
+ /** Toggle one key in a Set, returning a new Set (never mutating the argument). */
49
+ export function toggleInSet(set, key) {
50
+ const next = new Set(set);
51
+ if (next.has(key)) next.delete(key);
52
+ else next.add(key);
53
+ return next;
54
+ }
@@ -8,46 +8,93 @@
8
8
  * `settings-model.js` / `rpc-client.js` helpers; this component is a thin
9
9
  * renderer. Build the lib first (`pnpm --filter dsh-connect build`), then
10
10
  * bundle this in a `dsh web` shell (Vite).
11
+ *
12
+ * The pane is an accordion, not a flat list. Four channels with every field
13
+ * visible is ~2000px of content in a ~690px scroller, so the channel cards
14
+ * collapse, rarely-touched fields hide behind a second-level "advanced" fold,
15
+ * and a tab strip at the top jumps to (and opens) a channel. The rules for
16
+ * which cards start open live in `panel-state.mjs` — pure, and testable without
17
+ * a DOM, which matters because the bundle test's React stub can only supply a
18
+ * component's hook values, never observe what it initializes them to.
11
19
  */
12
20
  import * as React from 'react';
13
21
 
14
22
  import { SETTINGS_RPC_CHANNEL } from '../lib/settings/settings-rpc.js';
15
23
  import { loadSettings, saveSettings, saveCredentials } from '../lib/settings/rpc-client.js';
16
24
  import { snapshotToForm, buildConfigSave, buildCredentialSaves, CHANNEL_SECRET_FIELDS, CHANNEL_CONFIG_FIELDS, CHANNEL_DEFAULT_FIELDS, coerceConfigValue } from '../lib/settings/settings-model.js';
25
+ // Shared with the host, so the pane and the masking it displays can never
26
+ // disagree about which keys are confidential.
27
+ import { isMaskedSecret } from '../lib/settings/secret-disclosure.js';
28
+ import { LOCALES, tr, optionalText } from './locale.mjs';
29
+ import { initialOpenChannels, toggleInSet, isAdvanced } from './panel-state.mjs';
17
30
 
18
31
  export const name = 'dsh-connect-settings';
19
32
  export const inject = ['slots', 'connection', 'locale'];
20
33
  export const NS = 'dsh-connect';
21
34
 
22
- export const locale = {
23
- zh: { title: '连接设置', channels: '渠道', save: '保存', saved: '已保存', error: '保存失败', loading: '加载中', defaults: '公共默认(channelDefaults)', statePath: '设置文件', secret: '密钥', config: '配置', reachable: '已连接凭据', unreachable: '未配置凭据' },
24
- en: { title: 'Connection Settings', channels: 'Channels', save: 'Save', saved: 'Saved', error: 'Save failed', loading: 'Loading', defaults: 'Defaults (channelDefaults)', statePath: 'Settings file', secret: 'Secret', config: 'Config', reachable: 'Credential set', unreachable: 'Credential missing' },
25
- };
35
+ export const locale = LOCALES;
26
36
 
27
37
  const h = React.createElement;
28
38
 
39
+ /** The channels the pane knows about, in the order the cards are rendered. */
40
+ const ALL_CHANNELS = Object.keys(CHANNEL_SECRET_FIELDS);
41
+
29
42
  // Self-contained scoped stylesheet for the pane (injected once). The host's own
30
43
  // settings panes use per-feature CSS modules we can't import into this separate
31
44
  // bundle, so we ship our own consistent, theme-aware styles instead. Everything
32
45
  // is scoped under `.dsh-connect-settings` so nothing leaks into the shell.
46
+ //
47
+ // Two things here are load-bearing rather than cosmetic:
48
+ //
49
+ // 1. The `box-sizing` reset. Neither this bundle nor the host shell ships a
50
+ // global one, so under the default `content-box` a `.ds-input` with
51
+ // `width:100%` is ~20px (padding + border) *wider* than the grid column it
52
+ // sits in, and paints over the neighbouring column's text. That overflow is
53
+ // what "文字跟窗口重叠" actually was.
54
+ // 2. The host's theme tokens, with our old hex values as fallbacks. The host
55
+ // puts `--dsw-alias-*` on `body` / `body[data-ds-dark-theme]`, so inheriting
56
+ // them follows the user's in-app theme. A `prefers-color-scheme` block — what
57
+ // used to be here — ignores that choice entirely and repaints the pane black
58
+ // on a light shell for anyone whose OS is dark. Do not bring it back.
33
59
  const STYLE = `
34
- .dsh-connect-settings{--ds-bg:#ffffff;--ds-bg-sub:#f6f7f9;--ds-text:#1f2329;--ds-muted:#646a73;--ds-border:#e2e4e8;--ds-border-2:#c8cbd0;--ds-accent:#3b82f6;--ds-accent-fg:#ffffff;--ds-focus:rgba(59,130,246,.22);display:flex;flex-direction:column;gap:14px;min-width:280px;max-width:760px;color:var(--ds-text);font:13px/1.6 -apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;padding:4px 2px}
35
- @media(prefers-color-scheme:dark){.dsh-connect-settings{--ds-bg:#1a1b1e;--ds-bg-sub:#232428;--ds-text:#e8eaed;--ds-muted:#9aa0a6;--ds-border:#3b3d42;--ds-border-2:#5c5f66;--ds-accent:#5b9dff;--ds-accent-fg:#0f1115;--ds-focus:rgba(91,157,255,.25)}}
36
- .dsh-connect-settings .ds-card{border:1px solid var(--ds-border);border-radius:10px;background:var(--ds-bg);padding:12px 14px;display:flex;flex-direction:column;gap:10px}
60
+ .dsh-connect-settings,.dsh-connect-settings *,.dsh-connect-settings *::before,.dsh-connect-settings *::after{box-sizing:border-box}
61
+ .dsh-connect-settings{--ds-bg:var(--dsw-alias-bg-layer-3,#ffffff);--ds-bg-sub:var(--dsw-alias-bg-layer-1,#f6f7f9);--ds-text:var(--dsw-alias-label-primary,#1f2329);--ds-muted:var(--dsw-alias-label-tertiary,#646a73);--ds-border:var(--dsw-alias-border-l2,#e2e4e8);--ds-border-2:var(--dsw-alias-border-l3,#c8cbd0);--ds-accent:var(--dsw-alias-state-business-primary,#3b82f6);--ds-hover:var(--dsw-alias-interactive-bg-hover,#2631480f);display:flex;flex-direction:column;gap:12px;max-width:760px;color:var(--ds-text);font:13px/1.6 -apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
62
+ .dsh-connect-settings .ds-card{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ds-border);border-radius:10px;background:var(--ds-bg);padding:12px 14px}
37
63
  .dsh-connect-settings .ds-card-title{margin:0;font-size:13px;font-weight:600}
38
- .dsh-connect-settings .ds-channel{border:1px dashed var(--ds-border-2);border-radius:8px;padding:8px 12px;margin:0;background:var(--ds-bg-sub)}
39
- .dsh-connect-settings .ds-legend{padding:0 6px}
40
- .dsh-connect-settings .ds-legend label{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600}
41
- .dsh-connect-settings .ds-badge{margin-left:4px;font-size:11px;font-weight:500;padding:1px 8px;border-radius:99px;background:var(--ds-bg);color:var(--ds-muted);border:1px solid var(--ds-border-2)}
42
- .dsh-connect-settings .ds-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px 14px;margin-top:8px}
43
- .dsh-connect-settings .ds-field{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--ds-muted)}
44
- .dsh-connect-settings .ds-field.ds-check-field{flex-direction:row;align-items:center;gap:6px}
45
- .dsh-connect-settings .ds-check{width:16px;height:16px;accent-color:var(--ds-accent)}
46
- .dsh-connect-settings .ds-input{height:30px;width:100%;padding:0 9px;border:1px solid var(--ds-border-2);border-radius:6px;background:var(--ds-bg);color:var(--ds-text);font:inherit}
64
+ .dsh-connect-settings .ds-note{margin:0;font-size:11px;line-height:1.5;color:var(--ds-muted)}
65
+ .dsh-connect-settings .ds-tabs{display:flex;flex-wrap:wrap;gap:6px}
66
+ .dsh-connect-settings .ds-tab{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:1px solid var(--ds-border);border-radius:14px;background:transparent;color:var(--ds-text);font:inherit;font-size:12px;cursor:pointer}
67
+ .dsh-connect-settings .ds-tab:hover{background:var(--ds-hover)}
68
+ .dsh-connect-settings .ds-tab[aria-expanded=true]{border-color:var(--ds-accent);color:var(--ds-accent)}
69
+ .dsh-connect-settings .ds-dot{width:6px;height:6px;border-radius:50%;background:var(--ds-border-2);flex:none}
70
+ .dsh-connect-settings .ds-dot[data-on="1"]{background:var(--ds-accent)}
71
+ .dsh-connect-settings .ds-channel{border:1px solid var(--ds-border);border-radius:8px;background:var(--ds-bg-sub)}
72
+ .dsh-connect-settings .ds-channel-head{display:flex;align-items:center;gap:8px;padding:5px 10px}
73
+ .dsh-connect-settings .ds-enable{display:flex;align-items:center;flex:none}
74
+ .dsh-connect-settings .ds-channel-toggle{flex:1;min-width:0;display:flex;align-items:center;gap:8px;padding:5px 6px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:12px;font-weight:600;text-align:left;cursor:pointer}
75
+ .dsh-connect-settings .ds-channel-toggle:hover{background:var(--ds-hover);color:var(--ds-accent)}
76
+ .dsh-connect-settings .ds-channel-name{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
77
+ .dsh-connect-settings .ds-channel-body{display:flex;flex-direction:column;gap:8px;padding:0 10px 10px}
78
+ .dsh-connect-settings .ds-chevron{width:7px;height:7px;margin:-3px 4px 0 auto;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg);transition:transform .15s ease;flex:none}
79
+ .dsh-connect-settings [aria-expanded=true]>.ds-chevron,.dsh-connect-settings [aria-expanded=true] .ds-chevron{transform:rotate(-135deg);margin-top:2px}
80
+ .dsh-connect-settings .ds-badge{flex:none;font-size:11px;font-weight:500;padding:1px 8px;border-radius:99px;background:var(--ds-bg);color:var(--ds-muted);border:1px solid var(--ds-border-2)}
81
+ .dsh-connect-settings .ds-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px 14px}
82
+ .dsh-connect-settings .ds-field{display:flex;flex-direction:column;gap:4px;min-width:0}
83
+ .dsh-connect-settings .ds-control{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--ds-muted)}
84
+ .dsh-connect-settings .ds-control.ds-check-field{flex-direction:row;align-items:center;gap:6px}
85
+ .dsh-connect-settings .ds-hint{margin:0;font-size:11px;line-height:1.5;color:var(--ds-muted)}
86
+ .dsh-connect-settings .ds-preview{margin:0;font-size:11px;line-height:1.5;color:var(--ds-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;word-break:break-all}
87
+ .dsh-connect-settings .ds-preview b{font-weight:500;font-family:inherit;opacity:.75}
88
+ .dsh-connect-settings .ds-check{flex:none;width:16px;height:16px;accent-color:var(--ds-accent)}
89
+ .dsh-connect-settings .ds-input{height:30px;width:100%;min-width:0;padding:0 9px;border:1px solid var(--ds-border-2);border-radius:6px;background:var(--ds-bg);color:var(--ds-text);font:inherit}
47
90
  .dsh-connect-settings select.ds-input{cursor:pointer}
48
- .dsh-connect-settings .ds-input:focus{outline:none;border-color:var(--ds-accent);box-shadow:0 0 0 2px var(--ds-focus)}
49
- .dsh-connect-settings .ds-actions{display:flex;align-items:center;gap:12px;margin-top:2px}
50
- .dsh-connect-settings .ds-btn{height:32px;padding:0 18px;border:none;border-radius:6px;background:var(--ds-accent);color:var(--ds-accent-fg);font:inherit;font-weight:500;cursor:pointer}
91
+ .dsh-connect-settings .ds-input:focus{outline:none;border-color:var(--ds-accent);box-shadow:0 0 0 2px rgba(59,130,246,.25);box-shadow:0 0 0 2px color-mix(in srgb,var(--ds-accent) 25%,transparent)}
92
+ .dsh-connect-settings .ds-adv{display:flex;flex-direction:column;gap:8px}
93
+ .dsh-connect-settings .ds-advanced-toggle{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border:1px dashed var(--ds-border-2);border-radius:99px;background:transparent;color:var(--ds-muted);font:inherit;font-size:11px;cursor:pointer}
94
+ .dsh-connect-settings .ds-advanced-toggle:hover{background:var(--ds-hover);color:var(--ds-text)}
95
+ .dsh-connect-settings .ds-footer{position:sticky;bottom:0;z-index:1;display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--ds-border);border-radius:10px;background:var(--ds-bg)}
96
+ .dsh-connect-settings .ds-btn{height:32px;padding:0 18px;border:0;border-radius:6px;background:var(--dsw-alias-button-primary-fill,var(--ds-accent));color:var(--dsw-alias-label-primary-foreground,#ffffff);font:inherit;font-weight:500;cursor:pointer}
97
+ .dsh-connect-settings .ds-btn:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--ds-accent))}
51
98
  .dsh-connect-settings .ds-btn:disabled{opacity:.55;cursor:default}
52
99
  .dsh-connect-settings .ds-status{font-size:12px;color:var(--ds-muted)}
53
100
  `;
@@ -61,29 +108,138 @@ function injectStyles() {
61
108
  document.head.appendChild(el);
62
109
  }
63
110
 
64
- // Render a single config field control per its `kind` (text / number / boolean / select).
65
- function renderConfigField(field, value, onChange) {
66
- const label = field.label ?? field.key;
111
+ // Render one non-secret config field: its control, then its explanation.
112
+ //
113
+ // Label and hint are looked up by *config key* (`f.<key>`), so a field added
114
+ // without its text shows a legible fallback (`field.label`) instead of a raw
115
+ // identifier — and the locale-coverage test fails, which is the real guard.
116
+ function renderConfigField(field, value, onChange, t) {
117
+ const label = tr(t, `f.${field.key}`, field.label ?? field.key);
118
+ const hint = optionalText(t, `f.${field.key}.hint`);
119
+ let control;
67
120
  if (field.kind === 'boolean') {
68
- return h('label', { className: 'ds-field ds-check-field', key: `cfg-${field.key}` },
121
+ control = h('label', { className: 'ds-control ds-check-field' },
69
122
  h('input', { className: 'ds-check', type: 'checkbox', checked: !!value, onChange: (e) => onChange(e.target.checked) }),
70
123
  ' ' + label);
71
- }
72
- if (field.kind === 'select') {
73
- return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
74
- label + ' ',
124
+ } else if (field.kind === 'select') {
125
+ control = h('label', { className: 'ds-control' }, label + ' ',
75
126
  h('select', { className: 'ds-input', value: value ?? '', onChange: (e) => onChange(e.target.value) },
76
- h('option', { value: '' }, '(default)'),
77
- ...(field.options ?? []).map((opt) => h('option', { key: opt, value: opt }, opt))));
78
- }
79
- if (field.kind === 'number') {
80
- return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
81
- label + ' ',
127
+ h('option', { value: '' }, tr(t, 'o.default', label)),
128
+ ...(field.options ?? []).map((opt) => h('option', { key: opt, value: opt }, tr(t, `o.${opt}`, opt)))));
129
+ } else if (field.kind === 'number') {
130
+ control = h('label', { className: 'ds-control' }, label + ' ',
82
131
  h('input', { className: 'ds-input', type: 'number', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
132
+ } else {
133
+ control = h('label', { className: 'ds-control' }, label + ' ',
134
+ h('input', { className: 'ds-input', type: 'text', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
83
135
  }
84
- return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
85
- label + ' ',
86
- h('input', { className: 'ds-input', type: 'text', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
136
+ return h('div', { className: 'ds-field', key: `cfg-${field.key}` },
137
+ control,
138
+ hint ? h('p', { className: 'ds-hint' }, hint) : null);
139
+ }
140
+
141
+ // Render one secret field: a write-only input, plus a read-only preview of the
142
+ // stored value so the user can confirm what they configured.
143
+ //
144
+ // The preview is plain text, never the input's `value`. The host already masked
145
+ // it, and seeding an input with a mask would let a save write the mask *back* as
146
+ // the credential. Keeping the input blank also keeps "empty" meaning "leave the
147
+ // stored value alone", which is what a password field is expected to do.
148
+ function renderSecretField(ch, field, form, onChange, t) {
149
+ const preview = form.secretPreviews?.[ch]?.[field] ?? '';
150
+ const hint = optionalText(t, `s.${ch}.${field}.hint`);
151
+ return h('div', { className: 'ds-field', key: `sec-${ch}-${field}` },
152
+ h('label', { className: 'ds-control' },
153
+ tr(t, `s.${ch}.${field}`, field),
154
+ h('input', {
155
+ className: 'ds-input',
156
+ // Confidential keys stay masked while typing; identifiers (appId,
157
+ // clientId) don't, so a typo is visible before it is ever saved.
158
+ type: isMaskedSecret(field) ? 'password' : 'text',
159
+ autoComplete: 'off',
160
+ placeholder: preview ? t('configured') : t('notConfigured'),
161
+ value: form.secrets?.[ch]?.[field] ?? '',
162
+ onChange: (e) => onChange(e.target.value),
163
+ })),
164
+ h('p', { className: 'ds-preview' },
165
+ h('b', null, t('current')),
166
+ preview === '' ? t('notConfigured') : preview),
167
+ hint ? h('p', { className: 'ds-hint' }, hint) : null);
168
+ }
169
+
170
+ // One channel card: an enable checkbox, a header that folds the card, and — when
171
+ // open — its credentials and settings.
172
+ //
173
+ // A plain function rather than a child component, deliberately: the bundle test
174
+ // supplies hook values *positionally*, so every `useState` in the tree has to
175
+ // live in `ConnectSettingsTab` in a fixed order. Nothing in here may call a hook.
176
+ function renderChannel(ch, ctx) {
177
+ const { form, creds, t, open, advOverride, setChannels, setField, setChannelConfig, toggleOpen, toggleAdvanced } = ctx;
178
+ const name = tr(t, `channel.${ch}`, ch);
179
+ const channelHint = optionalText(t, `channel.${ch}.hint`);
180
+ const isOpen = open.has(ch);
181
+ const advOpen = advOverride?.has(ch) ?? false;
182
+ const configFields = CHANNEL_CONFIG_FIELDS[ch] ?? [];
183
+ // The split is what keeps a common case on one screen: two credentials and the
184
+ // behavioural switches, with the set-once fields one disclosure deeper.
185
+ const common = configFields.filter((f) => !isAdvanced(ch, f.key));
186
+ const advanced = configFields.filter((f) => isAdvanced(ch, f.key));
187
+
188
+ const configField = (field) => renderConfigField(
189
+ field,
190
+ form.channelConfigs?.[ch]?.[field.key],
191
+ (raw) => setChannelConfig(ch, field.key, raw),
192
+ t,
193
+ );
194
+
195
+ return h('div', { className: 'ds-channel', key: ch, id: `ds-ch-${ch}` },
196
+ // The checkbox sits beside the header button, not inside it: a button may not
197
+ // contain another interactive element, and browsers disagree about what to do
198
+ // when one does.
199
+ h('div', { className: 'ds-channel-head' },
200
+ h('label', { className: 'ds-enable' },
201
+ h('input', {
202
+ className: 'ds-check',
203
+ type: 'checkbox',
204
+ 'aria-label': name,
205
+ checked: form.channels.includes(ch),
206
+ onChange: (e) => setChannels(ch, e.target.checked),
207
+ })),
208
+ h('button', {
209
+ type: 'button',
210
+ className: 'ds-channel-toggle',
211
+ 'aria-expanded': isOpen,
212
+ // Only while the body exists: pointing at an id that is not in the
213
+ // document is worse than not pointing at all.
214
+ 'aria-controls': isOpen ? `ds-ch-${ch}-body` : undefined,
215
+ 'aria-label': `${name} — ${isOpen ? t('collapse') : t('expand')}`,
216
+ onClick: () => toggleOpen(ch),
217
+ },
218
+ h('span', { className: 'ds-channel-name' }, name),
219
+ h('span', { className: 'ds-badge' }, creds[ch] ? t('reachable') : t('unreachable')),
220
+ // Drawn in CSS, never a text node: the bundle test reads rendered text as
221
+ // user-visible copy, and a `▾` here would be collected as a stray string.
222
+ h('span', { className: 'ds-chevron' }))),
223
+ // Collapsed means *not rendered*, not `display:none`. The inputs are
224
+ // controlled from the parent's `form.secrets`, so folding a card cannot lose
225
+ // an unsaved secret — keeping it mounted would buy nothing.
226
+ isOpen ? h('div', { className: 'ds-channel-body', id: `ds-ch-${ch}-body` },
227
+ channelHint ? h('p', { className: 'ds-note' }, channelHint) : null,
228
+ h('div', { className: 'ds-fields' },
229
+ ...CHANNEL_SECRET_FIELDS[ch].map((field) => renderSecretField(ch, field, form, (value) => setField(ch, field, value), t)),
230
+ ...common.map(configField)),
231
+ advanced.length === 0 ? null : h('div', { className: 'ds-adv' },
232
+ h('button', {
233
+ type: 'button',
234
+ className: 'ds-advanced-toggle',
235
+ 'aria-expanded': advOpen,
236
+ 'aria-controls': advOpen ? `ds-ch-${ch}-adv` : undefined,
237
+ onClick: () => toggleAdvanced(ch),
238
+ },
239
+ h('span', null, t('advanced')),
240
+ h('span', { className: 'ds-chevron' })),
241
+ advOpen ? h('div', { className: 'ds-fields', id: `ds-ch-${ch}-adv` }, ...advanced.map(configField)) : null),
242
+ ) : null);
87
243
  }
88
244
 
89
245
  // Thin React renderer over the tested settings-model helpers.
@@ -91,6 +247,8 @@ export function ConnectSettingsTab({ rpcCall, t }) {
91
247
  const [form, setForm] = React.useState(null);
92
248
  const [status, setStatus] = React.useState('loading');
93
249
  const [creds, setCreds] = React.useState({});
250
+ const [openOverride, setOpenOverride] = React.useState(null);
251
+ const [advOverride, setAdvOverride] = React.useState(null);
94
252
  const rpc = (endpoint, payload) => rpcCall(endpoint, payload);
95
253
 
96
254
  React.useEffect(() => {
@@ -108,13 +266,32 @@ export function ConnectSettingsTab({ rpcCall, t }) {
108
266
  if (!form) return;
109
267
  setStatus('saving');
110
268
  try {
111
- await saveSettings(rpc, buildConfigSave(form));
112
- for (const c of buildCredentialSaves(form)) await saveCredentials(rpc, c.channel, c.values);
269
+ // The last snapshot in the chain wins: it is the one that reflects every
270
+ // write this save performed. Re-seeding the form from it also drops the
271
+ // typed secret values (their inputs are write-only by design) and picks up
272
+ // the new presence flags.
273
+ let snap = await saveSettings(rpc, buildConfigSave(form));
274
+ for (const c of buildCredentialSaves(form)) snap = await saveCredentials(rpc, c.channel, c.values);
275
+ setForm(snapshotToForm(snap));
276
+ setCreds(snap.credentials ?? {});
113
277
  setStatus('saved');
114
278
  } catch { setStatus('error'); }
115
279
  };
116
280
 
117
- const setChannels = (ch, on) => setForm((f) => ({ ...f, channels: on ? [...f.channels, ch] : f.channels.filter((x) => x !== ch) }));
281
+ const setChannels = (ch, on) => {
282
+ setForm((f) => ({ ...f, channels: on ? [...f.channels, ch] : f.channels.filter((x) => x !== ch) }));
283
+ // Enabling a channel opens it, and only ever opens: the user just said they
284
+ // care about this one.
285
+ //
286
+ // Recording the fold *either way* is what keeps the card from vanishing
287
+ // under the cursor. Until the user touches an enable box, the open set is
288
+ // derived on every render from the enabled list — so unticking Feishu drops
289
+ // it from `form.channels`, the derived default is recomputed without it, and
290
+ // the card collapses exactly as the user is looking at it. Writing the
291
+ // current fold into the override first freezes it, so an untick closes
292
+ // nothing.
293
+ setOpenOverride(on && !open.has(ch) ? new Set(open).add(ch) : new Set(open));
294
+ };
118
295
  const setField = (ch, field, value) => setForm((f) => ({ ...f, secrets: { ...f.secrets, [ch]: { ...(f.secrets[ch] ?? {}), [field]: value } } }));
119
296
  const setChannelConfig = (ch, key, raw) => setForm((f) => {
120
297
  const descriptor = (CHANNEL_CONFIG_FIELDS[ch] ?? []).find((x) => x.key === key);
@@ -132,36 +309,80 @@ export function ConnectSettingsTab({ rpcCall, t }) {
132
309
  });
133
310
 
134
311
  if (!form) return h('div', { className: 'dsh-connect-settings' }, t('loading'));
312
+
313
+ // Which cards are open is *derived*, not stored: the first render happens
314
+ // before the host's snapshot arrives, and the test stub cannot run effects, so
315
+ // an effect here would be the only place the default could ever be set — and
316
+ // it would never run. The override records only what the user has since done,
317
+ // on top of that default (`toggleInSet(open, …)` rather than a bare toggle),
318
+ // so opening DingTalk does not silently drop the enabled Feishu the user was
319
+ // already looking at.
320
+ const open = openOverride ?? initialOpenChannels(form.channels, ALL_CHANNELS);
321
+
322
+ const toggleOpen = (ch) => setOpenOverride(toggleInSet(open, ch));
323
+ // Advanced folds default to closed for every channel, so an empty set is the
324
+ // derived default here and no counterpart to `initialOpenChannels` is needed.
325
+ const toggleAdvanced = (ch) => setAdvOverride(toggleInSet(advOverride ?? new Set(), ch));
326
+ const focusChannel = (ch) => {
327
+ // A tab expands and scrolls, but never collapses: "show me Telegram" must not
328
+ // close the channel the user is halfway through editing.
329
+ if (!open.has(ch)) setOpenOverride(new Set(open).add(ch));
330
+ if (typeof document === 'undefined' || !document.getElementById) return;
331
+ // The header is rendered whether or not the card is open, so this resolves
332
+ // before React has re-rendered, and lands in the same place either way.
333
+ document.getElementById(`ds-ch-${ch}`)?.scrollIntoView?.({ block: 'nearest' });
334
+ };
335
+
135
336
  return h('div', { className: 'dsh-connect-settings' },
136
337
  h('section', { className: 'ds-card' },
137
338
  h('h4', { className: 'ds-card-title' }, t('channels')),
339
+ // Explains the masking before the user meets a truncated value and wonders
340
+ // whether their stored secret is corrupt.
341
+ h('p', { className: 'ds-note' }, t('previewNote')),
342
+ // A tab strip, but not `role=tablist`: several channels can be open at
343
+ // once, so there is no single "selected" tab to report. These are buttons
344
+ // that open and jump to a channel, and `aria-expanded` says so honestly.
345
+ h('nav', { className: 'ds-tabs', 'aria-label': t('tabsAria') },
346
+ ...ALL_CHANNELS.map((ch) => h('button', {
347
+ key: `tab-${ch}`,
348
+ type: 'button',
349
+ className: 'ds-tab',
350
+ 'aria-expanded': open.has(ch),
351
+ 'aria-controls': open.has(ch) ? `ds-ch-${ch}-body` : undefined,
352
+ onClick: () => focusChannel(ch),
353
+ },
354
+ h('span', { className: 'ds-dot', 'data-on': form.channels.includes(ch) ? '1' : '0' }),
355
+ tr(t, `channel.${ch}`, ch)))),
138
356
  // One card per built-in channel: enable toggle + cred badge + secret + config fields.
139
- ...Object.keys(CHANNEL_SECRET_FIELDS).map((ch) => h('fieldset', { className: 'ds-channel', key: ch },
140
- h('legend', { className: 'ds-legend' },
141
- h('label', null,
142
- h('input', { className: 'ds-check', type: 'checkbox', checked: form.channels.includes(ch), onChange: (e) => setChannels(ch, e.target.checked) }),
143
- h('span', null, ch),
144
- h('span', { className: 'ds-badge' }, creds[ch] ? t('reachable') : t('unreachable'))),
145
- ),
146
- h('div', { className: 'ds-fields' },
147
- ...CHANNEL_SECRET_FIELDS[ch].map((field) => h('label', { className: 'ds-field', key: `sec-${field}` },
148
- field,
149
- h('input', { className: 'ds-input', type: 'password', placeholder: field, value: form.secrets?.[ch]?.[field] ?? '', onChange: (e) => setField(ch, field, e.target.value) }))),
150
- ...(CHANNEL_CONFIG_FIELDS[ch] ?? []).map((field) => renderConfigField(field, form.channelConfigs?.[ch]?.[field.key], (raw) => setChannelConfig(ch, field.key, raw))),
151
- ),
152
- )),
153
- ),
357
+ ...ALL_CHANNELS.map((ch) => renderChannel(ch, {
358
+ form, creds, t, open, advOverride,
359
+ setChannels, setField, setChannelConfig, toggleOpen, toggleAdvanced,
360
+ }))),
154
361
  h('section', { className: 'ds-card' },
155
362
  h('h4', { className: 'ds-card-title' }, t('defaults')),
363
+ h('p', { className: 'ds-note' }, t('defaultsHint')),
156
364
  h('div', { className: 'ds-fields' },
157
- ...CHANNEL_DEFAULT_FIELDS.map((field) => renderConfigField(field, form.channelDefaults?.[field.key], (raw) => setDefault(field.key, raw))),
158
- h('label', { className: 'ds-field' }, t('statePath'),
159
- h('input', { className: 'ds-input', value: form.settingsStatePath ?? '', onChange: (e) => setForm((f) => ({ ...f, settingsStatePath: e.target.value })) })),
160
- ),
161
- h('div', { className: 'ds-actions' },
162
- h('button', { className: 'ds-btn', onClick: onSave, disabled: status === 'saving' }, t('save')),
163
- h('span', { className: 'ds-status' }, status === 'saved' ? t('saved') : status === 'error' ? t('error') : status),
365
+ ...CHANNEL_DEFAULT_FIELDS.map((field) => renderConfigField(field, form.channelDefaults?.[field.key], (raw) => setDefault(field.key, raw), t)),
366
+ // Only meaningful on the fallback plane: it names the file the pane
367
+ // persists to. With the namespace live that path is not consulted (and
368
+ // is not part of the section), so showing an editable field for it would
369
+ // silently swallow edits.
370
+ form.live ? null : h('div', { className: 'ds-field' },
371
+ h('label', { className: 'ds-control' }, t('statePath'),
372
+ h('input', { className: 'ds-input', value: form.settingsStatePath ?? '', onChange: (e) => setForm((f) => ({ ...f, settingsStatePath: e.target.value })) })),
373
+ h('p', { className: 'ds-hint' }, t('statePathHint'))),
164
374
  ),
375
+ h('div', { className: 'ds-status' }, form.live ? t('livePlane') : t('filePlane')),
376
+ ),
377
+ // Last child of the root, not of a card: `position:sticky` pins to the
378
+ // nearest scrollport only while its containing block is the scrolled box.
379
+ // Nested in the defaults card it could never move outside that card, so it
380
+ // pinned to nothing and Save scrolled away with the channel list.
381
+ h('div', { className: 'ds-footer' },
382
+ h('button', { className: 'ds-btn', type: 'button', onClick: onSave, disabled: status === 'saving' }, t('save')),
383
+ // Rendering `status` directly leaks the raw state ids (`idle`, `saving`)
384
+ // into the UI; every state has a locale entry instead.
385
+ h('span', { className: 'ds-status' }, tr(t, `status.${status}`, status)),
165
386
  ),
166
387
  );
167
388
  }
@@ -1 +1 @@
1
- {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAUA,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC9C,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,qFAAqF;QACrF,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,gEAAgE;IAChE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AA0BD,+CAA+C;AAC/C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;AAE5G,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;gBAExD,QAAQ,CAAC,EAAE,MAAM;IAM7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAOrD;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;;OAGG;IACH,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC;IAIxC;;OAEG;IACH,IAAI,IAAI,WAAW,EAAE;IAIrB;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE;IAUjD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI9D,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAQ/B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAU9C,OAAO,CAAC,IAAI;IAoCZ,OAAO,CAAC,IAAI;CAQb"}
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAYA,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC9C,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,qFAAqF;QACrF,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,gEAAgE;IAChE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AA0BD,+CAA+C;AAC/C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;AAE5G,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;gBAExD,QAAQ,CAAC,EAAE,MAAM;IAK7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAOrD;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;;OAGG;IACH,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC;IAIxC;;OAEG;IACH,IAAI,IAAI,WAAW,EAAE;IAIrB;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE;IAUjD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI9D,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAQ/B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAU9C,OAAO,CAAC,IAAI;IAoCZ,OAAO,CAAC,IAAI;CAQb"}
package/lib/binding.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { dirname, resolve } from "node:path";
10
+ import { resolveStateDir } from "./state-dir.js";
10
11
  function keyOf(channel, chatKey) {
11
12
  return `${channel}\u0000${chatKey}`;
12
13
  }
@@ -35,8 +36,7 @@ export class BindingStore {
35
36
  file;
36
37
  changeListeners = new Set();
37
38
  constructor(stateDir) {
38
- const dir = stateDir ?? process.env.DSH_CONNECT_STATE_DIR ?? ".dsh-connect";
39
- this.file = resolve(dir, "bindings.json");
39
+ this.file = resolve(resolveStateDir({ stateDir }), "bindings.json");
40
40
  this.load();
41
41
  }
42
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}