@skillpie/dsh-remote 1.1.0 → 1.2.0
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/lib/index.js +3 -1
- package/lib/relay-core.js +108 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -327,7 +327,9 @@ export default {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
// 7. Visitor restore worker: conceals the code from the address bar and
|
|
330
|
-
// keeps the bare origin reopening the tunnel (see concealScript).
|
|
330
|
+
// keeps the bare origin reopening the tunnel (see concealScript). The
|
|
331
|
+
// worker also probes 8-digit code paths and swaps a 404 (unknown or
|
|
332
|
+
// offline-revoked code) for the code-entry dialog.
|
|
331
333
|
if (mode === 'gui' && (direct || relayFaceEnabled)) {
|
|
332
334
|
disposers.push(webServer.register({
|
|
333
335
|
kind: 'exact',
|
package/lib/relay-core.js
CHANGED
|
@@ -65,6 +65,92 @@ export class RelayCore {
|
|
|
65
65
|
this.cmdSeq = 0
|
|
66
66
|
this.timers = new Set()
|
|
67
67
|
this.closed = false
|
|
68
|
+
this.enterDisposers = []
|
|
69
|
+
// 未知远程码兜底:webServer 的前缀匹配是「p 或 p/」语义,任何短于 8 字符
|
|
70
|
+
// 的前缀都命中不了 /<code>/ 路径,所以无法用通配路由接管数字码;改为
|
|
71
|
+
// ①独立输码页路由(供手动访问/边缘转发目标),②由 index.js 用
|
|
72
|
+
// webServer.tapIndex 把守卫脚本注进服务器 SPA 文档——未知码路径命中的
|
|
73
|
+
// 正是 SPA fallback 渲染的 index,守卫脚本据 pathname 弹输码框。
|
|
74
|
+
if (this.mode === 'gui') {
|
|
75
|
+
try {
|
|
76
|
+
this.enterDisposers.push(this.webServer.register({
|
|
77
|
+
kind: 'prefix',
|
|
78
|
+
path: '/dsh-remote-enter',
|
|
79
|
+
handler: (req, res) => {
|
|
80
|
+
try { this.serveEnter(req, res) } catch (error) {
|
|
81
|
+
this.log('relay', `输码页请求失败(已隔离): ${error?.message ?? error}`)
|
|
82
|
+
try { res.writeHead(500).end() } catch { /* gone */ }
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
}))
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.log('relay', `输码路由注册失败(已隔离): ${error?.message ?? error}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Standalone code-entry page: `/dsh-remote-enter?code=NNNNNNNN` renders the
|
|
94
|
+
* dialog pre-flagged with that code as invalid; without a code it opens the
|
|
95
|
+
* blank dialog. Also the redirect target for edge-level unknown-code rules.
|
|
96
|
+
*/
|
|
97
|
+
serveEnter(req, res) {
|
|
98
|
+
let code = ''
|
|
99
|
+
try { code = new URL(req.url ?? '/', 'http://localhost').searchParams.get('code') ?? '' } catch { code = '' }
|
|
100
|
+
const send = (status, body, headers = {}) => {
|
|
101
|
+
if (res.headersSent) { try { res.destroy() } catch { res.end() } ; return }
|
|
102
|
+
res.writeHead(status, { 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff', ...headers })
|
|
103
|
+
res.end(body)
|
|
104
|
+
}
|
|
105
|
+
const bad = /^[1-9]\d{7}$/.test(code) ? code : ''
|
|
106
|
+
if (code !== '' && bad === '') {
|
|
107
|
+
send(400, 'bad code\n', { 'Content-Type': 'text/plain; charset=utf-8' })
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
send(200, this.enterHtml(bad), { 'Content-Type': 'text/html; charset=utf-8' })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Code-entry page: shown for a well-formed but unknown remote code instead
|
|
115
|
+
* of the SPA's 404. Asks the visitor for a valid code and forwards to
|
|
116
|
+
* /<code>/, where the normal conceal+tunnel flow takes over — the entered
|
|
117
|
+
* code never lingers in a form, and a live code behaves exactly like
|
|
118
|
+
* opening its link for the first time.
|
|
119
|
+
*/
|
|
120
|
+
enterHtml(badCode) {
|
|
121
|
+
const hint = badCode
|
|
122
|
+
? `<div class="err">远程码 <code>${badCode}</code> 无效或宿主机未在线,请核对后重试。</div>`
|
|
123
|
+
: ''
|
|
124
|
+
return `<!DOCTYPE html>
|
|
125
|
+
<html lang="zh-CN"><head><meta charset="utf-8">
|
|
126
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
127
|
+
<meta name="robots" content="noindex">
|
|
128
|
+
<title>DeepSeek 远程访问</title>
|
|
129
|
+
<style>body{margin:0;height:100vh;display:flex;align-items:center;justify-content:center;background:#101114;color:#e8eaed;font:14px/1.6 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
|
|
130
|
+
.card{width:min(360px,90vw);background:#1a1c21;border:1px solid #2a2d34;border-radius:14px;padding:28px 26px;box-sizing:border-box}
|
|
131
|
+
h1{margin:0 0 6px;font-size:16px;font-weight:600}.sub{margin:0 0 18px;font-size:12.5px;color:#9aa0a6}
|
|
132
|
+
.row{display:flex;gap:8px}
|
|
133
|
+
input{flex:1;min-width:0;background:#101114;border:1px solid #2a2d34;border-radius:8px;color:#e8eaed;padding:9px 12px;font:15px ui-monospace,Menlo,monospace;letter-spacing:.12em;outline:none}
|
|
134
|
+
input:focus{border-color:#6b84ff}
|
|
135
|
+
button{background:#4a5cff;border:0;border-radius:8px;color:#fff;padding:9px 16px;font:14px inherit;cursor:pointer}
|
|
136
|
+
button:hover{background:#5b6bff}
|
|
137
|
+
.err{margin-top:14px;font-size:12.5px;color:#ff8a80;word-break:break-all}code{font-family:ui-monospace,Menlo,monospace;color:#ffb4a9}
|
|
138
|
+
.tip{margin-top:16px;font-size:11.5px;color:#6b7078}</style></head>
|
|
139
|
+
<body><div class="card"><h1>DeepSeek 远程访问</h1><p class="sub">请输入 8 位远程码以连接对应的宿主机。</p>
|
|
140
|
+
${hint}
|
|
141
|
+
<form class="row"><input name="code" inputmode="numeric" autocomplete="off" maxlength="8" placeholder="12345678" autofocus><button type="submit">进入</button></form>
|
|
142
|
+
<div class="tip">远程码由宿主机属主面板提供,仅在宿主机在线时可用。</div></div>
|
|
143
|
+
<script>(function(){
|
|
144
|
+
var f=document.querySelector('form');var i=document.querySelector('input');
|
|
145
|
+
i.addEventListener('input',function(){i.value=i.value.replace(/\\D/g,'').slice(0,8)});
|
|
146
|
+
f.addEventListener('submit',function(ev){
|
|
147
|
+
ev.preventDefault();
|
|
148
|
+
var c=i.value;
|
|
149
|
+
if(!/^[1-9]\\d{7}$/.test(c)){i.focus();return}
|
|
150
|
+
location.assign('/'+c+'/');
|
|
151
|
+
});
|
|
152
|
+
i.focus();
|
|
153
|
+
})();</script></body></html>`
|
|
68
154
|
}
|
|
69
155
|
|
|
70
156
|
later(fn, ms) {
|
|
@@ -951,6 +1037,8 @@ try{document.querySelectorAll('[role=menu]').forEach(patch)}catch(e){}
|
|
|
951
1037
|
|
|
952
1038
|
detachAll() {
|
|
953
1039
|
this.closed = true
|
|
1040
|
+
for (const dispose of this.enterDisposers) { try { dispose() } catch { /* gone */ } }
|
|
1041
|
+
this.enterDisposers = []
|
|
954
1042
|
for (const timer of this.timers) clearTimeout(timer)
|
|
955
1043
|
this.timers.clear()
|
|
956
1044
|
for (const hostId of [...this.hosts.keys()]) this.removeHost(hostId, 'relay shutting down')
|
|
@@ -989,21 +1077,40 @@ export function serviceWorkerScript() {
|
|
|
989
1077
|
return `/* dsh-remote visitor restore worker */
|
|
990
1078
|
var CODE='';
|
|
991
1079
|
try{CODE=new URL(self.location.href).searchParams.get('code')||''}catch(e){}
|
|
1080
|
+
var CODE_RE=/^\\/([1-9]\\d{7})(\\/|$)/;
|
|
992
1081
|
self.addEventListener('install',function(e){self.skipWaiting()});
|
|
993
1082
|
self.addEventListener('activate',function(e){e.waitUntil(self.clients.claim())});
|
|
994
1083
|
self.addEventListener('fetch',function(event){
|
|
995
1084
|
try{
|
|
996
1085
|
var req=event.request;
|
|
1086
|
+
if(req.method!=='GET')return;
|
|
997
1087
|
if(req.mode!=='navigate')return;
|
|
998
1088
|
var url=new URL(req.url);
|
|
999
1089
|
if(url.origin!==self.location.origin)return;
|
|
1000
1090
|
if((url.search||'').indexOf('gui')!==-1)return;
|
|
1001
1091
|
if((req.headers.get('cookie')||'').indexOf('dsh-auth-')!==-1)return;
|
|
1092
|
+
/* Unknown-code guard: an 8-digit path that answers 404 (no live host)
|
|
1093
|
+
gets the code-entry dialog instead of the bare 404 page. */
|
|
1094
|
+
var cm=url.pathname.match(CODE_RE);
|
|
1095
|
+
if(cm){
|
|
1096
|
+
event.respondWith((async function(){
|
|
1097
|
+
var probe;
|
|
1098
|
+
try{probe=await fetch(new Request(req.url,{method:'HEAD',cache:'no-store'}))}catch(e){return fetch(req)}
|
|
1099
|
+
if(probe&&probe.status===404){
|
|
1100
|
+
try{return await fetch('/dsh-remote-enter?code='+cm[1],{cache:'no-store'})}catch(e){return fetch(req)}
|
|
1101
|
+
}
|
|
1102
|
+
return fetch(req);
|
|
1103
|
+
})());
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1002
1106
|
if(url.pathname!=='/'&&url.pathname!=='')return;
|
|
1003
1107
|
if(!/^[1-9]\\d{7}$/.test(CODE))return;
|
|
1004
1108
|
event.respondWith((async function(){
|
|
1005
1109
|
var up;
|
|
1006
1110
|
try{up=await fetch('/'+CODE+'/',{cache:'no-store'})}catch(e){return}
|
|
1111
|
+
if(up.status===404){
|
|
1112
|
+
try{return await fetch('/dsh-remote-enter?code='+CODE,{cache:'no-store'})}catch(e){return fetch(req)}
|
|
1113
|
+
}
|
|
1007
1114
|
var ct=up.headers.get('content-type')||'';
|
|
1008
1115
|
if(ct.indexOf('text/html')===-1)return up;
|
|
1009
1116
|
var text=await up.text();
|
|
@@ -1023,3 +1130,4 @@ export function serveServiceWorker(req, res) {
|
|
|
1023
1130
|
})
|
|
1024
1131
|
res.end(serviceWorkerScript())
|
|
1025
1132
|
}
|
|
1133
|
+
|
package/package.json
CHANGED