Pullboard
git 저장소에 존재하는 작업 보드로, 에이전트가 작업을 자동으로 할당·검토하고 별도 설명이 필요 없는 로컬 오픈소스 도구
Node용 ZERO-DEPENDENCY 웹소켓 서버
ZERO-DEPENDENCY WebSocket SERVER for Node — RFC 6455 framing over node:http's `upgrade` event, on node:http/net/crypto/events and nothing else. Node 22 ships a WebSocket CLIENT (the `WebSocket` global
개발 도구TypeScript · PythonGitHub @Particle-Academy온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A WebSocket server for Node with zero runtime dependencies. Node ships a WebSocket client and no server; this is the server half -- RFC 6455 framing over node:http's upgrade event, with authorisation before the 101.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A WebSocket server for Node, with zero runtime dependencies.
Node 22 ships a WebSocket client — the WebSocket global, from undici — and
no server. This is the other half: RFC 6455 framing over node:http's upgrade
event, built on node:http, node:net, node:crypto and node:events and
nothing else.
npm install @particle-academy/fancy-ws
import { createServer } from 'node:http';
import { attachWebSocketServer } from '@particle-academy/fancy-ws';
const http = createServer();
const ws = attachWebSocketServer(http, { path: '/remote' });
ws.on('connection', (socket, request, verified) => {
socket.on('message', (data, isBinary) => {
if (!isBinary) {
socket.send(JSON.stringify({ echo: JSON.parse(data as string) }));
}
});
socket.on('close', (code, reason) => console.log('gone', code, reason));
});
ws.heartbeat(30_000);
http.listen(7421);
It shares the server, so plain HTTP routes on the same port keep working.
verify runs during the handshake, so a refusal is a plain HTTP response
rather than an open-then-close.
const ws = attachWebSocketServer(http, {
path: '/pair',
verify: async (req) => {
const pin = new URL(req.url ?? '/', 'http://local').searchParams.get('pin');
if (!pin) return 401; // any 4xx/5xx status
const session = await pins.claim(pin);
if (!session) return 403;
return { session }; // handed to `connection`
},
});
ws.on('connection', (socket, _req, { session }) => { /* already authorised */ });
verify returns | result |
|---|---|
true | accept; verified is {} |
| an object | accept; the object becomes verified |
false | refuse with 403 |
| a number | refuse with that status |
| throws | 500, and the error is emitted on the server's error event |
Why before and not after. An accept-then-close hands an unauthenticated peer a live frame parser, however briefly, and forces the client to tell "authorised" from "refused" by interpreting a close code. A 401 is something every HTTP client already understands.
One measured caveat if the client is Node's own: on a refused handshake, Node
v22.23.2 fires error and no close, and leaves readyState at
CONNECTING forever. A consumer waiting on close to learn the handshake
failed waits indefinitely — listen for error. This is a deviation from the
WHATWG spec's "fail the WebSocket connection", so it may change; the suite
asserts the current behaviour so a fix surfaces as a test failure here rather
than as a surprise in a consumer.
socket.send('text'); // or a Uint8Array for binary
socket.send(bytes); // returns false if not OPEN -- never throws
socket.close(1000, 'done');
socket.terminate(); // no handshake; reports 1006
socket.ping();
socket.readyState; // 0/1/2/3, as in the browser
socket.remoteAddress;
socket.protocol; // negotiated subprotocol, or null
socket.request; // the upgrade IncomingMessage
| event | arguments |
|---|---|
message | (data: string | Buffer, isBinary: boolean) |
close | (code: number, reason: string) — fires exactly once |
error | (error: Error) |
ping / pong | (data: Buffer) |
data is a string for text and a Buffer for binary, so JSON.parse(data)
needs no decode step. Messages are reassembled across fragments before delivery.
| option | default | |
|---|---|---|
path | (all) | handle only this exact pathname |
onPathMismatch | 'ignore' | 'ignore' leaves the upgrade for another listener; 'reject' answers 404 |
verify | — | authorise before the 101 (above) |
protocols | — | subprotocols supported, most preferred first |
maxPayload | 1 MiB | largest message, per frame and per reassembled message |
closeTimeoutMs | 10 000 | how long to await the peer's close frame |
onPathMismatch: 'ignore' is the default so this can share a port with
other protocols — Genie's HTTP front door passes upgrade through for its own
surfaces. The cost is worth knowing: with no other upgrade listener, an
unmatched upgrade sits open until the client times out, because Node does not
answer an upgrade once any listener exists. Use 'reject' when this is the only
WebSocket endpoint on the server.
const stop = ws.heartbeat(30_000); // pings; drops after 2 missed pongs
A TCP connection to a machine that slept, lost its network, or lost power stays
OPEN indefinitely: nothing arrives and nothing errors, and readyState is a
statement about this end only. The pong is the only evidence the peer is still
there. The interval timer is unref'd, so it never keeps a process alive.
Does: text, binary, fragmentation, masking enforcement, ping/pong, the close
handshake with code and reason, strict UTF-8 validation (1007), payload limits
per frame and per message (1009), minimal-length-encoding enforcement, RSV and
reserved-opcode refusal (1002), subprotocol negotiation, https.Server as well
as http.Server.
Does not: permessage-deflate (RSV1 is refused rather than ignored, so a
client offering it gets a clean failure instead of a corrupted message); a
client (Node has one built in); clustering or a pub/sub layer.
EventEmitter throws when 'error' is emitted with no listener, so a naive
implementation lets any peer take the process down with one malformed frame.
Every error path here checks for a listener first; the close frame still carries
the protocol verdict to the peer. There is a test that drives a violation
through a connection with no error listener attached and then asserts the
server is still serving.
0.1.x. Pre-1.0, so breaking changes land in minor releases — see
CHANGELOG.md. Requires Node >= 22.
MIT © Particle Academy