@dshn/agent 0.1.3 → 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 +51 -35
- 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
|
|
|
@@ -482,7 +482,6 @@ window.__ModuleLoader__.load({
|
|
|
482
482
|
function Panel(props) {
|
|
483
483
|
const s = props.status
|
|
484
484
|
const configured = s.configured
|
|
485
|
-
const apex = s.apex || 'ds.hn'
|
|
486
485
|
const [prefix, setPrefix] = react.useState(configured ? (s.subdomain || '') : '')
|
|
487
486
|
const [pw, setPw] = react.useState(configured ? (s.password || '') : '')
|
|
488
487
|
const [confirm, setConfirm] = react.useState('')
|
|
@@ -495,11 +494,12 @@ window.__ModuleLoader__.load({
|
|
|
495
494
|
const [e2eBusy, setE2eBusy] = react.useState(false)
|
|
496
495
|
const [e2eErr, setE2eErr] = react.useState('')
|
|
497
496
|
const [e2eMsg, setE2eMsg] = react.useState('')
|
|
498
|
-
//
|
|
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.
|
|
499
499
|
const rs0 = s.relaySettings || {}
|
|
500
|
+
const [mode, setMode] = react.useState(rs0.relayHost ? 'selfhost' : 'official')
|
|
500
501
|
const [relayHost, setRelayHost] = react.useState(rs0.relayHost || '')
|
|
501
502
|
const [originCa, setOriginCa] = react.useState(rs0.originCa || '')
|
|
502
|
-
const [showAdvanced, setShowAdvanced] = react.useState(!!rs0.relayHost)
|
|
503
503
|
|
|
504
504
|
// When the saved passwords arrive from a later status poll, fill them once.
|
|
505
505
|
react.useEffect(() => {
|
|
@@ -513,10 +513,23 @@ window.__ModuleLoader__.load({
|
|
|
513
513
|
react.useEffect(() => {
|
|
514
514
|
const rs = s.relaySettings
|
|
515
515
|
if (!rs) return
|
|
516
|
-
if (rs.relayHost && relayHost === '') { setRelayHost(rs.relayHost);
|
|
516
|
+
if (rs.relayHost && relayHost === '') { setRelayHost(rs.relayHost); setMode('selfhost') }
|
|
517
517
|
if (rs.originCa && originCa === '') setOriginCa(rs.originCa)
|
|
518
518
|
}, [s.relaySettings && s.relaySettings.relayHost])
|
|
519
519
|
|
|
520
|
+
// The domain suffix reflects the self-hosted relay AS YOU TYPE it (a
|
|
521
|
+
// client-side mirror of the server's apex derivation), so the preview shows
|
|
522
|
+
// YOUR domain before connecting. Official mode → the server's apex (ds.hn).
|
|
523
|
+
const apex = (() => {
|
|
524
|
+
if (mode !== 'selfhost') return s.apex || 'ds.hn'
|
|
525
|
+
const rh = relayHost.trim()
|
|
526
|
+
if (rh === '') return T.yourDomain
|
|
527
|
+
const bare = rh.replace(/^wss?:\/\//, '').replace(/:\d+$/, '').replace(/\/.*$/, '')
|
|
528
|
+
const parts = bare.split('.')
|
|
529
|
+
if (parts.length > 2 && (parts[0] === 'relay' || parts[0] === 'origin')) return parts.slice(1).join('.')
|
|
530
|
+
return bare || T.yourDomain
|
|
531
|
+
})()
|
|
532
|
+
|
|
520
533
|
const copyText = (text, tag) => {
|
|
521
534
|
if (!text || !navigator.clipboard) return
|
|
522
535
|
navigator.clipboard.writeText(text).then(() => { setCopied(tag); setTimeout(() => setCopied(''), 1200) }).catch(() => {})
|
|
@@ -539,7 +552,8 @@ window.__ModuleLoader__.load({
|
|
|
539
552
|
// whole thing is one flow. Once configured, e2e moves to its own dedicated
|
|
540
553
|
// control below (its own apply button, independent of the connection).
|
|
541
554
|
const e2eRegOk = configured || e2e.trim() === '' || e2e.trim().length >= MIN_PW
|
|
542
|
-
const
|
|
555
|
+
const selfhostOk = mode !== 'selfhost' || relayHost.trim() !== ''
|
|
556
|
+
const canSubmit = prefix.trim().length > 0 && !busy && e2eRegOk && selfhostOk
|
|
543
557
|
&& (configured ? pw.length >= MIN_PW : (st.ok && confirm.length > 0 && matches))
|
|
544
558
|
|
|
545
559
|
const submit = () => {
|
|
@@ -547,7 +561,8 @@ window.__ModuleLoader__.load({
|
|
|
547
561
|
setBusy(true); setErr('')
|
|
548
562
|
const e2eVal = e2e.trim()
|
|
549
563
|
fetch('/dshn/configure', { method: 'POST', headers: { 'content-type': 'application/json' },
|
|
550
|
-
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() : '' }) })
|
|
551
566
|
.then((r) => r.json().then((j) => ({ ok: r.ok, j })))
|
|
552
567
|
.then(async ({ ok, j }) => {
|
|
553
568
|
if (!ok) { setBusy(false); setErr((j && j.error) || 'failed'); return }
|
|
@@ -608,10 +623,29 @@ window.__ModuleLoader__.load({
|
|
|
608
623
|
|
|
609
624
|
liveErr ? h('div', { className: 'dshn-err' }, liveErr) : null,
|
|
610
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
|
+
|
|
611
645
|
h('label', { className: 'dshn-field' },
|
|
612
646
|
h('span', null, T.prefix),
|
|
613
647
|
h('div', { className: 'dshn-prefixwrap' },
|
|
614
|
-
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',
|
|
615
649
|
onChange: (e) => setPrefix(e.target.value.toLowerCase()) }),
|
|
616
650
|
h('span', { className: 'dshn-apex' }, '.' + apex)),
|
|
617
651
|
!configured ? h('div', { className: 'dshn-hint' }, T.prefixHint) : null),
|
|
@@ -685,24 +719,6 @@ window.__ModuleLoader__.load({
|
|
|
685
719
|
h('div', { className: 'dshn-hint', style: { marginTop: '7px' } }, T.e2eHint + ' ' + T.e2eIndep))
|
|
686
720
|
})() : null,
|
|
687
721
|
|
|
688
|
-
// Advanced — self-hosted relay. Point the agent at your own @dshn/relay
|
|
689
|
-
// instead of the default ds.hn. Collapsed unless a custom relay is already
|
|
690
|
-
// set; applied on Connect/Reconnect (changing it reconnects the tunnel).
|
|
691
|
-
h('div', { className: 'dshn-adv' },
|
|
692
|
-
h('button', { type: 'button', className: 'dshn-adv-toggle', onClick: () => setShowAdvanced(!showAdvanced) },
|
|
693
|
-
(showAdvanced ? '▾ ' : '▸ ') + T.advanced),
|
|
694
|
-
showAdvanced ? h('div', { className: 'dshn-adv-body' },
|
|
695
|
-
h('label', { className: 'dshn-field' },
|
|
696
|
-
h('span', null, T.relayHost),
|
|
697
|
-
h('input', { className: 'dshn-input', value: relayHost, placeholder: s.defaultRelayHost || 'relay.ds.hn',
|
|
698
|
-
autoComplete: 'off', spellCheck: false, onChange: (ev) => setRelayHost(ev.target.value) }),
|
|
699
|
-
h('div', { className: 'dshn-hint' }, T.relayHostHint)),
|
|
700
|
-
h('label', { className: 'dshn-field' },
|
|
701
|
-
h('span', null, T.relayCa),
|
|
702
|
-
h('textarea', { className: 'dshn-input dshn-ca', value: originCa, rows: 3, spellCheck: false,
|
|
703
|
-
placeholder: '-----BEGIN CERTIFICATE-----', onChange: (ev) => setOriginCa(ev.target.value) }),
|
|
704
|
-
h('div', { className: 'dshn-hint' }, T.relayCaHint))) : null),
|
|
705
|
-
|
|
706
722
|
// Disconnecting severs public access and the password is unrecoverable
|
|
707
723
|
// from the cloud — so it takes an explicit, spelled-out confirmation.
|
|
708
724
|
confirmDc ? h('div', { className: 'dshn-dcwarn' },
|