dsh-agent-proxy 0.1.0 → 0.2.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 +5 -4
- package/client.js +105 -23
- package/index.js +61 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,11 +31,12 @@ http_proxy https_proxy all_proxy(及大写形式)、NODE_USE_ENV_PROXY=1
|
|
|
31
31
|
|
|
32
32
|
按优先级:
|
|
33
33
|
|
|
34
|
-
1.
|
|
35
|
-
2.
|
|
36
|
-
3.
|
|
34
|
+
1. 设置页「代理地址」里填写的地址(留空表示自动);
|
|
35
|
+
2. 插件配置里的 `proxyUrl`;
|
|
36
|
+
3. Windows 系统代理(HKCU Internet Settings,即 Clash / V2Ray 打开的「系统代理」);
|
|
37
|
+
4. DSH 启动时已导出的 `HTTPS_PROXY` / `HTTP_PROXY`。
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
设置页开关下方有一个「代理地址」输入框:填了就用它(仅支持 `http://` / `https://`),留空自动使用系统代理。地址在失焦或回车时保存,开关开着会立即用新地址重连;开关下方会显示当前生效的地址与来源。
|
|
39
40
|
|
|
40
41
|
## 安装
|
|
41
42
|
|
package/client.js
CHANGED
|
@@ -11,30 +11,38 @@ window.__ModuleLoader__.load({
|
|
|
11
11
|
title: '代理',
|
|
12
12
|
rowTitle: '代理 dsh',
|
|
13
13
|
rowHint: 'dsh 使用 git 等工具超时时可开启代理',
|
|
14
|
+
addressLabel: '代理地址',
|
|
15
|
+
addressPlaceholder: 'http://127.0.0.1:7890',
|
|
16
|
+
addressHint: '仅支持 http 代理;留空则自动使用系统代理',
|
|
14
17
|
loading: '读取中…',
|
|
15
18
|
viaSystem: 'Windows 系统代理',
|
|
16
|
-
viaConfig: '
|
|
19
|
+
viaConfig: '自定义地址',
|
|
17
20
|
viaEnv: '环境变量中的代理',
|
|
18
21
|
current: '当前代理',
|
|
19
|
-
noProxy: '
|
|
22
|
+
noProxy: '未检测到系统代理,请填写代理地址,或先打开代理软件的系统代理',
|
|
20
23
|
loadFailed: '读取代理状态失败',
|
|
21
24
|
toggleFailed: '切换失败',
|
|
22
|
-
saveFailed: '
|
|
25
|
+
saveFailed: '保存失败',
|
|
26
|
+
addressBad: '地址无效:仅支持 http:// 或 https://,例如 http://127.0.0.1:7890',
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
const EN = {
|
|
26
30
|
title: 'Proxy',
|
|
27
31
|
rowTitle: 'Proxy for dsh',
|
|
28
32
|
rowHint: 'Turn this on when git and other tools keep timing out',
|
|
33
|
+
addressLabel: 'Proxy address',
|
|
34
|
+
addressPlaceholder: 'http://127.0.0.1:7890',
|
|
35
|
+
addressHint: 'HTTP proxies only; leave empty to use the system proxy',
|
|
29
36
|
loading: 'Loading…',
|
|
30
37
|
viaSystem: 'Windows system proxy',
|
|
31
|
-
viaConfig: '
|
|
38
|
+
viaConfig: 'custom address',
|
|
32
39
|
viaEnv: 'proxy from environment',
|
|
33
40
|
current: 'Current proxy',
|
|
34
|
-
noProxy: 'No system proxy detected.
|
|
41
|
+
noProxy: 'No system proxy detected. Enter an address, or turn on your proxy client system proxy.',
|
|
35
42
|
loadFailed: 'Failed to read the proxy state',
|
|
36
43
|
toggleFailed: 'Failed to switch',
|
|
37
|
-
saveFailed: '
|
|
44
|
+
saveFailed: 'Failed to save',
|
|
45
|
+
addressBad: 'Invalid address: http:// or https:// only, e.g. http://127.0.0.1:7890',
|
|
38
46
|
};
|
|
39
47
|
|
|
40
48
|
const styles = {
|
|
@@ -45,7 +53,7 @@ window.__ModuleLoader__.load({
|
|
|
45
53
|
padding: '16px',
|
|
46
54
|
display: 'flex',
|
|
47
55
|
flexDirection: 'column',
|
|
48
|
-
gap: '
|
|
56
|
+
gap: '12px',
|
|
49
57
|
boxSizing: 'border-box',
|
|
50
58
|
},
|
|
51
59
|
row: {
|
|
@@ -87,6 +95,28 @@ window.__ModuleLoader__.load({
|
|
|
87
95
|
lineHeight: 1.5,
|
|
88
96
|
color: 'var(--dsw-alias-label-danger, #cf222e)',
|
|
89
97
|
},
|
|
98
|
+
field: {
|
|
99
|
+
display: 'flex',
|
|
100
|
+
flexDirection: 'column',
|
|
101
|
+
gap: '6px',
|
|
102
|
+
},
|
|
103
|
+
fieldLabel: {
|
|
104
|
+
fontSize: '13px',
|
|
105
|
+
lineHeight: 1.4,
|
|
106
|
+
color: 'var(--dsw-alias-label-secondary, #57606a)',
|
|
107
|
+
},
|
|
108
|
+
input: {
|
|
109
|
+
boxSizing: 'border-box',
|
|
110
|
+
width: '100%',
|
|
111
|
+
border: '1px solid var(--dsw-alias-border-l2, #d0d7de)',
|
|
112
|
+
background: 'var(--dsw-alias-bg-layer-2, #ffffff)',
|
|
113
|
+
color: 'var(--dsw-alias-label-primary, #1f2329)',
|
|
114
|
+
borderRadius: '8px',
|
|
115
|
+
padding: '7px 10px',
|
|
116
|
+
fontSize: '13px',
|
|
117
|
+
lineHeight: 1.4,
|
|
118
|
+
outline: 'none',
|
|
119
|
+
},
|
|
90
120
|
};
|
|
91
121
|
|
|
92
122
|
function Switch({ checked, disabled, label, onChange }) {
|
|
@@ -136,6 +166,7 @@ window.__ModuleLoader__.load({
|
|
|
136
166
|
function AgentProxySection({ t }) {
|
|
137
167
|
const [state, setState] = React.useState(null);
|
|
138
168
|
const [busy, setBusy] = React.useState(false);
|
|
169
|
+
const [draft, setDraft] = React.useState('');
|
|
139
170
|
const [message, setMessage] = React.useState(null);
|
|
140
171
|
|
|
141
172
|
const load = React.useCallback(async () => {
|
|
@@ -147,6 +178,7 @@ window.__ModuleLoader__.load({
|
|
|
147
178
|
const body = await response.json();
|
|
148
179
|
if (!body || body.ok !== true || !body.data) throw new Error(body && body.error ? body.error : 'bad response');
|
|
149
180
|
setState(body.data);
|
|
181
|
+
setDraft(body.data.configuredUrl || '');
|
|
150
182
|
setMessage(null);
|
|
151
183
|
} catch (error) {
|
|
152
184
|
setMessage({ kind: 'bad', text: t('loadFailed') });
|
|
@@ -157,33 +189,55 @@ window.__ModuleLoader__.load({
|
|
|
157
189
|
load();
|
|
158
190
|
}, [load]);
|
|
159
191
|
|
|
192
|
+
const send = async (path, payload) => {
|
|
193
|
+
const response = await fetch(`${API}${path}`, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
credentials: 'same-origin',
|
|
196
|
+
headers: { 'content-type': 'application/json', 'x-dsh-agent-proxy': '1' },
|
|
197
|
+
body: JSON.stringify(payload),
|
|
198
|
+
});
|
|
199
|
+
const body = await response.json();
|
|
200
|
+
if (!body || body.ok !== true || !body.data) {
|
|
201
|
+
throw new Error(body && body.error ? body.error : t('saveFailed'));
|
|
202
|
+
}
|
|
203
|
+
return body.data;
|
|
204
|
+
};
|
|
205
|
+
|
|
160
206
|
const toggle = async () => {
|
|
161
207
|
if (busy) return;
|
|
162
208
|
setBusy(true);
|
|
163
209
|
setMessage(null);
|
|
164
210
|
try {
|
|
165
211
|
const next = !(state && state.enabled);
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
headers: { 'content-type': 'application/json', 'x-dsh-agent-proxy': '1' },
|
|
170
|
-
body: JSON.stringify({ enabled: next }),
|
|
171
|
-
});
|
|
172
|
-
const body = await response.json();
|
|
173
|
-
if (!body || body.ok !== true || !body.data) {
|
|
174
|
-
throw new Error(body && body.error ? body.error : t('toggleFailed'));
|
|
175
|
-
}
|
|
176
|
-
setState(body.data);
|
|
177
|
-
if (body.data.saved === false) setMessage({ kind: 'warn', text: t('saveFailed') });
|
|
178
|
-
if (body.data.enabled && body.data.error) setMessage({ kind: 'bad', text: body.data.error });
|
|
212
|
+
const data = await send('/toggle', { enabled: next });
|
|
213
|
+
setState(data);
|
|
214
|
+
if (data.saved === false) setMessage({ kind: 'warn', text: t('saveFailed') });
|
|
179
215
|
} catch (error) {
|
|
180
|
-
|
|
216
|
+
const text = String((error && error.message) || error);
|
|
217
|
+
setMessage({ kind: 'bad', text: text === 'no-proxy' ? t('noProxy') : text });
|
|
181
218
|
load();
|
|
182
219
|
} finally {
|
|
183
220
|
setBusy(false);
|
|
184
221
|
}
|
|
185
222
|
};
|
|
186
223
|
|
|
224
|
+
const saveAddress = async () => {
|
|
225
|
+
if (busy) return;
|
|
226
|
+
if (draft === (state && state.configuredUrl ? state.configuredUrl : '')) return;
|
|
227
|
+
setBusy(true);
|
|
228
|
+
setMessage(null);
|
|
229
|
+
try {
|
|
230
|
+
const data = await send('/settings', { proxyUrl: draft.trim() });
|
|
231
|
+
setState(data);
|
|
232
|
+
setDraft(data.configuredUrl || '');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const text = String((error && error.message) || error);
|
|
235
|
+
setMessage({ kind: 'bad', text: text === 'no-proxy' ? t('noProxy') : text });
|
|
236
|
+
} finally {
|
|
237
|
+
setBusy(false);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
187
241
|
const enabled = Boolean(state && state.enabled);
|
|
188
242
|
const proxyUrl = state && (state.proxyUrl || state.detectedUrl);
|
|
189
243
|
const sourceLabel = (() => {
|
|
@@ -195,7 +249,9 @@ window.__ModuleLoader__.load({
|
|
|
195
249
|
})();
|
|
196
250
|
|
|
197
251
|
const lines = [];
|
|
198
|
-
if (message)
|
|
252
|
+
if (message) {
|
|
253
|
+
lines.push(h('div', { key: 'msg', style: message.kind === 'warn' ? styles.warn : styles.bad }, message.text));
|
|
254
|
+
}
|
|
199
255
|
if (state === null && !message) lines.push(h('div', { key: 'loading', style: styles.hint }, t('loading')));
|
|
200
256
|
if (state && enabled && proxyUrl) {
|
|
201
257
|
lines.push(
|
|
@@ -206,7 +262,9 @@ window.__ModuleLoader__.load({
|
|
|
206
262
|
),
|
|
207
263
|
);
|
|
208
264
|
}
|
|
209
|
-
if (state && !enabled && !proxyUrl)
|
|
265
|
+
if (state && !enabled && !proxyUrl && !draft) {
|
|
266
|
+
lines.push(h('div', { key: 'noproxy', style: styles.warn }, t('noProxy')));
|
|
267
|
+
}
|
|
210
268
|
|
|
211
269
|
return h(
|
|
212
270
|
'div',
|
|
@@ -227,6 +285,30 @@ window.__ModuleLoader__.load({
|
|
|
227
285
|
onChange: toggle,
|
|
228
286
|
}),
|
|
229
287
|
),
|
|
288
|
+
h(
|
|
289
|
+
'div',
|
|
290
|
+
{ style: styles.field },
|
|
291
|
+
h('label', { style: styles.fieldLabel, htmlFor: 'dsh-agent-proxy-url' }, t('addressLabel')),
|
|
292
|
+
h('input', {
|
|
293
|
+
id: 'dsh-agent-proxy-url',
|
|
294
|
+
type: 'text',
|
|
295
|
+
spellCheck: false,
|
|
296
|
+
autoComplete: 'off',
|
|
297
|
+
value: draft,
|
|
298
|
+
placeholder: t('addressPlaceholder'),
|
|
299
|
+
disabled: busy || state === null,
|
|
300
|
+
onChange: (event) => setDraft(event.target.value),
|
|
301
|
+
onBlur: () => {
|
|
302
|
+
saveAddress();
|
|
303
|
+
},
|
|
304
|
+
onKeyDown: (event) => {
|
|
305
|
+
if (event.key === 'Enter') event.currentTarget.blur();
|
|
306
|
+
},
|
|
307
|
+
style: styles.input,
|
|
308
|
+
'aria-label': t('addressLabel'),
|
|
309
|
+
}),
|
|
310
|
+
h('div', { style: styles.hint }, t('addressHint')),
|
|
311
|
+
),
|
|
230
312
|
lines,
|
|
231
313
|
);
|
|
232
314
|
}
|
package/index.js
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
* - `PIP_PROXY` / `PIP_HTTPS_PROXY`
|
|
22
22
|
*
|
|
23
23
|
* Proxy address resolution, highest first:
|
|
24
|
-
* 1. the
|
|
25
|
-
* 2. the
|
|
26
|
-
* 3.
|
|
24
|
+
* 1. the address saved in the settings page (`$DSH_HOME/agent-proxy.json`)
|
|
25
|
+
* 2. the row's `config.proxyUrl`
|
|
26
|
+
* 3. the Windows system proxy (HKCU Internet Settings, when enabled)
|
|
27
|
+
* 4. a proxy already exported in the Harness environment
|
|
27
28
|
*
|
|
28
29
|
* `config.enabled` is only the initial default before the state file exists.
|
|
29
30
|
*/
|
|
@@ -524,7 +525,10 @@ export function apply(ctx, rawConfig) {
|
|
|
524
525
|
delete globalThis[APPLIED]
|
|
525
526
|
}
|
|
526
527
|
|
|
527
|
-
|
|
528
|
+
/** Address the user typed in the settings page, or the row's configured default. */
|
|
529
|
+
const configuredUrl = () => trimOrEmpty(readState()?.customProxyUrl) || trimOrEmpty(config.proxyUrl)
|
|
530
|
+
|
|
531
|
+
const detect = () => resolveProxy(configuredUrl())
|
|
528
532
|
|
|
529
533
|
const enable = () => {
|
|
530
534
|
if (status.enabled) return status
|
|
@@ -537,7 +541,7 @@ export function apply(ctx, rawConfig) {
|
|
|
537
541
|
const restore = existing?.restore ?? captureToolConfig()
|
|
538
542
|
applyToolConfig(resolved.url)
|
|
539
543
|
writeProxyEnvironment(resolved.url)
|
|
540
|
-
writeState({
|
|
544
|
+
writeState({ ...(existing ?? {}), enabled: true, restore })
|
|
541
545
|
globalThis[APPLIED] = true
|
|
542
546
|
status.enabled = true
|
|
543
547
|
status.proxyUrl = resolved.url
|
|
@@ -546,7 +550,7 @@ export function apply(ctx, rawConfig) {
|
|
|
546
550
|
return status
|
|
547
551
|
}
|
|
548
552
|
|
|
549
|
-
const
|
|
553
|
+
const unapply = () => {
|
|
550
554
|
const restore = readState()?.restore
|
|
551
555
|
if (restore) {
|
|
552
556
|
try {
|
|
@@ -557,7 +561,11 @@ export function apply(ctx, rawConfig) {
|
|
|
557
561
|
}
|
|
558
562
|
restoreOriginalEnvironment()
|
|
559
563
|
delete globalThis[APPLIED]
|
|
560
|
-
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const disable = () => {
|
|
567
|
+
unapply()
|
|
568
|
+
writeState({ ...(readState() ?? {}), enabled: false })
|
|
561
569
|
status.enabled = false
|
|
562
570
|
status.proxyUrl = ''
|
|
563
571
|
status.source = ''
|
|
@@ -572,12 +580,38 @@ export function apply(ctx, rawConfig) {
|
|
|
572
580
|
enabled: status.enabled,
|
|
573
581
|
proxyUrl: status.enabled ? status.proxyUrl : detected.url,
|
|
574
582
|
source: status.enabled ? status.source : detected.source,
|
|
583
|
+
configuredUrl: trimOrEmpty(readState()?.customProxyUrl),
|
|
575
584
|
detectedUrl: detected.url,
|
|
576
585
|
detectedSource: detected.source,
|
|
577
586
|
error: status.error,
|
|
578
587
|
}
|
|
579
588
|
}
|
|
580
589
|
|
|
590
|
+
/** Store a custom proxy address ('' = keep using the system proxy) and re-apply it. */
|
|
591
|
+
const saveProxyUrl = (raw) => {
|
|
592
|
+
const value = trimOrEmpty(raw)
|
|
593
|
+
const normalized = value ? normalizeProxyUrl(value) : ''
|
|
594
|
+
if (value && !normalized) {
|
|
595
|
+
throw Object.assign(
|
|
596
|
+
new Error('代理地址无效:仅支持 http:// 或 https:// 地址,例如 http://127.0.0.1:7890'),
|
|
597
|
+
{ statusCode: 400 },
|
|
598
|
+
)
|
|
599
|
+
}
|
|
600
|
+
const wasEnabled = status.enabled
|
|
601
|
+
if (wasEnabled) {
|
|
602
|
+
unapply()
|
|
603
|
+
status.enabled = false
|
|
604
|
+
status.proxyUrl = ''
|
|
605
|
+
status.source = ''
|
|
606
|
+
}
|
|
607
|
+
const next = { ...(readState() ?? {}), customProxyUrl: normalized }
|
|
608
|
+
// 0.1.x stored the resolved address here; it is not a user preference.
|
|
609
|
+
delete next.proxyUrl
|
|
610
|
+
writeState(next)
|
|
611
|
+
if (wasEnabled) enable()
|
|
612
|
+
return snapshot()
|
|
613
|
+
}
|
|
614
|
+
|
|
581
615
|
const state = readState()
|
|
582
616
|
const wantOn = typeof state?.enabled === 'boolean' ? state.enabled : config.enabled === true
|
|
583
617
|
if (wantOn) enable()
|
|
@@ -619,7 +653,7 @@ export function apply(ctx, rawConfig) {
|
|
|
619
653
|
if (body.enabled) {
|
|
620
654
|
enable()
|
|
621
655
|
if (!status.enabled) {
|
|
622
|
-
return fail(409, '
|
|
656
|
+
return fail(409, '未检测到代理地址:请在下方填写代理地址,或先打开代理软件的系统代理')
|
|
623
657
|
}
|
|
624
658
|
} else {
|
|
625
659
|
disable()
|
|
@@ -638,6 +672,25 @@ export function apply(ctx, rawConfig) {
|
|
|
638
672
|
}
|
|
639
673
|
},
|
|
640
674
|
},
|
|
675
|
+
{
|
|
676
|
+
path: `${API_PREFIX}/settings`,
|
|
677
|
+
methods: ['POST'],
|
|
678
|
+
requestBody: 'buffered',
|
|
679
|
+
async fetch(request) {
|
|
680
|
+
const rejected = guardMutation(request)
|
|
681
|
+
if (rejected) return fail(403, rejected)
|
|
682
|
+
try {
|
|
683
|
+
const body = await readJsonBody(request)
|
|
684
|
+
if (typeof body.proxyUrl !== 'string') {
|
|
685
|
+
return fail(400, 'proxyUrl 必须是字符串')
|
|
686
|
+
}
|
|
687
|
+
const data = saveProxyUrl(body.proxyUrl)
|
|
688
|
+
return jsonResponse(200, { ok: true, data: { ...data, saved: true } })
|
|
689
|
+
} catch (error) {
|
|
690
|
+
return fail(error?.statusCode ?? 500, messageOf(error))
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
},
|
|
641
694
|
]
|
|
642
695
|
|
|
643
696
|
const mount = (owner) => {
|