@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.
Files changed (2) hide show
  1. package/client.js +51 -39
  2. 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-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
+ .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
- advanced: '高级 · 自托管中继', relayHost: '中继地址',
396
- relayHostHint: '自托管时填你自己的中继,如 wss://tunnel.example.com;留空即用默认。改动后点“连接/重新连接”生效。',
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
- 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.',
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
- // Self-hosted relay overrides (advanced). Pre-filled from the saved settings.
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
- const [showAdvanced, setShowAdvanced] = react.useState(!!rs0.relayHost)
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); setShowAdvanced(true) }
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 not the default ds.hn before you've connected. Empty →
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 s.apex || 'ds.hn'
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 || (s.apex || 'ds.hn')
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 canSubmit = prefix.trim().length > 0 && !busy && e2eRegOk
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, relayHost: relayHost.trim(), originCa: originCa.trim() }) })
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' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dshn/agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Forward a local dsh web service to the public internet over ds.hn (bundled).",
5
5
  "keywords": [
6
6
  "dsh",