@dshn/agent 0.1.4 → 0.1.6
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 -39
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -331,13 +331,16 @@ 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
|
+
.dshn-catoggle { border: 0; background: transparent; cursor: pointer; padding: 2px 0; font-size: 11px; font-family: inherit;
|
|
342
|
+
color: var(--dsw-alias-label-tertiary, #8b9099); }
|
|
343
|
+
.dshn-catoggle:hover { color: var(--dsw-alias-label-primary, #1c1e21); }
|
|
341
344
|
.dshn-e2e-box { margin: 4px 0 12px; padding: 11px 12px; border-radius: 10px;
|
|
342
345
|
border: 1px solid var(--dsw-alias-border-l2, rgba(128,134,142,.25)); background: var(--dsw-alias-bg-layer-2, #f4f5f7); }
|
|
343
346
|
.dshn-e2e-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px;
|
|
@@ -392,8 +395,8 @@ window.__ModuleLoader__.load({
|
|
|
392
395
|
openSettings: '打开设置', open: '打开', addrLabel: '公网地址',
|
|
393
396
|
dcTitle: '确认断开公网转发?', dcWarn: '断开会立即切断公网访问。云端不保存你的密码、无法找回;若你没有另存密码,之后可能无法用同一前缀重新连接。',
|
|
394
397
|
dcConfirm: '确认断开', dcCancel: '取消', dcCopyFirst: '先复制密码',
|
|
395
|
-
|
|
396
|
-
relayHostHint: '
|
|
398
|
+
mode: '模式', modeOfficial: '官方 ds.hn', modeSelf: '自托管', yourDomain: '你的域名', relayHost: '中继地址',
|
|
399
|
+
relayHostHint: '填你自己的 @dshn/relay,如 wss://tunnel.example.com。子域会挂在它的域名下。',
|
|
397
400
|
relayCa: '中继证书(自签名,可选)',
|
|
398
401
|
relayCaHint: '仅当你的中继用自签名证书时:粘贴其 PEM 证书以固定信任(公有证书/套 Cloudflare 时留空)。' }
|
|
399
402
|
: { brand: 'Public forwarding · ds.hn', connecting: 'connecting…', live: 'live', off: 'off', notset: 'not set up',
|
|
@@ -416,8 +419,8 @@ window.__ModuleLoader__.load({
|
|
|
416
419
|
openSettings: 'open settings', open: 'open', addrLabel: 'Public address',
|
|
417
420
|
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
421
|
dcConfirm: 'Disconnect', dcCancel: 'Cancel', dcCopyFirst: 'Copy password first',
|
|
419
|
-
|
|
420
|
-
relayHostHint: '
|
|
422
|
+
mode: 'Mode', modeOfficial: 'Official ds.hn', modeSelf: 'Self-hosted', yourDomain: 'your-domain', relayHost: 'Relay host',
|
|
423
|
+
relayHostHint: 'Your own @dshn/relay, e.g. wss://tunnel.example.com. Your subdomain lives under its domain.',
|
|
421
424
|
relayCa: 'Relay CA (self-signed, optional)',
|
|
422
425
|
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
426
|
|
|
@@ -494,11 +497,15 @@ window.__ModuleLoader__.load({
|
|
|
494
497
|
const [e2eBusy, setE2eBusy] = react.useState(false)
|
|
495
498
|
const [e2eErr, setE2eErr] = react.useState('')
|
|
496
499
|
const [e2eMsg, setE2eMsg] = react.useState('')
|
|
497
|
-
//
|
|
500
|
+
// Mode: 'official' (ds.hn) vs 'selfhost' (your own @dshn/relay). Both share
|
|
501
|
+
// the subdomain + password + e2e; self-hosted just adds the relay location.
|
|
498
502
|
const rs0 = s.relaySettings || {}
|
|
503
|
+
const [mode, setMode] = react.useState(rs0.relayHost ? 'selfhost' : 'official')
|
|
499
504
|
const [relayHost, setRelayHost] = react.useState(rs0.relayHost || '')
|
|
500
505
|
const [originCa, setOriginCa] = react.useState(rs0.originCa || '')
|
|
501
|
-
|
|
506
|
+
// The self-signed CA is rarely needed (valid cert / Cloudflare cover most);
|
|
507
|
+
// hidden behind a toggle, auto-shown only if one is already saved.
|
|
508
|
+
const [showCa, setShowCa] = react.useState(!!rs0.originCa)
|
|
502
509
|
|
|
503
510
|
// When the saved passwords arrive from a later status poll, fill them once.
|
|
504
511
|
react.useEffect(() => {
|
|
@@ -512,21 +519,21 @@ window.__ModuleLoader__.load({
|
|
|
512
519
|
react.useEffect(() => {
|
|
513
520
|
const rs = s.relaySettings
|
|
514
521
|
if (!rs) return
|
|
515
|
-
if (rs.relayHost && relayHost === '') { setRelayHost(rs.relayHost);
|
|
516
|
-
if (rs.originCa && originCa === '') setOriginCa(rs.originCa)
|
|
522
|
+
if (rs.relayHost && relayHost === '') { setRelayHost(rs.relayHost); setMode('selfhost') }
|
|
523
|
+
if (rs.originCa && originCa === '') { setOriginCa(rs.originCa); setShowCa(true) }
|
|
517
524
|
}, [s.relaySettings && s.relaySettings.relayHost])
|
|
518
525
|
|
|
519
526
|
// The domain suffix reflects the self-hosted relay AS YOU TYPE it (a
|
|
520
527
|
// client-side mirror of the server's apex derivation), so the preview shows
|
|
521
|
-
// YOUR domain
|
|
522
|
-
// the server's current apex.
|
|
528
|
+
// YOUR domain before connecting. Official mode → the server's apex (ds.hn).
|
|
523
529
|
const apex = (() => {
|
|
530
|
+
if (mode !== 'selfhost') return s.apex || 'ds.hn'
|
|
524
531
|
const rh = relayHost.trim()
|
|
525
|
-
if (rh === '') return
|
|
532
|
+
if (rh === '') return T.yourDomain
|
|
526
533
|
const bare = rh.replace(/^wss?:\/\//, '').replace(/:\d+$/, '').replace(/\/.*$/, '')
|
|
527
534
|
const parts = bare.split('.')
|
|
528
535
|
if (parts.length > 2 && (parts[0] === 'relay' || parts[0] === 'origin')) return parts.slice(1).join('.')
|
|
529
|
-
return bare ||
|
|
536
|
+
return bare || T.yourDomain
|
|
530
537
|
})()
|
|
531
538
|
|
|
532
539
|
const copyText = (text, tag) => {
|
|
@@ -551,7 +558,8 @@ window.__ModuleLoader__.load({
|
|
|
551
558
|
// whole thing is one flow. Once configured, e2e moves to its own dedicated
|
|
552
559
|
// control below (its own apply button, independent of the connection).
|
|
553
560
|
const e2eRegOk = configured || e2e.trim() === '' || e2e.trim().length >= MIN_PW
|
|
554
|
-
const
|
|
561
|
+
const selfhostOk = mode !== 'selfhost' || relayHost.trim() !== ''
|
|
562
|
+
const canSubmit = prefix.trim().length > 0 && !busy && e2eRegOk && selfhostOk
|
|
555
563
|
&& (configured ? pw.length >= MIN_PW : (st.ok && confirm.length > 0 && matches))
|
|
556
564
|
|
|
557
565
|
const submit = () => {
|
|
@@ -559,7 +567,8 @@ window.__ModuleLoader__.load({
|
|
|
559
567
|
setBusy(true); setErr('')
|
|
560
568
|
const e2eVal = e2e.trim()
|
|
561
569
|
fetch('/dshn/configure', { method: 'POST', headers: { 'content-type': 'application/json' },
|
|
562
|
-
body: JSON.stringify({ subdomain: prefix.trim().toLowerCase(), password: pw,
|
|
570
|
+
body: JSON.stringify({ subdomain: prefix.trim().toLowerCase(), password: pw,
|
|
571
|
+
relayHost: mode === 'selfhost' ? relayHost.trim() : '', originCa: mode === 'selfhost' ? originCa.trim() : '' }) })
|
|
563
572
|
.then((r) => r.json().then((j) => ({ ok: r.ok, j })))
|
|
564
573
|
.then(async ({ ok, j }) => {
|
|
565
574
|
if (!ok) { setBusy(false); setErr((j && j.error) || 'failed'); return }
|
|
@@ -620,10 +629,31 @@ window.__ModuleLoader__.load({
|
|
|
620
629
|
|
|
621
630
|
liveErr ? h('div', { className: 'dshn-err' }, liveErr) : null,
|
|
622
631
|
|
|
632
|
+
// Mode: official ds.hn vs self-hosted. The subdomain + password + e2e
|
|
633
|
+
// below are shared; self-hosted just adds where your own relay lives.
|
|
634
|
+
h('div', { className: 'dshn-field' },
|
|
635
|
+
h('span', null, T.mode),
|
|
636
|
+
h('div', { className: 'dshn-modeseg' },
|
|
637
|
+
h('button', { type: 'button', className: 'dshn-modebtn' + (mode === 'official' ? ' dshn-on' : ''), onClick: () => setMode('official') }, T.modeOfficial),
|
|
638
|
+
h('button', { type: 'button', className: 'dshn-modebtn' + (mode === 'selfhost' ? ' dshn-on' : ''), onClick: () => setMode('selfhost') }, T.modeSelf))),
|
|
639
|
+
|
|
640
|
+
mode === 'selfhost' ? h('label', { className: 'dshn-field' },
|
|
641
|
+
h('span', null, T.relayHost),
|
|
642
|
+
h('input', { className: 'dshn-input', value: relayHost, placeholder: 'wss://tunnel.example.com',
|
|
643
|
+
autoComplete: 'off', spellCheck: false, autoFocus: !configured, onChange: (ev) => setRelayHost(ev.target.value) }),
|
|
644
|
+
h('div', { className: 'dshn-hint' }, T.relayHostHint)) : null,
|
|
645
|
+
// Self-signed CA — hidden behind a toggle; only the rare self-signed relay
|
|
646
|
+
// needs it (valid cert / behind Cloudflare needs nothing).
|
|
647
|
+
mode === 'selfhost' ? h('div', { className: 'dshn-field' },
|
|
648
|
+
h('button', { type: 'button', className: 'dshn-catoggle', onClick: () => setShowCa(!showCa) }, (showCa ? '▾ ' : '▸ ') + T.relayCa),
|
|
649
|
+
showCa ? h('textarea', { className: 'dshn-input dshn-ca', value: originCa, rows: 3, spellCheck: false, style: { marginTop: '6px' },
|
|
650
|
+
placeholder: '-----BEGIN CERTIFICATE-----', onChange: (ev) => setOriginCa(ev.target.value) }) : null,
|
|
651
|
+
showCa ? h('div', { className: 'dshn-hint' }, T.relayCaHint) : null) : null,
|
|
652
|
+
|
|
623
653
|
h('label', { className: 'dshn-field' },
|
|
624
654
|
h('span', null, T.prefix),
|
|
625
655
|
h('div', { className: 'dshn-prefixwrap' },
|
|
626
|
-
h('input', { className: 'dshn-input', value: prefix, placeholder: 'alice', autoFocus: !configured,
|
|
656
|
+
h('input', { className: 'dshn-input', value: prefix, placeholder: 'alice', autoFocus: !configured && mode !== 'selfhost',
|
|
627
657
|
onChange: (e) => setPrefix(e.target.value.toLowerCase()) }),
|
|
628
658
|
h('span', { className: 'dshn-apex' }, '.' + apex)),
|
|
629
659
|
!configured ? h('div', { className: 'dshn-hint' }, T.prefixHint) : null),
|
|
@@ -697,24 +727,6 @@ window.__ModuleLoader__.load({
|
|
|
697
727
|
h('div', { className: 'dshn-hint', style: { marginTop: '7px' } }, T.e2eHint + ' ' + T.e2eIndep))
|
|
698
728
|
})() : null,
|
|
699
729
|
|
|
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
730
|
// Disconnecting severs public access and the password is unrecoverable
|
|
719
731
|
// from the cloud — so it takes an explicit, spelled-out confirmation.
|
|
720
732
|
confirmDc ? h('div', { className: 'dshn-dcwarn' },
|