dsh-link-pulse 0.1.3 → 0.1.4
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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/index.js +97 -41
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import fs from 'node:fs'
|
|
4
|
+
import tls from 'node:tls'
|
|
5
|
+
import net from 'node:net'
|
|
4
6
|
import jsYaml from 'js-yaml'
|
|
5
7
|
|
|
6
8
|
export const name = 'link-pulse'
|
|
@@ -77,57 +79,111 @@ export function loadDynamicProviders() {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
/**
|
|
83
|
+
* 测量 Keep-Alive 状态下的真实单次 RTT 往返延迟
|
|
84
|
+
* (在已建立的加密 socket 上发送 HEAD 并测量首字节返回耗时)
|
|
85
|
+
*/
|
|
80
86
|
export async function probeLatency(url) {
|
|
81
87
|
if (!url || !/^https?:\/\//i.test(url)) {
|
|
82
88
|
return { latencyMs: -1, status: 'no_url', quality: 'offline', error: '无有效网络地址' }
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const controller = new AbortController()
|
|
93
|
-
const timeoutId = setTimeout(() => controller.abort(), 3500)
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const res = await fetch(probeUrl, {
|
|
97
|
-
method: 'HEAD',
|
|
98
|
-
signal: controller.signal,
|
|
99
|
-
headers: { 'User-Agent': 'DSH-LinkPulse/1.0' },
|
|
100
|
-
}).catch(async () => {
|
|
101
|
-
return await fetch(probeUrl, {
|
|
102
|
-
method: 'GET',
|
|
103
|
-
signal: controller.signal,
|
|
104
|
-
headers: { 'User-Agent': 'DSH-LinkPulse/1.0' },
|
|
105
|
-
})
|
|
106
|
-
})
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
let parsed
|
|
93
|
+
try {
|
|
94
|
+
parsed = new URL(url)
|
|
95
|
+
} catch {
|
|
96
|
+
return resolve({ latencyMs: -1, quality: 'error', reachable: false, error: 'URL 格式错误' })
|
|
97
|
+
}
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
const
|
|
99
|
+
const host = parsed.hostname
|
|
100
|
+
const isHttps = parsed.protocol === 'https:'
|
|
101
|
+
const port = parseInt(parsed.port, 10) || (isHttps ? 443 : 80)
|
|
102
|
+
const timeout = 3500
|
|
110
103
|
|
|
111
|
-
let
|
|
112
|
-
|
|
113
|
-
else if (latencyMs > 200) quality = 'good'
|
|
104
|
+
let timer = null
|
|
105
|
+
let socket = null
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
const cleanup = () => {
|
|
108
|
+
if (timer) clearTimeout(timer)
|
|
109
|
+
if (socket) {
|
|
110
|
+
socket.removeAllListeners()
|
|
111
|
+
socket.destroy()
|
|
112
|
+
}
|
|
120
113
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
|
|
115
|
+
timer = setTimeout(() => {
|
|
116
|
+
cleanup()
|
|
117
|
+
resolve({ latencyMs: 3500, quality: 'timeout', reachable: false, error: '请求超时 (>3.5s)' })
|
|
118
|
+
}, timeout)
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const onEstablished = (sock) => {
|
|
122
|
+
const start = performance.now()
|
|
123
|
+
const path = parsed.pathname || '/'
|
|
124
|
+
sock.write(`HEAD ${path} HTTP/1.1\r\nHost: ${host}\r\nUser-Agent: DSH-LinkPulse/1.0\r\nConnection: close\r\n\r\n`)
|
|
125
|
+
|
|
126
|
+
sock.once('data', () => {
|
|
127
|
+
const latencyMs = Math.max(1, Math.round(performance.now() - start))
|
|
128
|
+
cleanup()
|
|
129
|
+
|
|
130
|
+
let quality = 'excellent'
|
|
131
|
+
if (latencyMs > 600) quality = 'slow'
|
|
132
|
+
else if (latencyMs > 200) quality = 'good'
|
|
133
|
+
|
|
134
|
+
resolve({
|
|
135
|
+
latencyMs,
|
|
136
|
+
quality,
|
|
137
|
+
reachable: true,
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (isHttps) {
|
|
143
|
+
socket = tls.connect({
|
|
144
|
+
host,
|
|
145
|
+
port,
|
|
146
|
+
servername: host,
|
|
147
|
+
rejectUnauthorized: false,
|
|
148
|
+
timeout,
|
|
149
|
+
}, () => onEstablished(socket))
|
|
150
|
+
} else {
|
|
151
|
+
socket = net.connect({
|
|
152
|
+
host,
|
|
153
|
+
port,
|
|
154
|
+
timeout,
|
|
155
|
+
}, () => onEstablished(socket))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
socket.on('error', (err) => {
|
|
159
|
+
cleanup()
|
|
160
|
+
resolve({
|
|
161
|
+
latencyMs: -1,
|
|
162
|
+
quality: 'error',
|
|
163
|
+
reachable: false,
|
|
164
|
+
error: err.message || '连接失败',
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
socket.on('timeout', () => {
|
|
169
|
+
cleanup()
|
|
170
|
+
resolve({
|
|
171
|
+
latencyMs: 3500,
|
|
172
|
+
quality: 'timeout',
|
|
173
|
+
reachable: false,
|
|
174
|
+
error: '连接超时',
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
} catch (err) {
|
|
178
|
+
cleanup()
|
|
179
|
+
resolve({
|
|
180
|
+
latencyMs: -1,
|
|
181
|
+
quality: 'error',
|
|
182
|
+
reachable: false,
|
|
183
|
+
error: err.message || '连接异常',
|
|
184
|
+
})
|
|
129
185
|
}
|
|
130
|
-
}
|
|
186
|
+
})
|
|
131
187
|
}
|
|
132
188
|
|
|
133
189
|
function sendJson(res, statusCode, data) {
|