diff --git a/lua/amp/init.lua b/lua/amp/init.lua index 8d6eda4..e85a0d5 100644 --- a/lua/amp/init.lua +++ b/lua/amp/init.lua @@ -33,7 +33,8 @@ function M._on_client_connect() M.state.connected = true if not was_connected then - logger.info("init", "Connected to Amp") + -- Use print() directly to avoid [INFO] init: prefix from logger + print("● Connected to Amp CLI") end end diff --git a/lua/amp/server/client.lua b/lua/amp/server/client.lua index 7055727..87539f3 100644 --- a/lua/amp/server/client.lua +++ b/lua/amp/server/client.lua @@ -208,25 +208,36 @@ end ---@param code number|nil Close code (default: 1000) ---@param reason string|nil Close reason function M.close_client(client, code, reason) - if client.state == "closed" or client.state == "closing" then + -- Already closing/closed? Bail early. + if client.state == "closed" or client.tcp_handle:is_closing() then return end code = code or 1000 reason = reason or "" - if client.handshake_complete then + client.state = "closing" + + -- If handshake complete and code is allowed to be sent, send a Close frame + -- Per RFC 6455, code 1006 MUST NOT be sent in a Close frame + local can_send_close = client.handshake_complete and code ~= 1006 + + if can_send_close then local close_frame = frame.create_close_frame(code, reason) - client.tcp_handle:write(close_frame, function() + client.tcp_handle:write(close_frame, function(err) + -- Regardless of write result, ensure handle is closed exactly once + if not client.tcp_handle:is_closing() then + client.tcp_handle:close() + end client.state = "closed" - client.tcp_handle:close() end) else + -- For abnormal closure (1006) or no handshake, just drop TCP immediately + if not client.tcp_handle:is_closing() then + client.tcp_handle:close() + end client.state = "closed" - client.tcp_handle:close() end - - client.state = "closing" end ---Check if a client connection is alive diff --git a/lua/amp/server/tcp.lua b/lua/amp/server/tcp.lua index 3345ccd..15d33a5 100644 --- a/lua/amp/server/tcp.lua +++ b/lua/amp/server/tcp.lua @@ -162,7 +162,11 @@ function M._remove_client(server, client) server.clients[client.id] = nil if not client.tcp_handle:is_closing() then + client.state = "closing" client.tcp_handle:close() + -- Note: close() is async in libuv, but we mark as "closed" immediately + -- since the client is removed from the active list and won't be used again + client.state = "closed" end end end @@ -264,10 +268,10 @@ function M.start_ping_timer(server, interval) if client_manager.is_client_alive(client, interval * 2) then client_manager.send_ping(client, "ping") else - -- Client appears dead, close it - server.on_error("Client " .. client.id .. " appears dead, closing") - client_manager.close_client(client, 1006, "Connection timeout") + -- Client appears dead, drop the connection (no Close frame for 1006) + server.on_error(("Client %s appears dead, closing"):format(client.id)) M._remove_client(server, client) + server.on_disconnect(client, 1006, "Connection timeout") end end end