mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-12 09:24:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cd96ab8fb |
No files matched your search
@@ -37,7 +37,12 @@ function startBridge({ token, authDelayMs = 0, onGetContext = null }) {
|
||||
sockets.add(socket);
|
||||
socket.on("close", () => sockets.delete(socket));
|
||||
let buffer = "";
|
||||
let authenticated = false;
|
||||
socket.setEncoding("utf8");
|
||||
const writeLine = (payload) => {
|
||||
if (socket.destroyed) return;
|
||||
socket.write(`${JSON.stringify(payload)}\n`);
|
||||
};
|
||||
socket.on("data", (chunk) => {
|
||||
buffer += chunk;
|
||||
let newlineIdx;
|
||||
@@ -52,17 +57,29 @@ function startBridge({ token, authDelayMs = 0, onGetContext = null }) {
|
||||
token: msg.params?.token,
|
||||
});
|
||||
const respond = () => {
|
||||
if (socket.destroyed) return;
|
||||
if (msg.method === "auth/verify") {
|
||||
if (msg.params?.token !== token) {
|
||||
socket.write(`${JSON.stringify({
|
||||
writeLine({
|
||||
jsonrpc: "2.0",
|
||||
id: msg.id,
|
||||
error: { code: -32001, message: "Authentication required. Send auth/verify with valid token first." },
|
||||
})}\n`);
|
||||
});
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
socket.write(`${JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: { ok: true } })}\n`);
|
||||
authenticated = true;
|
||||
writeLine({ jsonrpc: "2.0", id: msg.id, result: { ok: true } });
|
||||
return;
|
||||
}
|
||||
// Match the app bridge: a call before auth/verify closes the socket.
|
||||
if (!authenticated) {
|
||||
writeLine({
|
||||
jsonrpc: "2.0",
|
||||
id: msg.id,
|
||||
error: { code: -32001, message: "Authentication required. Send auth/verify with valid token first." },
|
||||
});
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
if (msg.method === "netcatty/getContext" && onGetContext) {
|
||||
@@ -72,11 +89,11 @@ function startBridge({ token, authDelayMs = 0, onGetContext = null }) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
socket.write(`${JSON.stringify({
|
||||
writeLine({
|
||||
jsonrpc: "2.0",
|
||||
id: msg.id,
|
||||
result: { ok: true, marker: token },
|
||||
})}\n`);
|
||||
});
|
||||
};
|
||||
if (msg.method === "auth/verify" && authDelayMs > 0) {
|
||||
setTimeout(respond, authDelayMs);
|
||||
|
||||
@@ -122,19 +122,33 @@ function guardWriteOperation(command, { skipBlocklist = false } = {}) {
|
||||
}
|
||||
|
||||
let tcpSocket = null;
|
||||
let pendingRequests = new Map(); // id -> { resolve, reject }
|
||||
let pendingRequests = new Map(); // id -> { resolve, reject, sock }
|
||||
let nextRpcId = 1;
|
||||
let tcpBuffer = "";
|
||||
|
||||
// `destroyed` stays false after the peer FIN has already moved readyState to
|
||||
// "closed". Writing in that window hits writeAfterFIN and rejects the call
|
||||
// with "TCP connection lost" instead of reconnecting.
|
||||
function isLiveSocket(sock) {
|
||||
return Boolean(
|
||||
sock
|
||||
&& !sock.destroyed
|
||||
&& sock.readyState === "open"
|
||||
&& sock.writable
|
||||
&& !sock.readableEnded
|
||||
&& !sock.writableEnded
|
||||
);
|
||||
}
|
||||
|
||||
function rejectPendingOnSocket(sock, externalMessage) {
|
||||
if (tcpSocket && tcpSocket !== sock) return;
|
||||
const managed = !externalDiscoveryPath;
|
||||
if (managed && tcpSocket === sock) tcpSocket = null;
|
||||
const message = managed ? "Not connected to Netcatty" : externalMessage;
|
||||
for (const { reject: rej } of pendingRequests.values()) {
|
||||
rej(new Error(message));
|
||||
for (const [id, pending] of pendingRequests) {
|
||||
if (pending.sock !== sock) continue;
|
||||
pendingRequests.delete(id);
|
||||
pending.reject(new Error(message));
|
||||
}
|
||||
pendingRequests.clear();
|
||||
}
|
||||
|
||||
function connectTcp() {
|
||||
@@ -198,15 +212,16 @@ function connectTcp() {
|
||||
|
||||
function sendRpc(method, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!tcpSocket || tcpSocket.destroyed) {
|
||||
if (!isLiveSocket(tcpSocket)) {
|
||||
reject(new Error("Not connected to Netcatty"));
|
||||
return;
|
||||
}
|
||||
const sock = tcpSocket;
|
||||
const id = nextRpcId++;
|
||||
pendingRequests.set(id, { resolve, reject });
|
||||
pendingRequests.set(id, { resolve, reject, sock });
|
||||
const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
||||
debugLog("rpcCall", { id, method });
|
||||
tcpSocket.write(msg);
|
||||
sock.write(msg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -246,7 +261,12 @@ function dropActiveSocket() {
|
||||
let connecting = null;
|
||||
|
||||
async function connectAndAuth() {
|
||||
if (tcpSocket?.destroyed) tcpSocket = null;
|
||||
// Drop the reference only. In-flight requests stay bound to the old socket
|
||||
// and are rejected when that socket closes; they are not replayed.
|
||||
if (!isLiveSocket(tcpSocket)) {
|
||||
tcpSocket = null;
|
||||
tcpBuffer = "";
|
||||
}
|
||||
refreshExternalEndpoint();
|
||||
tcpBuffer = "";
|
||||
try {
|
||||
@@ -263,8 +283,11 @@ async function connectAndAuth() {
|
||||
}
|
||||
|
||||
function ensureConnected() {
|
||||
if (tcpSocket && !tcpSocket.destroyed) return Promise.resolve();
|
||||
// Join an in-flight reconnect before trusting tcpSocket. connectTcp assigns
|
||||
// the socket before auth/verify returns; a second caller must wait for that
|
||||
// re-auth instead of sending on an unauthenticated socket.
|
||||
if (connecting) return connecting;
|
||||
if (isLiveSocket(tcpSocket)) return Promise.resolve();
|
||||
connecting = connectAndAuth().finally(() => {
|
||||
connecting = null;
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user