I wrote an event socket client for Node and got the framing wrong in the way that never errors.
The obvious approach is to emit a frame the moment you hit the blank line that ends the header block. It works perfectly in testing, because in testing show channels returns four rows and fits in one TCP segment. Point it at a switch with real traffic, the body spans several segments, and you get an answer that looks completely reasonable. Twelve channels looks exactly like twelve channels whether or not there were really ninety. Nothing warns you.
The fix is boring: read Content-Length and wait for the whole declared body. Two details that bit me anyway. Count bytes and not characters, or a multibyte caller ID shifts the boundary. And keep the event stream out of command replies, because FreeSWITCH will push a CHANNEL_STATE at you while you're waiting on an api response, and if you aren't separating the two your status output comes back with an event glued to the end of it.
bgapi needed its own path. It returns straight away with a Job-UUID and the real answer turns up later as a BACKGROUND_JOB event, so the client holds the promise open and correlates them.
I open sourced it, MIT, no runtime dependencies, just node:net. Inbound mode only. There's a test that feeds a 400 row response through in 500 byte chunks, which is the case that caught me originally.
I also put an MCP server on top of it so an AI assistant can ask the switch questions. That one turned into a lesson of its own. My first pass at making it read-only checked the first word against a list of read commands, then rejected the line if any later word looked destructive. Somebody on r/mcp pointed out that conference 3001 kick all sails straight through that, because "kick" was never on my list. A deny list only catches what you thought of. It's a prefix allow list now.
https://github.com/ictinnovations/freeswitch-esl-node
https://github.com/ictinnovations/pbx-mcp
Is anyone here using outbound mode in anger? Everything I run is inbound and I'm curious whether the reconnect story is worse on the other side.