CodeSampleX

示例

socket.io-client 4.8.3: Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing

已验证示例 — npm socket.io-client 4.8.3: Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing.…

sha256:a34e5524e2d70aa4a594e6e5c07de316ebbcb9ce2828e6283a63ad65c5913e95

本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。 提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。 MIT-0

执行证据

声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。

证据依据
签名契约通过
验证回执
3
构建过它的签名密钥
3
声明的环境 node linux x64 node node npm

验证运行环境

环境 契约 阶段 运行日期
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-15
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-07

案例

HOW
目标
Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing
包
符号
  • io
  • WebSocketServer
环境
node
创建时间
2026-08-16T06:30:54Z

契约

  1. assert default socket.io-client connection fails with xhr poll error 426 when pointed at a plain WebSocket server
  2. assert websocket-only socket.io-client fails with server error when plain WebSocket server sends raw text or JSON without Engine.IO packet 0 OPEN
  3. assert websocket-only socket.io-client times out when plain WebSocket server accepts connection but stays silent
  4. assert socket.io-client requires Engine.IO OPEN packet 0 and Socket.IO CONNECT ACK packet 40 to emit connect, and frames event messages as 42[eventName, payload]

文件

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs

下载源代码构件 (tar.gz)

源代码

NOTES.md
# NOTES

## `search_known_solution` Result
`search_known_solution` returned `MATCH: COMPATIBLE` (low confidence) pointing to generic `ws` echo and API distinction samples (`sha256:91b961d23ba84c3205b8885fc80b5203a7d47a923e7a8aab6729d513e2a66a95` and `sha256:1a1d36a350a9eb2dcd345ed3b5163cee5cc2ec75783de4b1c90f8d313b012ee7`), with NO sample addressing the protocol incompatibility between `socket.io-client` and plain WebSocket servers.

## What a naive model would write instead
A naive model assumes `socket.io-client` is a generic WebSocket client and connects directly via `io("ws://localhost:PORT")` or `io("http://localhost:PORT", { transports: ["websocket"] })`, expecting standard event listener callbacks (`socket.on("message", ...)`) to receive plain strings or JSON objects emitted by a plain WebSocket (`ws`) server.

