@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.
Files changed (2) hide show
  1. package/client.js +51 -35
  2. 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-adv { margin: 2px 0 12px; }
335
- .dshn-adv-toggle { border: 0; background: transparent; cursor: pointer; padding: 4px 0; font-size: 11.5px;
336
- color: var(--dsw-alias-label-tertiary, #8b9099); font-family: inherit; }
337
- .dshn-adv-toggle:hover { color: var(--dsw-alias-label-primary, #1c1e21); }
338
- .dshn-adv-body { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; padding: 11px 12px; border-radius: 10px;
339
- border: 1px solid var(--dsw-alias-border-l2, rgba(128,134,142,.25)); background: var(--dsw-alias-bg-layer-2, #f4f5f7); }
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
- advanced: '高级 · 自托管中继', relayHost: '中继地址',
396
- relayHostHint: '自托管时填你自己的中继,如 wss://tunnel.example.com;留空即用默认。改动后点“连接/重新连接”生效。',
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
- advanced: 'Advanced · self-hosted relay', relayHost: 'Relay host',
420
- relayHostHint: 'For self-hosting, your own relay, e.g. wss://tunnel.example.com; blank uses the default. Connect / Reconnect to apply.',
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
- // Self-hosted relay overrides (advanced). Pre-filled from the saved settings.
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); setShowAdvanced(true) }
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 canSubmit = prefix.trim().length > 0 && !busy && e2eRegOk
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, relayHost: relayHost.trim(), originCa: originCa.trim() }) })
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' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dshn/agent",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Forward a local dsh web service to the public internet over ds.hn (bundled).",
5
5
  "keywords": [
6
6
  "dsh",