@delt/claude-alarm 0.9.0 → 0.9.1
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/README.md +29 -4
- package/dist/cli.js +156 -37
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +26 -33
- package/dist/hub/server.js +137 -32
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +137 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/dashboard/index.html +26 -33
|
@@ -871,7 +871,7 @@
|
|
|
871
871
|
<div class="token-overlay hidden" id="tokenOverlay">
|
|
872
872
|
<div class="token-form">
|
|
873
873
|
<h2>Authentication Required</h2>
|
|
874
|
-
<p>Enter the hub token to connect.
|
|
874
|
+
<p>Enter the hub token to connect. Run <code>claude-alarm token</code>, or open the login link printed by <code>claude-alarm hub start</code>.</p>
|
|
875
875
|
<input type="text" id="tokenInput" placeholder="Paste token here..." autocomplete="off">
|
|
876
876
|
<button id="tokenSubmit">Connect</button>
|
|
877
877
|
<div class="token-error" id="tokenError">Connection failed. Check your token.</div>
|
|
@@ -1026,7 +1026,6 @@
|
|
|
1026
1026
|
selectedSession: null,
|
|
1027
1027
|
messages: {},
|
|
1028
1028
|
notifications: [],
|
|
1029
|
-
token: null,
|
|
1030
1029
|
pendingImage: null,
|
|
1031
1030
|
unread: {},
|
|
1032
1031
|
waitingReply: {},
|
|
@@ -1291,25 +1290,28 @@
|
|
|
1291
1290
|
initTheme();
|
|
1292
1291
|
|
|
1293
1292
|
// --- Token handling ---
|
|
1294
|
-
function
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
return urlToken;
|
|
1293
|
+
async function checkAuth() {
|
|
1294
|
+
try {
|
|
1295
|
+
return (await fetch('/api/status')).status !== 401;
|
|
1296
|
+
} catch {
|
|
1297
|
+
return true;
|
|
1300
1298
|
}
|
|
1301
|
-
return sessionStorage.getItem('claude-alarm-token');
|
|
1302
1299
|
}
|
|
1303
1300
|
|
|
1304
1301
|
function showTokenForm() { $('#tokenOverlay').classList.remove('hidden'); }
|
|
1305
1302
|
function hideTokenForm() { $('#tokenOverlay').classList.add('hidden'); }
|
|
1306
1303
|
|
|
1307
|
-
$('#tokenSubmit').addEventListener('click', () => {
|
|
1304
|
+
$('#tokenSubmit').addEventListener('click', async () => {
|
|
1308
1305
|
const token = $('#tokenInput').value.trim();
|
|
1309
1306
|
if (!token) return;
|
|
1310
|
-
|
|
1311
|
-
|
|
1307
|
+
const res = await fetch('/api/login', {
|
|
1308
|
+
method: 'POST',
|
|
1309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1310
|
+
body: JSON.stringify({ token }),
|
|
1311
|
+
}).catch(() => null);
|
|
1312
|
+
if (!res || !res.ok) { $('#tokenError').style.display = 'block'; return; }
|
|
1312
1313
|
$('#tokenError').style.display = 'none';
|
|
1314
|
+
$('#tokenInput').value = '';
|
|
1313
1315
|
hideTokenForm();
|
|
1314
1316
|
connect();
|
|
1315
1317
|
});
|
|
@@ -1320,25 +1322,23 @@
|
|
|
1320
1322
|
// --- WebSocket ---
|
|
1321
1323
|
function connect() {
|
|
1322
1324
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
+
const ws = new WebSocket(`${proto}://${location.host}/ws/dashboard`);
|
|
1326
|
+
let opened = false;
|
|
1325
1327
|
|
|
1326
1328
|
ws.onopen = () => {
|
|
1329
|
+
opened = true;
|
|
1327
1330
|
state.ws = ws;
|
|
1328
1331
|
hideTokenForm();
|
|
1329
1332
|
$('#connDot').classList.add('connected');
|
|
1330
1333
|
$('#connLabel').textContent = 'Connected';
|
|
1331
1334
|
};
|
|
1332
|
-
ws.onclose = () => {
|
|
1335
|
+
ws.onclose = async () => {
|
|
1333
1336
|
state.ws = null;
|
|
1334
1337
|
$('#connDot').classList.remove('connected');
|
|
1335
1338
|
$('#connLabel').textContent = 'Disconnected';
|
|
1339
|
+
if (!opened && !(await checkAuth())) { showTokenForm(); return; }
|
|
1336
1340
|
setTimeout(connect, 3000);
|
|
1337
1341
|
};
|
|
1338
|
-
ws.onerror = () => {
|
|
1339
|
-
if (!state.token) { showTokenForm(); } else { $('#tokenError').style.display = 'block'; showTokenForm(); }
|
|
1340
|
-
ws.close();
|
|
1341
|
-
};
|
|
1342
1342
|
ws.onmessage = (e) => { try { handleMessage(JSON.parse(e.data)); } catch {} };
|
|
1343
1343
|
}
|
|
1344
1344
|
|
|
@@ -2128,16 +2128,14 @@
|
|
|
2128
2128
|
$('#settingsToggle').addEventListener('click', async () => {
|
|
2129
2129
|
// Load webhook data
|
|
2130
2130
|
try {
|
|
2131
|
-
const
|
|
2132
|
-
const res = await fetch(`/api/webhooks${tokenQuery}`);
|
|
2131
|
+
const res = await fetch(`/api/webhooks`);
|
|
2133
2132
|
const data = await res.json();
|
|
2134
2133
|
webhookData = data.webhooks || [];
|
|
2135
2134
|
} catch { webhookData = []; }
|
|
2136
2135
|
renderWebhooks();
|
|
2137
2136
|
// Load telegram data
|
|
2138
2137
|
try {
|
|
2139
|
-
const
|
|
2140
|
-
const res = await fetch(`/api/telegram${tokenQuery}`);
|
|
2138
|
+
const res = await fetch(`/api/telegram`);
|
|
2141
2139
|
const data = await res.json();
|
|
2142
2140
|
const tg = data.telegram || {};
|
|
2143
2141
|
if (!$('#tgBotToken').value || $('#tgBotToken').value.includes('...')) {
|
|
@@ -2172,8 +2170,7 @@
|
|
|
2172
2170
|
$('#webhookSave').addEventListener('click', async () => {
|
|
2173
2171
|
const valid = webhookData.filter(w => w.url.trim());
|
|
2174
2172
|
try {
|
|
2175
|
-
|
|
2176
|
-
await fetch(`/api/webhooks${tokenQuery}`, {
|
|
2173
|
+
await fetch(`/api/webhooks`, {
|
|
2177
2174
|
method: 'POST',
|
|
2178
2175
|
headers: { 'Content-Type': 'application/json' },
|
|
2179
2176
|
body: JSON.stringify({ webhooks: valid }),
|
|
@@ -2248,8 +2245,7 @@
|
|
|
2248
2245
|
$('#tgDetectStatus').textContent = 'Detecting...';
|
|
2249
2246
|
$('#tgChatList').innerHTML = '';
|
|
2250
2247
|
try {
|
|
2251
|
-
const
|
|
2252
|
-
const res = await fetch(`/api/telegram/detect${tokenQuery}`, {
|
|
2248
|
+
const res = await fetch(`/api/telegram/detect`, {
|
|
2253
2249
|
method: 'POST',
|
|
2254
2250
|
headers: { 'Content-Type': 'application/json' },
|
|
2255
2251
|
body: JSON.stringify({ botToken }),
|
|
@@ -2286,8 +2282,7 @@
|
|
|
2286
2282
|
const chatId = $('#tgChatId').value.trim();
|
|
2287
2283
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
2288
2284
|
try {
|
|
2289
|
-
const
|
|
2290
|
-
const res = await fetch(`/api/telegram/test${tokenQuery}`, {
|
|
2285
|
+
const res = await fetch(`/api/telegram/test`, {
|
|
2291
2286
|
method: 'POST',
|
|
2292
2287
|
headers: { 'Content-Type': 'application/json' },
|
|
2293
2288
|
body: JSON.stringify({ botToken, chatId }),
|
|
@@ -2306,8 +2301,7 @@
|
|
|
2306
2301
|
if (!botToken || !chatId) { showTgStatus('Bot Token and Chat ID are required.', false); return; }
|
|
2307
2302
|
if (botToken.includes('...')) { showTgStatus('Bot Token is masked. Please enter the full token.', false); return; }
|
|
2308
2303
|
try {
|
|
2309
|
-
|
|
2310
|
-
await fetch(`/api/telegram${tokenQuery}`, {
|
|
2304
|
+
await fetch(`/api/telegram`, {
|
|
2311
2305
|
method: 'POST',
|
|
2312
2306
|
headers: { 'Content-Type': 'application/json' },
|
|
2313
2307
|
body: JSON.stringify({ telegram: { botToken, chatId, enabled } }),
|
|
@@ -2335,8 +2329,7 @@
|
|
|
2335
2329
|
document.querySelector('.sessions-panel').classList.add('mobile-active');
|
|
2336
2330
|
}
|
|
2337
2331
|
|
|
2338
|
-
|
|
2339
|
-
connect();
|
|
2332
|
+
checkAuth().then((ok) => (ok ? connect() : showTokenForm()));
|
|
2340
2333
|
})();
|
|
2341
2334
|
</script>
|
|
2342
2335
|
</body>
|
package/dist/hub/server.js
CHANGED
|
@@ -521,6 +521,7 @@ ${previewHtml}${truncNote}`;
|
|
|
521
521
|
}
|
|
522
522
|
async handleCallbackQuery(query) {
|
|
523
523
|
if (!query.data) return;
|
|
524
|
+
if (String(query.message?.chat.id) !== String(this.config.chatId)) return;
|
|
524
525
|
if (query.data.startsWith("sess:")) {
|
|
525
526
|
await this.handleSessionSelectCallback(query);
|
|
526
527
|
return;
|
|
@@ -760,8 +761,66 @@ function logStartup(context) {
|
|
|
760
761
|
`);
|
|
761
762
|
}
|
|
762
763
|
|
|
764
|
+
// src/hub/auth.ts
|
|
765
|
+
import crypto from "crypto";
|
|
766
|
+
var SESSION_COOKIE = "ca_session";
|
|
767
|
+
var COOKIE_MAX_AGE = 30 * 24 * 60 * 60;
|
|
768
|
+
function safeEqual(a, b) {
|
|
769
|
+
if (typeof a !== "string") return false;
|
|
770
|
+
const x = Buffer.from(a);
|
|
771
|
+
const y = Buffer.from(b);
|
|
772
|
+
return x.length === y.length && crypto.timingSafeEqual(x, y);
|
|
773
|
+
}
|
|
774
|
+
function dashboardCookieValue(token) {
|
|
775
|
+
return crypto.createHmac("sha256", token).update("claude-alarm-dashboard").digest("hex");
|
|
776
|
+
}
|
|
777
|
+
function parseCookies(header) {
|
|
778
|
+
const out = {};
|
|
779
|
+
for (const part of (header ?? "").split(";")) {
|
|
780
|
+
const i = part.indexOf("=");
|
|
781
|
+
if (i <= 0) continue;
|
|
782
|
+
const raw = part.slice(i + 1).trim();
|
|
783
|
+
let value = raw;
|
|
784
|
+
try {
|
|
785
|
+
value = decodeURIComponent(raw);
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
out[part.slice(0, i).trim()] = value;
|
|
789
|
+
}
|
|
790
|
+
return out;
|
|
791
|
+
}
|
|
792
|
+
function sessionCookieHeader(token, secure) {
|
|
793
|
+
return `${SESSION_COOKIE}=${dashboardCookieValue(token)}; HttpOnly; SameSite=Strict; Path=/; Max-Age=${COOKIE_MAX_AGE}${secure ? "; Secure" : ""}`;
|
|
794
|
+
}
|
|
795
|
+
function isAuthorized(req, token, allowQueryToken) {
|
|
796
|
+
const auth = req.headers.authorization;
|
|
797
|
+
if (auth?.startsWith("Bearer ") && safeEqual(auth.slice(7), token)) return true;
|
|
798
|
+
if (safeEqual(parseCookies(req.headers.cookie)[SESSION_COOKIE], dashboardCookieValue(token))) return true;
|
|
799
|
+
if (allowQueryToken) {
|
|
800
|
+
const q = new URL(req.url ?? "/", "http://hub").searchParams.get("token");
|
|
801
|
+
if (safeEqual(q, token)) return true;
|
|
802
|
+
}
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
function isCrossOrigin(req) {
|
|
806
|
+
const origin = req.headers.origin;
|
|
807
|
+
if (!origin) return false;
|
|
808
|
+
try {
|
|
809
|
+
return new URL(origin).host !== req.headers.host;
|
|
810
|
+
} catch {
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function isJsonRequest(req) {
|
|
815
|
+
return String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json");
|
|
816
|
+
}
|
|
817
|
+
function isSecureRequest(req) {
|
|
818
|
+
return req.headers["x-forwarded-proto"] === "https";
|
|
819
|
+
}
|
|
820
|
+
|
|
763
821
|
// src/hub/server.ts
|
|
764
822
|
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
823
|
+
var MAX_WS_PAYLOAD = 16 * 1024 * 1024;
|
|
765
824
|
var HubServer = class {
|
|
766
825
|
httpServer;
|
|
767
826
|
wssChannel;
|
|
@@ -779,6 +838,7 @@ var HubServer = class {
|
|
|
779
838
|
heartbeatInterval;
|
|
780
839
|
channelAlive = /* @__PURE__ */ new Map();
|
|
781
840
|
// sessionId -> alive flag
|
|
841
|
+
socketOwners = /* @__PURE__ */ new WeakMap();
|
|
782
842
|
host;
|
|
783
843
|
port;
|
|
784
844
|
token;
|
|
@@ -801,22 +861,23 @@ var HubServer = class {
|
|
|
801
861
|
this.initTelegram(fullConfig.telegram);
|
|
802
862
|
}
|
|
803
863
|
this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
|
|
804
|
-
this.wssChannel = new WebSocketServer({ noServer: true });
|
|
864
|
+
this.wssChannel = new WebSocketServer({ noServer: true, maxPayload: MAX_WS_PAYLOAD });
|
|
805
865
|
this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
|
|
806
866
|
this.wssChannel.on("error", (err) => logger.warn(`Channel WebSocket server error: ${err.message}`));
|
|
807
|
-
this.wssDashboard = new WebSocketServer({ noServer: true });
|
|
867
|
+
this.wssDashboard = new WebSocketServer({ noServer: true, maxPayload: MAX_WS_PAYLOAD });
|
|
808
868
|
this.wssDashboard.on("connection", (ws) => this.handleDashboardConnection(ws));
|
|
809
869
|
this.wssDashboard.on("error", (err) => logger.warn(`Dashboard WebSocket server error: ${err.message}`));
|
|
810
870
|
this.httpServer.on("upgrade", (req, socket, head) => {
|
|
811
871
|
socket.on("error", (err) => logger.debug(`Upgrade socket error: ${err.message}`));
|
|
812
|
-
const url = new URL(req.url,
|
|
872
|
+
const url = new URL(req.url ?? "/", "http://hub");
|
|
813
873
|
const pathname = url.pathname;
|
|
814
|
-
if (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
874
|
+
if (isCrossOrigin(req)) {
|
|
875
|
+
this.rejectUpgrade(socket, 403, "Forbidden", pathname, req);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (!this.authorized(req, true)) {
|
|
879
|
+
this.rejectUpgrade(socket, 401, "Unauthorized", pathname, req);
|
|
880
|
+
return;
|
|
820
881
|
}
|
|
821
882
|
if (pathname === WS_PATH_CHANNEL) {
|
|
822
883
|
this.wssChannel.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -868,31 +929,28 @@ var HubServer = class {
|
|
|
868
929
|
}
|
|
869
930
|
// --- HTTP Handler ---
|
|
870
931
|
handleHttp(req, res) {
|
|
871
|
-
const url = new URL(req.url,
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
875
|
-
}
|
|
876
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
877
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
878
|
-
if (req.method === "OPTIONS") {
|
|
879
|
-
res.writeHead(204);
|
|
880
|
-
res.end();
|
|
932
|
+
const url = new URL(req.url ?? "/", "http://hub");
|
|
933
|
+
if (isCrossOrigin(req)) {
|
|
934
|
+
this.jsonResponse(res, 403, { error: "Cross-origin request rejected" });
|
|
881
935
|
return;
|
|
882
936
|
}
|
|
883
|
-
if (
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
887
|
-
if (bearerToken !== this.token) {
|
|
888
|
-
this.jsonResponse(res, 401, { error: "Unauthorized" });
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
937
|
+
if (req.method === "POST" && !isJsonRequest(req)) {
|
|
938
|
+
this.jsonResponse(res, 415, { error: "Content-Type must be application/json" });
|
|
939
|
+
return;
|
|
892
940
|
}
|
|
893
941
|
if (url.pathname === "/" && req.method === "GET") {
|
|
894
|
-
this.serveDashboard(res);
|
|
895
|
-
|
|
942
|
+
this.serveDashboard(req, res, url);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (url.pathname === "/api/login" && req.method === "POST") {
|
|
946
|
+
this.handleLogin(req, res);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (!this.authorized(req, false)) {
|
|
950
|
+
this.jsonResponse(res, 401, { error: "Unauthorized" });
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (url.pathname === "/api/sessions" && req.method === "GET") {
|
|
896
954
|
this.jsonResponse(res, 200, { sessions: this.sessions.getAll() });
|
|
897
955
|
} else if (url.pathname === "/api/status" && req.method === "GET") {
|
|
898
956
|
this.jsonResponse(res, 200, {
|
|
@@ -943,7 +1001,13 @@ var HubServer = class {
|
|
|
943
1001
|
this.jsonResponse(res, 404, { error: "Not found" });
|
|
944
1002
|
}
|
|
945
1003
|
}
|
|
946
|
-
serveDashboard(res) {
|
|
1004
|
+
serveDashboard(req, res, url) {
|
|
1005
|
+
const linkToken = url.searchParams.get("token");
|
|
1006
|
+
if (linkToken !== null && this.token && safeEqual(linkToken, this.token)) {
|
|
1007
|
+
res.writeHead(302, { Location: "/", "Set-Cookie": sessionCookieHeader(this.token, isSecureRequest(req)) });
|
|
1008
|
+
res.end();
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
947
1011
|
const candidates = [
|
|
948
1012
|
path4.join(__dirname, "..", "dashboard", "index.html"),
|
|
949
1013
|
// from dist/hub/
|
|
@@ -970,6 +1034,20 @@ var HubServer = class {
|
|
|
970
1034
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
971
1035
|
res.end("<html><body><h1>claude-alarm</h1><p>Dashboard HTML not found. Reinstall the package.</p></body></html>");
|
|
972
1036
|
}
|
|
1037
|
+
async handleLogin(req, res) {
|
|
1038
|
+
const body = await this.readBody(req);
|
|
1039
|
+
if (!this.token) {
|
|
1040
|
+
res.writeHead(204);
|
|
1041
|
+
res.end();
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (typeof body?.token !== "string" || !safeEqual(body.token, this.token)) {
|
|
1045
|
+
this.jsonResponse(res, 401, { error: "Unauthorized" });
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
res.writeHead(204, { "Set-Cookie": sessionCookieHeader(this.token, isSecureRequest(req)) });
|
|
1049
|
+
res.end();
|
|
1050
|
+
}
|
|
973
1051
|
async handleApiSend(req, res) {
|
|
974
1052
|
const body = await this.readBody(req);
|
|
975
1053
|
if (!body) {
|
|
@@ -1046,6 +1124,22 @@ var HubServer = class {
|
|
|
1046
1124
|
});
|
|
1047
1125
|
}
|
|
1048
1126
|
handleChannelMessage(ws, isLocal, msg) {
|
|
1127
|
+
if (msg.type === "register") {
|
|
1128
|
+
const id = msg.session.id;
|
|
1129
|
+
const owned = this.socketOwners.get(ws);
|
|
1130
|
+
if (owned && owned !== id) {
|
|
1131
|
+
logger.warn(`Rejected register for ${id}: connection already owns ${owned}`);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
const holder = this.channelSockets.get(id);
|
|
1135
|
+
if (holder && holder !== ws) {
|
|
1136
|
+
this.socketOwners.delete(holder);
|
|
1137
|
+
holder.terminate();
|
|
1138
|
+
}
|
|
1139
|
+
this.socketOwners.set(ws, id);
|
|
1140
|
+
} else if ("sessionId" in msg && this.socketOwners.get(ws) !== msg.sessionId) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1049
1143
|
switch (msg.type) {
|
|
1050
1144
|
case "register": {
|
|
1051
1145
|
const session = msg.session;
|
|
@@ -1069,7 +1163,6 @@ var HubServer = class {
|
|
|
1069
1163
|
break;
|
|
1070
1164
|
}
|
|
1071
1165
|
case "peer_name": {
|
|
1072
|
-
if (this.channelSockets.get(msg.sessionId) !== ws) break;
|
|
1073
1166
|
const updated = this.sessions.setPeerName(msg.sessionId, msg.peerName);
|
|
1074
1167
|
if (updated) {
|
|
1075
1168
|
logger.info(`Peer name for ${msg.sessionId}: ${msg.peerName ?? "-"}`);
|
|
@@ -1397,6 +1490,18 @@ var HubServer = class {
|
|
|
1397
1490
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
1398
1491
|
res.end(JSON.stringify(body));
|
|
1399
1492
|
}
|
|
1493
|
+
authorized(req, allowQueryToken) {
|
|
1494
|
+
return !this.token || isAuthorized(req, this.token, allowQueryToken);
|
|
1495
|
+
}
|
|
1496
|
+
rejectUpgrade(socket, status, text, pathname, req) {
|
|
1497
|
+
logger.warn(`Rejected ${pathname} upgrade (${status}) from ${req.socket.remoteAddress}`);
|
|
1498
|
+
socket.write(`HTTP/1.1 ${status} ${text}\r
|
|
1499
|
+
Connection: close\r
|
|
1500
|
+
Content-Length: 0\r
|
|
1501
|
+
\r
|
|
1502
|
+
`);
|
|
1503
|
+
socket.destroy();
|
|
1504
|
+
}
|
|
1400
1505
|
isLocalRequest(req) {
|
|
1401
1506
|
const addr = req.socket.remoteAddress;
|
|
1402
1507
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|