## How the wrong version fails
It fails loudly at the protocol handshake layer: by default, HTTP polling fails with `xhr poll error` (HTTP 426 / 404); with forced WebSocket transport, the client aborts with `server error` on un-framed data or hangs until `timeout` because Engine.IO packet `0` (OPEN) and Socket.IO packet `40` (CONNECT ACK) are never received, preventing `socket.connected` from ever becoming `true` and buffering all emitted messages without transmitting them.
csx.json
{"case":{"caseId":"case:sha256:fac86b17c36bfe76ac35e52f0014551563313b4841d0aa7717153895b88747a4","constraints":{"runtime":"node"},"contract":["assert default socket.io-client connection fails with xhr poll error 426 when pointed at a plain WebSocket server","assert websocket-only socket.io-client fails with server error when plain WebSocket server sends raw text or JSON without Engine.IO packet 0 OPEN","assert websocket-only socket.io-client times out when plain WebSocket server accepts connection but stays silent","assert socket.io-client requires Engine.IO OPEN packet 0 and Socket.IO CONNECT ACK packet 40 to emit connect, and frames event messages as 42[eventName, payload]"],"goal":"Why socket.io-client cannot talk to a plain WebSocket server due to Engine.IO handshake and Socket.IO wire framing","kind":"HOW","packages":["pkg:npm/socket.io-client@4.8.3","pkg:npm/ws@8.21.3"],"schemaVersion":1,"symbols":["io","WebSocketServer"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/socket.io-client@4.8.3","pkg:npm/ws@8.21.3"],"schemaVersion":1,"symbols":["io","WebSocketServer"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "sample",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "sample",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "socket.io-client": "4.8.3",
        "ws": "8.21.3"
      }
    },
    "node_modules/@socket.io/component-emitter": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
      "license": "MIT"
    },
    "node_modules/debug": {
      "version": "4.4.3",
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
      "license": "MIT",
      "dependencies": {
        "ms": "^2.1.3"
      },
      "engines": {
        "node": ">=6.0"
      },
      "peerDependenciesMeta": {
        "supports-color": {
          "optional": true
        }
      }
    },
    "node_modules/engine.io-client": {
      "version": "6.6.6",
      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz",
      "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==",
      "license": "MIT",
      "dependencies": {
        "@socket.io/component-emitter": "~3.1.0",
        "debug": "~4.4.1",
        "engine.io-parser": "~5.2.1",
        "ws": "~8.21.0",
        "xmlhttprequest-ssl": "~2.1.1"
      }
    },
    "node_modules/engine.io-parser": {
      "version": "5.2.3",
      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      }
    },
    "node_modules/ms": {
      "version": "2.1.3",
      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
      "license": "MIT"
    },
    "node_modules/socket.io-client": {
      "version": "4.8.3",
      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
      "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
      "license": "MIT",
      "dependencies": {
        "@socket.io/component-emitter": "~3.1.0",
        "debug": "~4.4.1",
        "engine.io-client": "~6.6.1",
        "socket.io-parser": "~4.2.4"
      },
      "engines": {
        "node": ">=10.0.0"
      }
    },
    "node_modules/socket.io-parser": {
      "version": "4.2.7",
      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
      "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
      "license": "MIT",
      "dependencies": {
        "@socket.io/component-emitter": "~3.1.0",
        "debug": "~4.4.1"
      },
      "engines": {
        "node": ">=10.0.0"
      }
    },
    "node_modules/ws": {
      "version": "8.21.3",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
      "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    },
    "node_modules/xmlhttprequest-ssl": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
      "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
      "engines": {
        "node": ">=0.4.0"
      }
    }
  }
}
package.json
{
  "name": "sample",
  "version": "1.0.0",
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "socket.io-client": "4.8.3",
    "ws": "8.21.3"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import { WebSocketServer } from 'ws';
import { io } from 'socket.io-client';

async function main() {
  // --- Test 1: Default transport (HTTP polling first) against plain WebSocket server ---
  {
    const wss = new WebSocketServer({ port: 0 });
    await new Promise(r => wss.on('listening', r));
    const port = wss.address().port;

    const socket = io(`http://127.0.0.1:${port}`, {
      timeout: 1000,
      reconnection: false,
    });

    const err = await new Promise((resolve) => {
      socket.on('connect_error', resolve);
    });

    assert.equal(socket.connected, false, 'Socket.IO client must not connect to plain WS server');
    assert.equal(err.message, 'xhr poll error', 'Default transport fails at HTTP polling handshake');
    assert.equal(err.description, 426, 'Plain WS server returns HTTP 426 Upgrade Required for polling GET');

    socket.close();
    await new Promise(r => wss.close(r));
  }

  // --- Test 2: Forced WebSocket transport against plain WS server sending raw text/JSON ---
  {
    const wss = new WebSocketServer({ port: 0 });
    await new Promise(r => wss.on('listening', r));
    const port = wss.address().port;

    let serverReceivedMessages = [];
    wss.on('connection', (ws) => {
      // Plain WS server sends raw application JSON without Engine.IO packet header
      ws.send(JSON.stringify({ type: 'greeting', payload: 'hello' }));
      ws.on('message', (msg) => serverReceivedMessages.push(msg.toString()));
    });

    const socket = io(`ws://127.0.0.1:${port}`, {
      transports: ['websocket'],
      timeout: 1000,
      reconnection: false,
    });

    socket.emit('chat', 'this should never reach server');

    const err = await new Promise((resolve) => {
      socket.on('connect_error', resolve);
    });

    assert.equal(socket.connected, false, 'Socket must not be connected when raw data received');
    assert.equal(err.message, 'server error', 'Engine.IO parser fails on non-Engine.IO packet type prefix');
    assert.equal(serverReceivedMessages.length, 0, 'Socket.IO client buffers events and sends nothing before handshake');

    socket.close();
    await new Promise(r => wss.close(r));
  }

  // --- Test 3: Forced WebSocket transport against silent plain WS server ---
  {
    const wss = new WebSocketServer({ port: 0 });
    await new Promise(r => wss.on('listening', r));
    const port = wss.address().port;

    wss.on('connection', (_ws) => {
      // Server accepts WebSocket upgrade but never sends Engine.IO packet 0 OPEN
    });

    const socket = io(`ws://127.0.0.1:${port}`, {
      transports: ['websocket'],
      timeout: 300,
      reconnection: false,
    });

    const err = await new Promise((resolve) => {
      socket.on('connect_error', resolve);
    });

    assert.equal(socket.connected, false, 'Socket remains disconnected while waiting for handshake');
    assert.equal(err.message, 'timeout', 'Client times out waiting for Engine.IO OPEN packet 0');

    socket.close();
    await new Promise(r => wss.close(r));
  }

  // --- Test 4: Wire protocol verification (Engine.IO 4 + Socket.IO v4 wire framing) ---
  {
    const wss = new WebSocketServer({ port: 0 });
    await new Promise(r => wss.on('listening', r));
    const port = wss.address().port;

    const wirePacketsReceivedByServer = [];

    wss.on('connection', (ws) => {
      // Step 1: Server must send Engine.IO packet '0' (OPEN) with session metadata
      ws.send('0' + JSON.stringify({
        sid: 'sess-abc-123',
        upgrades: [],
        pingInterval: 25000,
        pingTimeout: 20000,
        maxPayload: 1000000,
      }));

      ws.on('message', (data) => {
        const raw = data.toString();
        wirePacketsReceivedByServer.push(raw);

        // Step 2: Client sends Socket.IO namespace CONNECT packet '40'
        if (raw === '40') {
          // Server acknowledges root namespace connection
          ws.send('40{"sid":"sock-xyz-789"}');
        } else if (raw.startsWith('42')) {
          // Step 3: Client sends event '42["custom_event",{"x":1}]'
          // Server sends back event '42["server_reply",{"status":"ok"}]'
          ws.send('42["server_reply",{"status":"ok"}]');
        }
      });
    });

    const socket = io(`ws://127.0.0.1:${port}`, {
      transports: ['websocket'],
      reconnection: false,
    });

    await new Promise((resolve, reject) => {
      socket.on('connect', resolve);
      socket.on('connect_error', reject);
    });

    assert.equal(socket.connected, true, 'Socket connects once Engine.IO 0 and Socket.IO 40 are satisfied');
    assert.equal(socket.id, 'sock-xyz-789', 'Socket ID matches Socket.IO namespace handshake sid');

    const serverReplyPromise = new Promise((resolve) => {
      socket.on('server_reply', resolve);
    });

    socket.emit('custom_event', { x: 1 });

    const reply = await serverReplyPromise;
    assert.deepEqual(reply, { status: 'ok' }, 'Client parsed Socket.IO event payload successfully');

    // Verify wire framing sent over raw WebSocket
    assert.equal(wirePacketsReceivedByServer[0], '40', 'Client must send Engine.IO 4 (MESSAGE) + Socket.IO 0 (CONNECT)');
    assert.equal(wirePacketsReceivedByServer[1], '42["custom_event",{"x":1}]', 'Client must frame events as 42[eventName, data]');

    socket.close();
    await new Promise(r => wss.close(r));
  }
}

main().then(() => {
  process.exit(0);
}).catch((err) => {
  console.error('Contract assertion failed:', err);
  process.exit(1);
});

原始种子者

csx-seed