elysia-nnn-socket-client
v1.0.0
Published
WebSocket client for elysia-nnn-socket 1.0 — room:join/leave with ack protocol, auto-prefixed room events, multi-arg payload packing, reconnect re-join
Downloads
141
Maintainers
Readme
elysia-nnn-socket-client
English | Tiếng Việt
Phiên bản: 1.0.0 · Dùng cùng
elysia-nnn-socket^1.0
Client WebSocket native cho protocol JSON của elysia-nnn-socket — API giống nnn-socket.io-client: room:join, event có prefix theo room, giao thức ack, tự join lại sau reconnect.
Client này triển khai wire format mà elysia-nnn-socket 1.0 dùng:
- Đi ra (client → server) —
{ type, ...positionalArgs, __ack? }(mỗi top-level key sautypelà một positional argument mà handler server nhận). - Broadcast đến (server → client) —
{ type, ...payload }cho 1 object,{ type, data }cho 1 primitive,{ type, args }cho nhiều arg,{ type }cho không có arg. - Ack đến —
{ __ack, ok: true, response }vớiresponsemangRoomAck({ ok, room, params }hoặc{ ok: false, error }).
Cài đặt
bun add elysia-nnn-socket-clientKhông phụ thuộc Socket.IO — chỉ dùng WebSocket của trình duyệt / Bun. Chạy được trên browser, Bun, Deno, Node ≥ 18.
Ví dụ nhanh
import { createElysiaNnnClient } from "elysia-nnn-socket-client";
const client = createElysiaNnnClient({
url: "http://localhost:3000",
path: "/ws",
query: { user: "alice" }, // upgrade query (vd: auth middleware)
});
await client.connect();
const chat = client.room("chat");
chat.on("welcome", (data) => console.log("welcome", data));
await chat.join();
chat.emit("message", { text: "chào" });
await chat.leave();
client.disconnect();API chính
| API | Mô tả |
|-----|--------|
| createElysiaNnnClient(options) | Tạo client |
| client.connect() | Mở WebSocket, trả Promise<void> |
| client.disconnect() | Đóng và xóa danh sách room đã join |
| client.closeSocket() | Chỉ đóng socket (giữ room cho autoRejoin) |
| client.connected | true khi underlying WebSocket đang OPEN |
| client.joinedRooms | string[] các room đang join |
| client.room("chat") | Lấy / tạo RoomHandle |
| client.room("room/[id]", { id: "a" }) | Room động qua buildRoom |
| client.join("chat") / client.leave("chat") | Viết tắt của room().join() / .leave() |
| room.join() / room.leave() | Join/leave có ack, trả RoomJoinResult |
| room.emit("message", data) | Gửi <room>:message |
| room.emitJoined("message", data) | join() + emit() trong 1 lệnh |
| room.on("message", fn) | Nghe <room>:message |
| room.off("message", fn?) | Gỡ listener (hoặc tất cả) |
| room.once("message", fn) | Listener một lần |
| client.on("ping", fn) | Event namespace (không có prefix) |
| client.emit("ping", data) | Event namespace fire-and-forget |
| client.sendWire({ type, ... }) | Gửi raw wire |
| client.requestAck(event, payload) | Gửi + chờ ack, resolve response |
| buildRoom("room/[id]", params) | Resolve template [param] |
Tuỳ chọn
{
url: string; // base http(s) — tự đổi sang ws(s)
path?: "/ws"; // phải trùng server `endpoint`
query?: Record<string, string>;
joinEvent?: "room:join"; // phải trùng server `joinEvent`
leaveEvent?: "room:leave";// phải trùng server `leaveEvent`
autoRejoin?: true;
ackTimeout?: 10_000;
connectTimeout?: 10_000;
onRejoinError?: (room, err) => void;
logRejoinErrors?: false;
}Lỗi
NnnRoomError— server ack{ ok: false, error }NnnAckTimeoutError— hết thời gian chờ ackNnnSocketError— connect / not connected / thiếu param
Wire format — bảng tra cứu
emit(event, ...args) pack args theo cùng cách server unpack:
| Args | Wire shape | Server nhận |
|------|------------|-------------|
| không có | { type: "evt" } | handler() |
| 1 object | { type: "evt", ...payload } | handler(payload) |
| 1 primitive | { type: "evt", data: value } | handler(value) |
| nhiều | { type: "evt", args: [...] } | handler(...args) |
on(event, handler) decode broadcast đến theo type và dispatch với cùng quy tắc — server broadcast socket.emit("chat:message", { from, text }) sẽ tới handler của bạn dưới dạng (payload) => ... với payload = { from, text }.
Luồng ack
// Gửi đi
client.requestAck<{ ok: true; room: string }>(
"chat:save",
{ id: "doc-1", body: "..." },
);
// Wire: { type: "chat:save", id: "doc-1", body: "...", __ack: "<rand>" }
// Server handler (signature: `save(socket, ctx, id, body, ack)`)
on: {
save(ws, ctx, id, body, ack) {
ack({ ok: true, room: "chat" }); // → response của promise đang chờ
},
}
// Envelope đến (resolve của requestAck nhận `response`)
{ __ack: "<rand>", ok: true, response: { ok: true, room: "chat" } }Room động
import { buildRoom } from "elysia-nnn-socket-client";
buildRoom("room/[id]", { id: "alpha" }); // "room/alpha"
const room = client.room("room/[id]", { id: "alpha" });
await room.join();
room.emit("say", "hello"); // → server `say(socket, ctx, "hello")`
room.on("echo", (payload) => console.log(payload));Reconnect
const client = createElysiaNnnClient({ url, autoRejoin: true });
await client.connect();
await client.join("chat");
client.closeSocket(); // đóng connection, giữ joinedRooms
await client.connect(); // tự join lại "chat" khi mở lạionRejoinError(room, error) được gọi khi ack re-join về { ok: false, error }. Error là NnnRoomError. Room bị xoá khỏi joinedRooms trong mọi trường hợp.
Test
bun test tests --concurrency 1E2E test chạy với package elysia-nnn-socket ngay trong monorepo — tạo fixture room file, khởi động Bun server thật, exercise đầy đủ flow join → emit → ack.
License
MIT
