@dshn/agent 0.1.4 → 0.1.5
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/client.js +42 -38
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -331,12 +331,12 @@ window.__ModuleLoader__.load({
|
|
|
331
331
|
.dshn-err { color: var(--dsw-alias-state-error-primary, #e5484d); font-size: 11.5px; margin-bottom: 9px; }
|
|
332
332
|
.dshn-hint { color: var(--dsw-alias-label-tertiary, #8b9099); font-size: 10.5px; margin-top: 3px; }
|
|
333
333
|
.dshn-note { color: var(--dsw-alias-state-warn-primary, #d98324); font-size: 10.5px; margin: 2px 0 10px; }
|
|
334
|
-
.dshn-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
.dshn-
|
|
339
|
-
|
|
334
|
+
.dshn-modeseg { display: flex; gap: 4px; padding: 3px; border-radius: 9px; background: var(--dsw-alias-bg-layer-2, #f4f5f7);
|
|
335
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128,134,142,.22)); }
|
|
336
|
+
.dshn-modebtn { flex: 1; padding: 6px 10px; border: 0; border-radius: 6px; cursor: pointer; font-size: 12.5px; font-family: inherit;
|
|
337
|
+
background: transparent; color: var(--dsw-alias-label-secondary, #4a4f57); }
|
|
338
|
+
.dshn-modebtn.dshn-on { background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #1c1e21);
|
|
339
|
+
box-shadow: 0 1px 2px rgba(0,0,0,.08); }
|
|
340
340
|
.dshn-ca { min-height: 54px; resize: vertical; font-family: ui-monospace, Menlo, monospace; font-size: 11px; line-height: 1.4; }
|
|
341
341
|
.dshn-e2e-box { margin: 4px 0 12px; padding: 11px 12px; border-radius: 10px;
|
|
342
342
|
border: 1px solid var(--dsw-alias-border-l2, rgba(128,134,142,.25)); background: var(--dsw-alias-bg-layer-2, #f4f5f7); }
|
|
@@ -392,8 +392,8 @@ window.__ModuleLoader__.load({
|
|
|
392
392
|
openSettings: '打开设置', open: '打开', addrLabel: '公网地址',
|
|
393
393
|
dcTitle: '确认断开公网转发?', dcWarn: '断开会立即切断公网访问。云端不保存你的密码、无法找回;若你没有另存密码,之后可能无法用同一前缀重新连接。',
|
|
394
394
|
dcConfirm: '确认断开', dcCancel: '取消', dcCopyFirst: '先复制密码',
|
|
395
|
-
|
|
396
|
-
relayHostHint: '
|
|
395
|
+
mode: '模式', modeOfficial: '官方 ds.hn', modeSelf: '自托管', yourDomain: '你的域名', relayHost: '中继地址',
|
|
396
|
+
relayHostHint: '填你自己的 @dshn/relay,如 wss://tunnel.example.com。子域会挂在它的域名下。',
|
|
397
397
|
relayCa: '中继证书(自签名,可选)',
|
|
398
398
|
relayCaHint: '仅当你的中继用自签名证书时:粘贴其 PEM 证书以固定信任(公有证书/套 Cloudflare 时留空)。' }
|
|
399
399
|
: { brand: 'Public forwarding · ds.hn', connecting: 'connecting…', live: 'live', off: 'off', notset: 'not set up',
|
|
@@ -416,8 +416,8 @@ window.__ModuleLoader__.load({
|
|
|
416
416
|
openSettings: 'open settings', open: 'open', addrLabel: 'Public address',
|
|
417
417
|
dcTitle: 'Disconnect public forwarding?', dcWarn: 'This immediately cuts off public access. The cloud does not store your password and cannot recover it — if you have not saved it elsewhere, you may not be able to reconnect with the same prefix.',
|
|
418
418
|
dcConfirm: 'Disconnect', dcCancel: 'Cancel', dcCopyFirst: 'Copy password first',
|
|
419
|
-
|
|
420
|
-
relayHostHint: '
|
|
419
|
+
mode: 'Mode', modeOfficial: 'Official ds.hn', modeSelf: 'Self-hosted', yourDomain: 'your-domain', relayHost: 'Relay host',
|
|
420
|
+
relayHostHint: 'Your own @dshn/relay, e.g. wss://tunnel.example.com. Your subdomain lives under its domain.',
|
|
421
421
|
relayCa: 'Relay CA (self-signed, optional)',
|
|
422
422
|
relayCaHint: 'Only when your relay uses a self-signed cert: paste its PEM to pin trust (leave blank for a public cert / behind Cloudflare).' }
|
|
423
423
|
|
|
@@ -494,11 +494,12 @@ window.__ModuleLoader__.load({
|
|
|
494
494
|
const [e2eBusy, setE2eBusy] = react.useState(false)
|
|
495
495
|
const [e2eErr, setE2eErr] = react.useState('')
|
|
496
496
|
const [e2eMsg, setE2eMsg] = react.useState('')
|
|
497
|
-
//
|
|
497
|
+
// Mode: 'official' (ds.hn) vs 'selfhost' (your own @dshn/relay). Both share
|
|
498
|
+
// the subdomain + password + e2e; self-hosted just adds the relay location.
|
|
498
499
|
const rs0 = s.relaySettings || {}
|
|
500
|
+
const [mode, setMode] = react.useState(rs0.relayHost ? 'selfhost' : 'official')
|
|
499
501
|
const [relayHost, setRelayHost] = react.useState(rs0.relayHost || '')
|
|
500
502
|
const [originCa, setOriginCa] = react.useState(rs0.originCa || '')
|
|
501
|
-
const [showAdvanced, setShowAdvanced] = react.useState(!!rs0.relayHost)
|
|
502
503
|
|
|
503
504
|
// When the saved passwords arrive from a later status poll, fill them once.
|
|
504
505
|
react.useEffect(() => {
|
|
@@ -512,21 +513,21 @@ window.__ModuleLoader__.load({
|
|
|
512
513
|
react.useEffect(() => {
|
|
513
514
|
const rs = s.relaySettings
|
|
514
515
|
if (!rs) return
|
|
515
|
-
if (rs.relayHost && relayHost === '') { setRelayHost(rs.relayHost);
|
|
516
|
+
if (rs.relayHost && relayHost === '') { setRelayHost(rs.relayHost); setMode('selfhost') }
|
|
516
517
|
if (rs.originCa && originCa === '') setOriginCa(rs.originCa)
|
|
517
518
|
}, [s.relaySettings && s.relaySettings.relayHost])
|
|
518
519
|
|
|
519
520
|
// The domain suffix reflects the self-hosted relay AS YOU TYPE it (a
|
|
520
521
|
// client-side mirror of the server's apex derivation), so the preview shows
|
|
521
|
-
// YOUR domain
|
|
522
|
-
// the server's current apex.
|
|
522
|
+
// YOUR domain before connecting. Official mode → the server's apex (ds.hn).
|
|
523
523
|
const apex = (() => {
|
|
524
|
+
if (mode !== 'selfhost') return s.apex || 'ds.hn'
|
|
524
525
|
const rh = relayHost.trim()
|
|
525
|
-
if (rh === '') return
|
|
526
|
+
if (rh === '') return T.yourDomain
|
|
526
527
|
const bare = rh.replace(/^wss?:\/\//, '').replace(/:\d+$/, '').replace(/\/.*$/, '')
|
|
527
528
|
const parts = bare.split('.')
|
|
528
529
|
if (parts.length > 2 && (parts[0] === 'relay' || parts[0] === 'origin')) return parts.slice(1).join('.')
|
|
529
|
-
return bare ||
|
|
530
|
+
return bare || T.yourDomain
|
|
530
531
|
})()
|
|
531
532
|
|
|
532
533
|
const copyText = (text, tag) => {
|
|
@@ -551,7 +552,8 @@ window.__ModuleLoader__.load({
|
|
|
551
552
|
// whole thing is one flow. Once configured, e2e moves to its own dedicated
|
|
552
553
|
// control below (its own apply button, independent of the connection).
|
|
553
554
|
const e2eRegOk = configured || e2e.trim() === '' || e2e.trim().length >= MIN_PW
|
|
554
|
-
const
|
|
555
|
+
const selfhostOk = mode !== 'selfhost' || relayHost.trim() !== ''
|
|
556
|
+
const canSubmit = prefix.trim().length > 0 && !busy && e2eRegOk && selfhostOk
|
|
555
557
|
&& (configured ? pw.length >= MIN_PW : (st.ok && confirm.length > 0 && matches))
|
|
556
558
|
|
|
557
559
|
const submit = () => {
|
|
@@ -559,7 +561,8 @@ window.__ModuleLoader__.load({
|
|
|
559
561
|
setBusy(true); setErr('')
|
|
560
562
|
const e2eVal = e2e.trim()
|
|
561
563
|
fetch('/dshn/configure', { method: 'POST', headers: { 'content-type': 'application/json' },
|
|
562
|
-
body: JSON.stringify({ subdomain: prefix.trim().toLowerCase(), password: pw,
|
|
564
|
+
body: JSON.stringify({ subdomain: prefix.trim().toLowerCase(), password: pw,
|
|
565
|
+
relayHost: mode === 'selfhost' ? relayHost.trim() : '', originCa: mode === 'selfhost' ? originCa.trim() : '' }) })
|
|
563
566
|
.then((r) => r.json().then((j) => ({ ok: r.ok, j })))
|
|
564
567
|
.then(async ({ ok, j }) => {
|
|
565
568
|
if (!ok) { setBusy(false); setErr((j && j.error) || 'failed'); return }
|
|
@@ -620,10 +623,29 @@ window.__ModuleLoader__.load({
|
|
|
620
623
|
|
|
621
624
|
liveErr ? h('div', { className: 'dshn-err' }, liveErr) : null,
|
|
622
625
|
|
|
626
|
+
// Mode: official ds.hn vs self-hosted. The subdomain + password + e2e
|
|
627
|
+
// below are shared; self-hosted just adds where your own relay lives.
|
|
628
|
+
h('div', { className: 'dshn-field' },
|
|
629
|
+
h('span', null, T.mode),
|
|
630
|
+
h('div', { className: 'dshn-modeseg' },
|
|
631
|
+
h('button', { type: 'button', className: 'dshn-modebtn' + (mode === 'official' ? ' dshn-on' : ''), onClick: () => setMode('official') }, T.modeOfficial),
|
|
632
|
+
h('button', { type: 'button', className: 'dshn-modebtn' + (mode === 'selfhost' ? ' dshn-on' : ''), onClick: () => setMode('selfhost') }, T.modeSelf))),
|
|
633
|
+
|
|
634
|
+
mode === 'selfhost' ? h('label', { className: 'dshn-field' },
|
|
635
|
+
h('span', null, T.relayHost),
|
|
636
|
+
h('input', { className: 'dshn-input', value: relayHost, placeholder: 'wss://tunnel.example.com',
|
|
637
|
+
autoComplete: 'off', spellCheck: false, autoFocus: !configured, onChange: (ev) => setRelayHost(ev.target.value) }),
|
|
638
|
+
h('div', { className: 'dshn-hint' }, T.relayHostHint)) : null,
|
|
639
|
+
mode === 'selfhost' ? h('label', { className: 'dshn-field' },
|
|
640
|
+
h('span', null, T.relayCa),
|
|
641
|
+
h('textarea', { className: 'dshn-input dshn-ca', value: originCa, rows: 3, spellCheck: false,
|
|
642
|
+
placeholder: '-----BEGIN CERTIFICATE-----', onChange: (ev) => setOriginCa(ev.target.value) }),
|
|
643
|
+
h('div', { className: 'dshn-hint' }, T.relayCaHint)) : null,
|
|
644
|
+
|
|
623
645
|
h('label', { className: 'dshn-field' },
|
|
624
646
|
h('span', null, T.prefix),
|
|
625
647
|
h('div', { className: 'dshn-prefixwrap' },
|
|
626
|
-
h('input', { className: 'dshn-input', value: prefix, placeholder: 'alice', autoFocus: !configured,
|
|
648
|
+
h('input', { className: 'dshn-input', value: prefix, placeholder: 'alice', autoFocus: !configured && mode !== 'selfhost',
|
|
627
649
|
onChange: (e) => setPrefix(e.target.value.toLowerCase()) }),
|
|
628
650
|
h('span', { className: 'dshn-apex' }, '.' + apex)),
|
|
629
651
|
!configured ? h('div', { className: 'dshn-hint' }, T.prefixHint) : null),
|
|
@@ -697,24 +719,6 @@ window.__ModuleLoader__.load({
|
|
|
697
719
|
h('div', { className: 'dshn-hint', style: { marginTop: '7px' } }, T.e2eHint + ' ' + T.e2eIndep))
|
|
698
720
|
})() : null,
|
|
699
721
|
|
|
700
|
-
// Advanced — self-hosted relay. Point the agent at your own @dshn/relay
|
|
701
|
-
// instead of the default ds.hn. Collapsed unless a custom relay is already
|
|
702
|
-
// set; applied on Connect/Reconnect (changing it reconnects the tunnel).
|
|
703
|
-
h('div', { className: 'dshn-adv' },
|
|
704
|
-
h('button', { type: 'button', className: 'dshn-adv-toggle', onClick: () => setShowAdvanced(!showAdvanced) },
|
|
705
|
-
(showAdvanced ? '▾ ' : '▸ ') + T.advanced),
|
|
706
|
-
showAdvanced ? h('div', { className: 'dshn-adv-body' },
|
|
707
|
-
h('label', { className: 'dshn-field' },
|
|
708
|
-
h('span', null, T.relayHost),
|
|
709
|
-
h('input', { className: 'dshn-input', value: relayHost, placeholder: s.defaultRelayHost || 'relay.ds.hn',
|
|
710
|
-
autoComplete: 'off', spellCheck: false, onChange: (ev) => setRelayHost(ev.target.value) }),
|
|
711
|
-
h('div', { className: 'dshn-hint' }, T.relayHostHint)),
|
|
712
|
-
h('label', { className: 'dshn-field' },
|
|
713
|
-
h('span', null, T.relayCa),
|
|
714
|
-
h('textarea', { className: 'dshn-input dshn-ca', value: originCa, rows: 3, spellCheck: false,
|
|
715
|
-
placeholder: '-----BEGIN CERTIFICATE-----', onChange: (ev) => setOriginCa(ev.target.value) }),
|
|
716
|
-
h('div', { className: 'dshn-hint' }, T.relayCaHint))) : null),
|
|
717
|
-
|
|
718
722
|
// Disconnecting severs public access and the password is unrecoverable
|
|
719
723
|
// from the cloud — so it takes an explicit, spelled-out confirmation.
|
|
720
724
|
confirmDc ? h('div', { className: 'dshn-dcwarn' },
|