1"use strict";(self.webpackChunkfiber_docs=self.webpackChunkfiber_docs||[]).push([["1081"],{698439(e,t,n){n.r(t),n.d(t,{metadata:()=>s,default:()=>h,frontMatter:()=>r,contentTitle:()=>d,toc:()=>o,assets:()=>c});var s=JSON.parse('{"id":"socketio/socketio","title":"Socket.io","description":"Release","source":"@site/contrib_versioned_docs/version-v3_newrelic_v1.x.x/socketio/README.md","sourceDirName":"socketio","slug":"/socketio/","permalink":"/contrib/v3_newrelic_v1.x.x/socketio/","draft":false,"unlisted":false,"editUrl":"https://github.com/gofiber/contrib/edit/main/v3/socketio/README.md","tags":[],"version":"v3_newrelic_v1.x.x","lastUpdatedAt":1791052031000,"frontMatter":{"id":"socketio"},"sidebar":"left_sidebar","previous":{"title":"Sentry","permalink":"/contrib/v3_newrelic_v1.x.x/sentry/"},"next":{"title":"SocketIO Legacy Event Shim","permalink":"/contrib/v3_newrelic_v1.x.x/socketio/legacy/"}}'),l=n(474848),i=n(28453);let r={id:"socketio"},d="Socket.io",c={},o=[{value:"Features",id:"features",level:2},{value:"Known limitations",id:"known-limitations",level:2},{value:"Production hardening notes",id:"production-hardening-notes",level:4},{value:"Performance",id:"performance",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Go version support",id:"go-version-support",level:2},{value:"Install",id:"install",level:2},{value:"Protocol compatibility",id:"protocol-compatibility",level:2},{value:"Required client configuration",id:"required-client-configuration",level:3},{value:"Polling pitfalls",id:"polling-pitfalls",level:4},{value:"Tunable globals",id:"tunable-globals",level:3},{value:"Message format",id:"message-format",level:3},{value:"Acks, namespaces, handshake auth",id:"acks-namespaces-handshake-auth",level:3},{value:"Multi-argument emits",id:"multi-argument-emits",level:4},{value:"Server-initiated acks",id:"server-initiated-acks",level:4},{value:"Client-initiated acks",id:"client-initiated-acks",level:4},{value:"Namespaces",id:"namespaces",level:4},{value:"Handshake auth",id:"handshake-auth",level:4},{value:"Signatures",id:"signatures",level:2},{value:"Example",id:"example",level:2},{value:"Go server",id:"go-server",level:3},{value:"TypeScript / JavaScript client",id:"typescript--javascript-client",level:3},{value:"Supported events",id:"supported-events",level:2},{value:"Event Payload object",id:"event-payload-object",level:2},{value:"Socket instance functions",id:"socket-instance-functions",level:2}];function a(e){let t={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(t.header,{children:(0,l.jsx)(t.h1,{id:"socketio",children:"Socket.io"})}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.img,{src:"https://img.shields.io/github/v/tag/gofiber/contrib?filter=*socketio*",alt:"Release"}),"\n",(0,l.jsx)(t.a,{href:"https://gofiber.io/discord",children:(0,l.jsx)(t.img,{src:"https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7",alt:"Discord"})}),"\n",(0,l.jsx)(t.img,{src:"https://github.com/gofiber/contrib/workflows/Test%20Socket.io/badge.svg",alt:"Test"})]}),"\n",(0,l.jsxs)(t.p,{children:["WebSocket wrapper for ",(0,l.jsx)(t.a,{href:"https://github.com/gofiber/fiber",children:"Fiber"})," that implements the ",(0,l.jsx)(t.a,{href:"https://github.com/socketio/engine.io-protocol",children:"Engine.IO v4"})," / ",(0,l.jsx)(t.a,{href:"https://github.com/socketio/socket.io-protocol",children:"Socket.IO v5"})," wire protocol, making it fully compatible with the official ",(0,l.jsx)(t.a,{href:"https://socket.io/docs/v4/client-api/",children:(0,l.jsx)(t.code,{children:"socket.io-client"})})," library."]}),"\n",(0,l.jsxs)(t.p,{children:["For applications that used older ",(0,l.jsx)(t.code,{children:"socketio"})," releases as a plain WebSocket event bus, migrate to ",(0,l.jsx)(t.code,{children:"github.com/gofiber/contrib/v3/websocket/event"}),". A deprecated compatibility shim is available at ",(0,l.jsx)(t.code,{children:"github.com/gofiber/contrib/v3/socketio/legacy"}),"."]}),"\n",(0,l.jsx)(t.p,{children:(0,l.jsx)(t.strong,{children:"Compatible with Fiber v3."})}),"\n",(0,l.jsx)(t.h2,{id:"features",children:"Features"}),"\n",(0,l.jsx)(t.p,{children:"This middleware implements the full Engine.IO v4 / Socket.IO v5 wire protocol. Highlights:"}),"\n",(0,l.jsxs)(t.ul,{children:["\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Synchronous handshake."})," The Engine.IO OPEN / Socket.IO CONNECT exchange completes before the user ",(0,l.jsx)(t.code,{children:"New()"})," callback returns, so emits issued inside the callback are ordered after the handshake reply."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"HTTP long-polling fallback (opt-in)."})," Set ",(0,l.jsx)(t.code,{children:"socketio.EnablePolling = true"})," and mount the same handler for ",(0,l.jsx)(t.code,{children:"GET"})," and ",(0,l.jsx)(t.code,{children:"POST"})," to accept ",(0,l.jsx)(t.code,{children:"transport=polling"})," clients. Polling sessions speak the same Engine.IO v4 / Socket.IO v5 wire protocol over HTTP and route through the same listener API (Emit, Ack, Close, Broadcast). Polling-to-WebSocket transport upgrade is not yet implemented; sessions that connect via polling stay on polling."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Namespaces and handshake auth."})," The negotiated namespace is honoured for inbound and outbound packets; the client's connect-time ",(0,l.jsx)(t.code,{children:"auth"})," payload is exposed via ",(0,l.jsx)(t.code,{children:"Websocket.HandshakeAuth()"})," and ",(0,l.jsx)(t.code,{children:"EventPayload.HandshakeAuth"}),"."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Inbound acks."})," Client-initiated callbacks surface as ",(0,l.jsx)(t.code,{children:"EventPayload.HasAck"})," / ",(0,l.jsx)(t.code,{children:"AckID"}),"; reply once with ",(0,l.jsx)(t.code,{children:"payload.Ack(args...)"}),"."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Outbound acks."})," Server-initiated ",(0,l.jsx)(t.code,{children:"EmitWithAck"}),", ",(0,l.jsx)(t.code,{children:"EmitWithAckTimeout"}),", and ",(0,l.jsx)(t.code,{children:"EmitWithAckArgs"})," round-trip a callback id and invoke the supplied callback when the client acks (or on timeout/disconnect)."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Multi-arg events."})," Inbound events expose every argument tuple as ",(0,l.jsx)(t.code,{children:"EventPayload.Args [][]byte"}),"; outbound ",(0,l.jsx)(t.code,{children:"EmitArgs"})," / ",(0,l.jsx)(t.code,{children:"EmitWithAckArgs"})," send pre-encoded JSON tuples."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Deterministic heartbeat."})," Server PINGs every ",(0,l.jsx)(t.code,{children:"PingInterval"}),"; the connection is torn down if no PONG arrives within ",(0,l.jsx)(t.code,{children:"PingTimeout"}),". The heartbeat is a runtime timer, not a goroutine, and an idle read deadline of ",(0,l.jsx)(t.code,{children:"PingInterval + PingTimeout"})," backs it up."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Closing handshake."})," ",(0,l.jsx)(t.code,{children:"Close"})," queues the SIO DISCONNECT packet and a Close frame behind the emits already pending, keeps reading until the peer's Close frame arrives (bounded by ",(0,l.jsx)(t.code,{children:"CloseTimeout"}),"), then closes the socket, so ",(0,l.jsx)(t.code,{children:"socket.io-client"})," reports ",(0,l.jsx)(t.code,{children:"io server disconnect"})," and does not reconnect. A client SIO DISCONNECT is answered the same way. Every tear-down releases the socket, including when a write is stalled on a peer that stopped reading."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Two goroutines per connection."})," The upgrade handler's goroutine runs the read loop and one goroutine serialises writes; polling sessions own no goroutine at all."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Zero-copy inbound, SWAR outbound."})," Inbound events are validated once and split into sub-slices of the frame they arrived in; outbound frames are built in a single pre-sized allocation with the SWAR JSON string encoder from ",(0,l.jsx)(t.code,{children:"gofiber/utils"}),", and a broadcast builds each namespace's frame once for all its recipients."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Coalesced writes."})," On top of the websocket middleware's write coalescing, replies to a burst of pipelined client frames leave in one write."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"EIO 0x1E batched frames."})," Multi-packet WebSocket frames separated by ASCII RS (",(0,l.jsx)(t.code,{children:"0x1E"}),") are parsed correctly, with a hard cap (",(0,l.jsx)(t.code,{children:"MaxBatchPackets"}),") to prevent slice-header amplification."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Reserved-event-name guard."}
1)," User code cannot register or emit names reserved by the protocol (e.g. ",(0,l.jsx)(t.code,{children:"connect"}),", ",(0,l.jsx)(t.code,{children:"disconnect"}),")."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"EIO version validation."})," Handshakes that advertise an unsupported ",(0,l.jsx)(t.code,{children:"EIO"})," version are rejected."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Auth payload validation."})," The auth blob must be a JSON object and is bounded by ",(0,l.jsx)(t.code,{children:"MaxAuthPayload"}),"; oversize or malformed payloads are answered with CONNECT_ERROR."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"DoS hardening."})," ",(0,l.jsx)(t.code,{children:"MaxPayload"}),", ",(0,l.jsx)(t.code,{children:"MaxBatchPackets"}),", ",(0,l.jsx)(t.code,{children:"MaxEventNameLength"}),", ",(0,l.jsx)(t.code,{children:"MaxEventArgs"}),", and ",(0,l.jsx)(t.code,{children:"MaxAuthPayload"})," bound every attacker-controlled length and count."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Lock-free listener registry"})," plus ",(0,l.jsx)(t.code,{children:"atomic.Bool isAlive"}),", removing the per-event mutex from the hot path."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Optional drop-frames-on-overflow."})," When ",(0,l.jsx)(t.code,{children:"DropFramesOnOverflow"})," is true, a saturated send queue drops the offending frame and fires ",(0,l.jsx)(t.code,{children:"EventError"})," instead of tearing down the connection."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Graceful drain."})," The package-level ",(0,l.jsx)(t.code,{children:"Shutdown(ctx)"})," closes every active socket and waits for each worker to exit (or until ",(0,l.jsx)(t.code,{children:"ctx"})," is cancelled)."]}),"\n"]}),"\n",(0,l.jsx)(t.h2,{id:"known-limitations",children:"Known limitations"}),"\n",(0,l.jsxs)(t.ul,{children:["\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"One namespace per Engine.IO connection."})," Each WebSocket binds the namespace negotiated during the SIO CONNECT packet; multiplexing several namespaces over one EIO connection is not supported. A CONNECT for another namespace on an established connection is answered with CONNECT_ERROR (",(0,l.jsx)(t.code,{children:"Invalid namespace"}),") instead of an acknowledgement whose events would never be delivered."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"No BINARY_EVENT (5) / BINARY_ACK (6)."})," Binary Socket.IO frames are passed through as raw ",(0,l.jsx)(t.code,{children:"EventMessage"})," data; attachment reassembly is not implemented."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"No connection-state recovery."})," Resume-on-reconnect (Socket.IO's ",(0,l.jsx)(t.code,{children:"connectionStateRecovery"})," feature) is not implemented; reconnects always start a fresh session."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"No polling-to-WebSocket transport upgrade."})," When polling is enabled, sessions that open with ",(0,l.jsx)(t.code,{children:"transport=polling"})," advertise an empty ",(0,l.jsx)(t.code,{children:"upgrades"})," array and stay on polling for the session lifetime. Clients that need WebSocket from the start should configure ",(0,l.jsx)(t.code,{children:"transports: ['websocket']"}),"."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"No JSONP polling fallback."})," JSONP requests (",(0,l.jsx)(t.code,{children:"?j=N"}),") are rejected with engine.io error code 3. Modern browsers use XHR2/fetch; JSONP support is not planned."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"CORS is not handled by the middleware."})," Mount ",(0,l.jsx)(t.code,{children:"github.com/gofiber/fiber/v3/middleware/cors"})," (or your preferred CORS middleware) upstream of the polling route to control the policy. Long-poll holds connections open for up to ~25s by default, so reverse-proxy timeouts must accommodate (e.g. nginx ",(0,l.jsx)(t.code,{children:"proxy_read_timeout >= 60s"})," and ",(0,l.jsx)(t.code,{children:"proxy_buffering off"}),")."]}),"\n"]}),"\n",(0,l.jsx)(t.h4,{id:"production-hardening-notes",children:"Production hardening notes"}),"\n",(0,l.jsxs)(t.ul,{children:["\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Rate limiting"}),". Each polling open allocates a ",(0,l.jsx)(t.code,{children:"*Websocket"})," and arms a heartbeat timer (no goroutine). With ",(0,l.jsx)(t.code,{children:"EnablePolling = true"})," an unauthenticated client can create sessions until ",(0,l.jsx)(t.code,{children:"HandshakeTimeout"})," reaps idle ones (10s default). Mount ",(0,l.jsx)(t.code,{children:"github.com/gofiber/fiber/v3/middleware/limiter"}
1)," upstream of the route to bound concurrent session creation."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Write timeout"}),". A long-poll GET response that the client never reads pins a fasthttp worker on TCP backpressure. Configure ",(0,l.jsx)(t.code,{children:"fiber.Config{WriteTimeout: ...}"})," (a few seconds is typically appropriate) so abandoned reads do not strand workers."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Burst sizing"}),". ",(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"})," (default ",(0,l.jsx)(t.code,{children:"1024"}),") bounds the per-session outbound buffer. With the default ",(0,l.jsx)(t.code,{children:"DropFramesOnOverflow = false"})," a synchronous burst of more than 1024 emits inside a single listener call disconnects the session with ",(0,l.jsx)(t.code,{children:"ErrSendQueueClosed"}),". Either pace large bursts across drains, raise ",(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"}),", or set ",(0,l.jsx)(t.code,{children:"DropFramesOnOverflow = true"})," to tolerate overflow at the cost of dropped frames + ",(0,l.jsx)(t.code,{children:"EventError"}),"."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Listener panics"}),". Both transports recover panics inside the ",(0,l.jsx)(t.code,{children:"New()"})," callback and inside event listeners; the panic value is logged via the package ",(0,l.jsx)(t.code,{children:"Logger"})," hook. Avoid ",(0,l.jsx)(t.code,{children:"panic(string(attackerControlledBytes))"})," to prevent log injection in downstream consumers."]}),"\n"]}),"\n",(0,l.jsx)(t.h2,{id:"performance",children:"Performance"}),"\n",(0,l.jsxs)(t.p,{children:["The hot paths avoid ",(0,l.jsx)(t.code,{children:"encoding/json"}),"'s reflection and copy nothing they do not have to. Inbound frames are handed over by the websocket middleware's pooled ",(0,l.jsx)(t.code,{children:"ReadMessage"}),", validated once, and split into sub-slices; outbound frames are assembled in one pre-sized buffer with ",(0,l.jsx)(t.code,{children:"gofiber/utils"}),"' SWAR JSON string encoder; a broadcast builds each namespace's frame once and shares it between recipients. Each WebSocket connection costs two goroutines (the upgrade handler running the read loop, plus the writer) and one runtime timer instead of four goroutines; a polling session costs none."]}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.code,{children:"benchstat"})," over ",(0,l.jsx)(t.code,{children:"go test -run '^$' -bench . -benchmem -count=6"})," on a 4 vCPU runner, before and after (in-memory listener, root namespace):"]}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Benchmark"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Before"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"After"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Change"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Parse inbound event (name + 3 args)"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"2.71 \xb5s, 16 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"0.75 \xb5s, 2 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"-72% time"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Build outbound event, JSON argument"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"716 ns, 5 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"421 ns, 1 alloc"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"-41% time"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Build outbound event, raw-text argument"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"1.13 \xb5s, 12 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"198 ns, 1 alloc"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"-82% time"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Round trip: emit, listener, reply (1 connection)"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"15.9 \xb5s, 24 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"8.5 \xb5s, 10 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"-47% time"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Broadcast to 1024 subscribers"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"1.58 ms, 7192 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"0.54 ms, 2009 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"-66% time"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Connect: handshake and close"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"119 \xb5s, 143 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"140 \xb5s, 155 allocs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"+17% time"})]})]})]}),"\n",(0,l.jsxs)(t.p,{children:["The connect path is the one that got slower; almost all of it is the websocket middleware's upgrade now running through ",(0,l.jsx)(t.code,{children:"net/http"}),"'s upgrader on a fasthttp hijack, which is what makes write coalescing possible, and the same shift appears when the previous socketio code is built against it."]}),"\n",(0,l.jsx)(t.h2,{id:"configuration",children:"Configuration"}),"\n",(0,l.jsx)(t.p,{children:"All tunables are package-level variables; override before the first connection is accepted."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Variable"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Default"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Meaning"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PingInterval"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"25s"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"How often the server emits Engine.IO PING."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PingTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"20s"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Grace window for the client PONG before the connection is killed."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"HandshakeTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"10s"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Hard deadline for completing EIO OPEN + SIO CONNECT."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxPayload"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.code,{children:"1_000_000"})," (1 MB)"]}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Max bytes per inbound WebSocket frame; advertised to the client."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxAuthPayload"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"8 KiB"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"}
1,children:"Max bytes for the SIO CONNECT auth JSON."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxBatchPackets"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"256"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Max EIO packets in a single ",(0,l.jsx)(t.code,{children:"0x1E"}),"-batched frame."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxEventNameLength"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"256"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Max length of an inbound SIO event name."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxEventArgs"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"256"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Max elements (event name included) in an inbound SIO EVENT or ACK array."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"CloseTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"5s"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["One budget for the whole tear-down after ",(0,l.jsx)(t.code,{children:"Close"}),": waiting for a slot in a saturated send queue, reading for the peer's Close frame and letting a stalled write finish all share it. Zero closes the socket as soon as the frames are queued."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"WriteTimeout"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.code,{children:"0"})," (disabled)"]}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Deadline for a single WebSocket frame write by the send goroutine; a peer that stops reading is otherwise only detected by the heartbeat."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"OutboundAckTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"30s"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Default ack deadline for ",(0,l.jsx)(t.code,{children:"EmitWithAck"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"SendQueueSize"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"100"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Capacity of the per-connection outbound queue."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"DropFramesOnOverflow"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"false"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["If true, drop the offending frame on overflow (fires ",(0,l.jsx)(t.code,{children:"EventError"}),")."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"RetrySendTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"20ms"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Deprecated: no effect. The websocket middleware allocates a connection per upgrade, so there is no released connection to retry against."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxSendRetry"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"5"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Deprecated: no effect; see ",(0,l.jsx)(t.code,{children:"RetrySendTimeout"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"ReadTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"10ms"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Deprecated: no longer consulted by the read loop; kept for backward compatibility."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"EnablePolling"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"false"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["If true, the handler returned from ",(0,l.jsx)(t.code,{children:"New"})," also serves Engine.IO HTTP long-polling on ",(0,l.jsx)(t.code,{children:"GET"}),"/",(0,l.jsx)(t.code,{children:"POST"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PollingMaxBufferSize"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"1_000_000"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Cap on a single polling HTTP body (request POST or response GET drain). The drain that ends a session always carries the SIO DISCONNECT and EIO CLOSE packets; farewell frames that do not fit beside them are dropped."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxPollWait"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"30s"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Maximum time a long-poll GET blocks waiting for outbound frames."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"1024"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Cap on buffered outbound frames per polling session; overflow honors ",(0,l.jsx)(t.code,{children:"DropFramesOnOverflow"}),". The SIO DISCONNECT and EIO CLOSE packets queued by ",(0,l.jsx)(t.code,{children:"Close"})," are exempt, so a queue an ",(0,l.jsx)(t.code,{children:"EventClose"})," listener filled still ends the session cleanly."]})]})]})]}),"\n",(0,l.jsxs)(t.p,{children:["Use ",(0,l.jsx)(t.code,{children:"socketio.Shutdown(ctx)"})," from ",(0,l.jsx)(t.code,{children:"fiber.App.ShutdownWithContext"})," for a deterministic drain."]}),"\n",(0,l.jsx)(t.h2,{id:"go-version-support",children:"Go version support"}),"\n",(0,l.jsxs)(t.p,{children:["We only support the latest two versions of Go. Visit ",(0,l.jsx)(t.a,{href:"https://go.dev/doc/devel/release",children:"https://go.dev/doc/devel/release"})," for more information."]}),"\n",(0,l.jsx)(t.h2,{id:"install",children:"Install"}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-sh",children:"go get -u github.com/gofiber/fiber/v3\ngo get -u github.com/gofiber/contrib/v3/socketio\n"})}),"\n",(0,l.jsx)(t.h2,{id:"protocol-compatibility",children:"Protocol compatibility"}),"\n",(0,l.jsxs)(t.p,{children:["The middleware automatically handles the Engine.IO / Socket.IO handshake so you do ",(0,l.jsx)(t.strong,{children:"not"})," need any special server-side code; just point your ",(0,l.jsx)(t.code,{children:"socket.io-client"})," at the WebSocket endpoint."]}
1),"\n",(0,l.jsx)(t.h3,{id:"required-client-configuration",children:"Required client configuration"}),"\n",(0,l.jsxs)(t.p,{children:["The default ",(0,l.jsx)(t.code,{children:"socket.io-client"})," transport order is ",(0,l.jsx)(t.code,{children:"['polling', 'websocket']"}),". The middleware supports both, but polling is ",(0,l.jsx)(t.strong,{children:"opt-in"}),":"]}),"\n",(0,l.jsx)(t.p,{children:(0,l.jsx)(t.strong,{children:"WebSocket only (default):"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-js",children:'import { io } from "socket.io-client";\n\nconst socket = io("http://localhost:3000", {\n path: "/ws", // match the Fiber route\n transports: ["websocket"], // skip polling\n});\n'})}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.strong,{children:"Polling (or polling + websocket fallback):"})," enable ",(0,l.jsx)(t.code,{children:"EnablePolling"})," server-side and mount the handler for both ",(0,l.jsx)(t.code,{children:"GET"})," and ",(0,l.jsx)(t.code,{children:"POST"}),":"]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'socketio.EnablePolling = true\nh := socketio.New(func(kws *socketio.Websocket) { /* ... */ })\napp.Get("/ws", h)\napp.Post("/ws", h)\n// Optionally allow CORS preflight:\n// app.Options("/ws", h)\n'})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-js",children:'import { io } from "socket.io-client";\n\n// Default transport order: polling first, then upgrade attempt. Since this\n// implementation does not yet upgrade polling sessions to WebSocket, the\n// session stays on polling. For a forced WebSocket connect use\n// transports: ["websocket"]; for polling-only use transports: ["polling"].\nconst socket = io("http://localhost:3000", {\n path: "/ws",\n});\n'})}),"\n",(0,l.jsxs)(t.blockquote,{children:["\n",(0,l.jsxs)(t.p,{children:["CORS is not handled by the middleware. If your client connects from a different origin, mount your preferred CORS middleware (e.g. ",(0,l.jsx)(t.code,{children:"github.com/gofiber/fiber/v3/middleware/cors"}),") upstream of the route. Long-polling holds a request open for up to ~25s by default, so reverse-proxy timeouts must accommodate (",(0,l.jsx)(t.code,{children:"proxy_read_timeout"})," >= 60s on nginx, ",(0,l.jsx)(t.code,{children:"proxy_buffering off"}),")."]}),"\n"]}),"\n",(0,l.jsx)(t.h4,{id:"polling-pitfalls",children:"Polling pitfalls"}),"\n",(0,l.jsxs)(t.ul,{children:["\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Forgot to mount POST."})," Polling clients send packets via POST; without ",(0,l.jsx)(t.code,{children:"app.Post(path, h)"})," (or ",(0,l.jsx)(t.code,{children:"app.All(...)"}),") the server returns 404 and the client loops with ",(0,l.jsx)(t.code,{children:"transport error"}),". Always mount both ",(0,l.jsx)(t.code,{children:"GET"})," and ",(0,l.jsx)(t.code,{children:"POST"})," for polling routes."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsxs)(t.strong,{children:[(0,l.jsx)(t.code,{children:"kws.Conn"})," is nil on polling."]})," Use ",(0,l.jsx)(t.code,{children:"kws.IsPolling()"})," to branch, or stick to the transport-agnostic ",(0,l.jsx)(t.code,{children:"Emit"}),", ",(0,l.jsx)(t.code,{children:"EmitEvent"}),", ",(0,l.jsx)(t.code,{children:"EmitArgs"}),", ",(0,l.jsx)(t.code,{children:"EmitWithAck*"}),", ",(0,l.jsx)(t.code,{children:"Broadcast"}),", ",(0,l.jsx)(t.code,{children:"Ack"}),", and ",(0,l.jsx)(t.code,{children:"Close"})," methods. They all work identically on both transports."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Snapshot vs live request state."})," ",(0,l.jsx)(t.code,{children:"kws.Locals"}),", ",(0,l.jsx)(t.code,{children:"kws.Params"}),", ",(0,l.jsx)(t.code,{children:"kws.Query"}),", ",(0,l.jsx)(t.code,{children:"kws.Cookies"})," are captured at session-open time on polling sessions (because fasthttp recycles the request context after the OPEN handler returns). Store mutable per-connection data via ",(0,l.jsx)(t.code,{children:"kws.SetAttribute"})," instead."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsxs)(t.strong,{children:["Burst bigger than ",(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"}),"."]})," With the default ",(0,l.jsx)(t.code,{children:"DropFramesOnOverflow = false"}),", emitting more than ",(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"})," (1024) frames before any GET drains them tears the session down with ",(0,l.jsx)(t.code,{children:"ErrSendQueueClosed"}),". Either pace bursts, raise ",(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"}),", or set ",(0,l.jsx)(t.code,{children:"DropFramesOnOverflow = true"})," to drop the offending frames + fire ",(0,l.jsx)(t.code,{children:"EventError(ErrSendQueueOverflow)"})," instead."]}),"\n",(0,l.jsxs)(t.li,{children:[(0,l.jsx)(t.strong,{children:"Body limit collision."})," If your Fiber app sets ",(0,l.jsx)(t.code,{children:"BodyLimit"})," lower than ",(0,l.jsx)(t.code,{children:"PollingMaxBufferSize"}),", fasthttp rejects the POST before our handler runs. Keep ",(0,l.jsx)(t.code,{children:"BodyLimit"})," >= ",(0,l.jsx)(t.code,{children:"PollingMaxBufferSize"}),"."]}),"\n"]}),"\n",(0,l.jsx)(t.h3,{id:"tunable-globals",children:"Tunable globals"}),"\n",(0,l.jsxs)(t.p,{children:["These package-level variables can be overridden before the first connection is accepted (typically in ",(0,l.jsx)(t.code,{children:"init()"})," or early in ",(0,l.jsx)(t.code,{children:"main"}),"). They control timing and limits for the Engine.IO / Socket.IO transport."]}
1),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Variable"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Default"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PingInterval"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"25 * time.Second"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Interval between Engine.IO PING frames sent by the server to keep the connection alive."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PingTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"20 * time.Second"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"How long the server waits for the client's PONG before considering the connection dead."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"HandshakeTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"10 * time.Second"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Maximum time allowed for the Engine.IO / Socket.IO handshake (including namespace CONNECT) to complete."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"CloseTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"5 * time.Second"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Budget for the whole tear-down after ",(0,l.jsx)(t.code,{children:"Close"})," or a client SIO DISCONNECT; the socket is closed once the peer's Close frame arrives or the budget is spent, whatever it was spent on."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"WriteTimeout"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.code,{children:"0"})," (disabled)"]}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Deadline for a single WebSocket frame write; zero relies on the heartbeat to detect a peer that stopped reading."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxPayload"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.code,{children:"1 << 20"})," (1 MiB)"]}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Maximum size in bytes for a single inbound WebSocket frame; oversize messages close the socket."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxAuthPayload"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:[(0,l.jsx)(t.code,{children:"8 << 10"})," (8 KiB)"]}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Maximum size in bytes for the Socket.IO CONNECT auth JSON."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxBatchPackets"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"256"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Maximum number of Engine.IO packets accepted in a single ",(0,l.jsx)(t.code,{children:"0x1E"}),"-batched frame."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxEventNameLength"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"256"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"}
1,children:"Maximum length of an inbound Socket.IO event name."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxEventArgs"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"256"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Maximum number of elements, event name included, in an inbound Socket.IO EVENT or ACK array."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"OutboundAckTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"30 * time.Second"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Default timeout used by ",(0,l.jsx)(t.code,{children:"EmitWithAck"})," when no per-call timeout is supplied."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"DropFramesOnOverflow"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"false"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["If true, saturated outbound queues drop the offending frame and fire ",(0,l.jsx)(t.code,{children:"EventError"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"RetrySendTimeout"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"20 * time.Millisecond"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Deprecated: no effect; the send goroutine writes each frame exactly once."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxSendRetry"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"5"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Deprecated: no effect; see ",(0,l.jsx)(t.code,{children:"RetrySendTimeout"}),"."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"EnablePolling"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"false"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["If true, the handler also accepts Engine.IO HTTP long-polling on ",(0,l.jsx)(t.code,{children:"GET"}),"/",(0,l.jsx)(t.code,{children:"POST"})," (opt-in fallback)."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PollingMaxBufferSize"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"1_000_000"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Cap on a single polling HTTP body (POST request body or GET drain response body), in bytes; the packets ",(0,l.jsx)(t.code,{children:"Close"})," queues always fit in the drain that ends the session."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"MaxPollWait"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"30 * time.Second"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Maximum time a long-poll GET blocks waiting for outbound frames before returning an empty 200."})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"PollQueueMaxFrames"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"1024"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Maximum buffered outbound frames per polling session before overflow handling applies; the packets ",(0,l.jsx)(t.code,{children:"Close"})," queues are exempt."]})]})]})]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"func init() {\n socketio.PingInterval = 15 * time.Second\n socketio.PingTimeout = 10 * time.Second\n socketio.MaxPayload = 4 << 20 // 4 MiB\n}\n"})}),"\n",(0,l.jsx)(t.h3,{id:"message-format",children:"Message format"}),"\n",(0,l.jsx)(t.p,{children:"All messages are exchanged as Socket.IO events."}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Side"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"API call"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Wire format"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Server \u2192 Client"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:'kws.Emit([]byte("hello"))'})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:'42["message","hello"]'})})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Server \u2192 Client"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:'kws.EmitEvent("greet", data)'})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:'42["greet",<data>]'})})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Client \u2192 Server"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:'socket.emit("message", obj)'})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["fires ",(0,l.jsx)(t.code,{children:"EventMessage"})," with ",(0,l.jsx)(t.code,{children:"obj"})]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Client \u2192 Server"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:'socket.emit("custom", obj)'})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["fires the ",(0,l.jsx)(t.code,{children:'"custom"'})," event"]})]})]})]}),"\n",(0,l.jsxs)(t.blockquote,{children:["\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.strong,{children:"Note:"})," ",(0,l.jsx)(t.code,{children:"Emit"}),", ",(0,l.jsx)(t.code,{children:"EmitEvent"}),", ",(0,l.jsx)(t.code,{children:"EmitArgs"}),", and ack-emitting variants pass valid JSON through unchanged. Raw text bytes are encoded as JSON strings for compatibility with older examples."]}),"\n"]}),"\n",(0,l.jsx)(t.h3,{id:"acks-namespaces-handshake-auth",children:"Acks, namespaces, handshake auth"}),"\n",(0,l.jsx)(t.p,{children:"The middleware implements the full Socket.IO v5 ack flow and forwards the client's connect-time auth payload to your handlers."}),"\n",(0,l.jsx)(t.h4,{id:"multi-argument-emits",children:"Multi-argument emits"}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.code,{children:"EmitArgs"})," and ",(0,l.jsx)(t.code,{children:"EmitWithAckArgs"})," accept a variadic list of values, so you can send richer event tuples without manually concatenating arrays. Valid JSON is passed through unchanged; raw text is encoded as a JSON string."]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'// 42["greet","hi",{"id":1}]\nkws.EmitArgs("greet", []byte(`"hi"`), []byte(`{"id":1}`))\n'})}),"\n",(0,l.jsx)(t.h4,{id:"server-initiated-acks",children:"Server-initiated acks"}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.code,{children:"EmitWithAck"})," (and ",(0,l.jsx)(t.code,{children:"EmitWithAckTimeout"}),") emit an event with an ack id and invoke the supplied callback once the client acks, or with an error when the timeout expires. ",(0,l.jsx)(t.code,{children:"EmitWithAck"})," uses ",(0,l.jsx)(t.code,{children:"OutboundAckTimeout"}),"; ",(0,l.jsx)(t.code,{children:"EmitWithAckTimeout"})," takes a per-call duration plus a structured ",(0,l.jsx)(t.code,{children:"AckCallback"})," that distinguishes timeout from disconnect."]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'kws.EmitWithAckTimeout("ping", []byte(`"hello"`), 3*time.Second, func(ack []byte, err error) {\n if err != nil {\n log.Printf("ack failed: %v", err)\n return\n }\n // ack is the raw JSON the client passed to its callback (single value\n // or a JSON-array literal for multi-arg acks).\n})\n'})}),"\n",(0,l.jsx)(t.h4,{id:"client-initiated-acks",children:"Client-initiated acks"}),"\n",(0,l.jsxs)(t.p,{children:["When the client emits with a callback, the inbound event payload carries an ack id. Use ",(0,l.jsx)(t.code,{children:"HasAck"})," and ",(0,l.jsx)(t.code,{children:"AckID"})," to detect it, then send a single ack reply via ",(0,l.jsx)(t.code,{children:"EventPayload.Ack"}),":"]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'socketio.On("greet", func(ep *socketio.EventPayload) {\n if ep.HasAck {\n // ep.Args holds the raw JSON arguments the client sent.\n _ = ep.Ack([]byte(`"ok"`))\n }\n})\n'})}),"\n",(0,l.jsx)(t.h4,{id:"namespaces",children:"Namespaces"}),"\n",(0,l.jsx)(t.p,{children:"The middleware honours the namespace negotiated during the Socket.IO CONNECT packet. Events emitted from the server are routed back on the same namespace the client joined; no extra configuration is required on the Go side."}),"\n",(0,l.jsx)(t.h4,{id:"handshake-auth",children:"Handshake auth"}),"\n",(0,l.jsxs)(t.p,{children:["The client's ",(0,l.jsx)(t.code,{children:"auth"})," payload must be a JSON object. It is parsed during the Socket.IO handshake and exposed to handlers as ",(0,l.jsx)(t.code,{children:"EventPayload.HandshakeAuth"})," (raw JSON bytes). It is most commonly inspected on ",(0,l.jsx)(t.code,{children:"EventConnect"}),":"]}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-js",children:'// client\nconst socket = io("http://localhost:3000", {\n path: "/ws",\n transports: ["websocket"],\n auth: { token: "secret" },\n});\n'})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'socketio.On(socketio.EventConnect, func(ep *socketio.EventPayload) {\n // ep.HandshakeAuth == []byte(`{"token":"secret"}`)\n var auth struct{ Token string `json:"token"` }\n _ = json.Unmarshal(ep.HandshakeAuth, &auth)\n})\n'})}),"\n",(0,l.jsx)(t.h2,{id:"signatures",children:"Signatures"}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Initialize new socketio in the callback this will\n// execute a callback that expects kws *Websocket Object\n// and optional config websocket.Config\nfunc New(callback func(kws *Websocket), config ...websocket.Config) func(fiber.Ctx) error\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Add listener callback for an event into the listeners list\nfunc On(event string, callback func(payload *EventPayload))\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Emit the message to a specific socket uuids list\n// Ignores all errors\nfunc EmitToList(uuids []string, message []byte, mType ...int)\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Emit to a specific socket connection\nfunc EmitTo(uuid string, message []byte, mType ...int) error\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Broadcast to all the active connections\nfunc Broadcast(message []byte, mType ...int)\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Fire custom event on all connections\nfunc Fire(event string, data []byte)\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'// Emit a named event with multiple arguments\n// (e.g. EmitArgs("greet", []byte(`"hi"`), []byte(`{"id":1}`)))\nfunc (kws *Websocket) EmitArgs(event string, args ...[]byte)\n'})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Emit a named event and invoke cb when the client acks (or on timeout /\n// disconnect). The default deadline is OutboundAckTimeout. The callback\n// receives the raw JSON ack value (or nil on timeout/disconnect).\nfunc (kws *Websocket) EmitWithAck(event string, data []byte, cb func(ack []byte))\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Like EmitWithAck but with a per-call timeout and a structured AckCallback\n// that distinguishes ErrAckTimeout from ErrAckDisconnected. Pass timeout = 0\n// to disable the timeout.\nfunc (kws *Websocket) EmitWithAckTimeout(event string, data []byte, timeout time.Duration, cb AckCallback)\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Multi-argument variant of EmitWithAck. The callback receives the slice of\n// raw ack arguments the client supplied (or an error on timeout /\n// disconnect). Uses OutboundAckTimeout.\nfunc (kws *Websocket) EmitWithAckArgs(event string, args [][]byte, cb func([][]byte, error))\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// HandshakeAuth returns the raw JSON auth payload sent by the client at\n/
1/ connect time (nil if the client did not provide one).\nfunc (kws *Websocket) HandshakeAuth() json.RawMessage\n"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"// Ack sends a Socket.IO ACK frame back to the client for the inbound event\n// represented by this payload. Idempotent: only the first invocation\n// produces a wire frame; later calls return ErrAckAlreadySent.\nfunc (ep *EventPayload) Ack(args ...[]byte) error\n"})}),"\n",(0,l.jsx)(t.h2,{id:"example",children:"Example"}),"\n",(0,l.jsx)(t.h3,{id:"go-server",children:"Go server"}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:'package main\n\nimport (\n "encoding/json"\n "fmt"\n "log"\n\n "github.com/gofiber/contrib/v3/socketio"\n "github.com/gofiber/contrib/v3/websocket"\n "github.com/gofiber/fiber/v3"\n)\n\n// MessageObject Basic chat message object\ntype MessageObject struct {\n Data string `json:"data"`\n From string `json:"from"`\n Event string `json:"event"`\n To string `json:"to"`\n}\n\nfunc main() {\n\n // The key for the map is message.to\n clients := make(map[string]string)\n\n // Start a new Fiber application\n app := fiber.New()\n\n // Setup the middleware to retrieve the data sent in first GET request\n app.Use(func(c fiber.Ctx) error {\n // IsWebSocketUpgrade returns true if the client\n // requested upgrade to the WebSocket protocol.\n if websocket.IsWebSocketUpgrade(c) {\n c.Locals("allowed", true)\n return c.Next()\n }\n return fiber.ErrUpgradeRequired\n })\n\n // Multiple event handling supported\n socketio.On(socketio.EventConnect, func(ep *socketio.EventPayload) {\n fmt.Printf("Connection event 1 - User: %s", ep.Kws.GetStringAttribute("user_id"))\n })\n\n // Custom event handling supported\n socketio.On("CUSTOM_EVENT", func(ep *socketio.EventPayload) {\n fmt.Printf("Custom event - User: %s", ep.Kws.GetStringAttribute("user_id"))\n // ---\x3e\n\n // DO YOUR BUSINESS HERE\n\n // ---\x3e\n })\n\n // On message event\n socketio.On(socketio.EventMessage, func(ep *socketio.EventPayload) {\n\n fmt.Printf("Message event - User: %s - Message: %s", ep.Kws.GetStringAttribute("user_id"), string(ep.Data))\n\n message := MessageObject{}\n\n // Unmarshal the json message\n // {\n // "from": "<user-id>",\n // "to": "<recipient-user-id>",\n // "event": "CUSTOM_EVENT",\n // "data": "hello"\n //}\n err := json.Unmarshal(ep.Data, &message)\n if err != nil {\n fmt.Println(err)\n return\n }\n\n // Fire custom event based on some\n // business logic\n if message.Event != "" {\n ep.Kws.Fire(message.Event, []byte(message.Data))\n }\n\n // Emit the message directly to specified user\n err = ep.Kws.EmitTo(clients[message.To], ep.Data, socketio.TextMessage)\n if err != nil {\n fmt.Println(err)\n }\n })\n\n // On disconnect event\n socketio.On(socketio.EventDisconnect, func(ep *socketio.EventPayload) {\n // Remove the user from the local clients\n delete(clients, ep.Kws.GetStringAttribute("user_id"))\n fmt.Printf("Disconnection event - User: %s", ep.Kws.GetStringAttribute("user_id"))\n })\n\n // On close event\n // This event is called when the server disconnects the user actively with .Close() method\n socketio.On(socketio.EventClose, func(ep *socketio.EventPayload) {\n // Remove the user from the local clients\n delete(clients, ep.Kws.GetStringAttribute("user_id"))\n fmt.Printf("Close event - User: %s", ep.Kws.GetStringAttribute("user_id"))\n })\n\n // On error event\n socketio.On(socketio.EventError, func(ep *socketio.EventPayload) {\n fmt.Printf("Error event - User: %s", ep.Kws.GetStringAttribute("user_id"))\n })\n\n app.Get("/ws/:id", socketio.New(func(kws *socketio.Websocket) {\n\n // Retrieve the user id from endpoint\n userId := kws.Params("id")\n\n // Add the connection to the list of the connected clients\n // The UUID is generated randomly and is the key that allow\n // socketio to manage Emit/EmitTo/Broadcast\n clients[userId] = kws.UUID\n\n // Every websocket connection has an optional session key => value storage\n kws.SetAttribute("user_id", userId)\n\n // Broadcast to all the connected users the newcomer\n newUserMsg, _ := json.Marshal(fmt.Sprintf("New user connected: %s and UUID: %s", userId, kws.UUID))\n kws.Broadcast(newUserMsg, true, socketio.TextMessage)\n\n // Write welcome message. Raw text is encoded as a JSON string.\n welcomeMsg, _ := json.Marshal(fmt.Sprintf("Hello user: %s with UUID: %s", userId, kws.UUID))\n kws.Emit(welcomeMsg, socketio.TextMessage)\n }))\n\n log.Fatal(app.Listen(":3000"))\n}\n'})}),"\n",(0,l.jsx)(t.h3,{id:"typescript--javascript-client",children:"TypeScript / JavaScript client"}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-ts",children:'import { io } from "socket.io-client";\n\nconst socket = io("http://localhost:3000", {\n path: "/ws",\n transports: ["websocket"],\n});\n\nsocket.on("connect", () => {\n console.log("connected, sid =", socket.id);\n\n // Send a message to the server\n socket.emit("message", {\n from: "user1",\n to: "user2",\n event: "",\n data: "hello",\n });\n});\n\nsocket.on("message", (data: unknown) =>
1 {\n console.log("received message:", data);\n});\n\nsocket.on("disconnect", (reason) => {\n console.log("disconnected:", reason);\n});\n'})}),"\n",(0,l.jsx)(t.hr,{}),"\n",(0,l.jsx)(t.h2,{id:"supported-events",children:"Supported events"}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Const"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Event"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventMessage"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"message"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Fired when a ",(0,l.jsx)(t.code,{children:'socket.emit("message", \u2026)'})," event is received from the client"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventPing"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"ping"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Fired when a WebSocket PING control frame is received (RFC 6455); ",(0,l.jsx)(t.code,{children:"Data"})," carries its payload. Engine.IO PING is server-originated and not surfaced via this event."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventPong"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"pong"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Fired when an Engine.IO PONG (",(0,l.jsx)(t.code,{children:'"3"'}),") replies to the server's heartbeat or when a WebSocket PONG control frame is received."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventDisconnect"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"disconnect"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Fired exactly once on disconnection. ",(0,l.jsx)(t.code,{children:"Error"})," is nil for a clean close (",(0,l.jsx)(t.code,{children:"Close"}),", a client SIO DISCONNECT, or a peer Close frame with code 1000, 1001 or none, RFC 6455 section 11.7) and carries the cause otherwise."]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventConnect"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"connect"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Fired after the Engine.IO / Socket.IO handshake completes; ",(0,l.jsx)(t.code,{children:"ep.HandshakeAuth"})," is populated with the client's ",(0,l.jsx)(t.code,{children:"auth"})," payload (raw JSON, nil if not provided)"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventClose"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"close"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Fired exactly once when the connection is actively closed from the server via ",(0,l.jsx)(t.code,{children:"Close"}),", before the closing frames are queued: frames a listener emits still reach the client. Different from client disconnection"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EventError"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"error"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Fired when some error appears useful also for debugging websockets"})]})]})]}),"\n",(0,l.jsxs)(t.p,{children:["Custom events map directly to the event name used in ",(0,l.jsx)(t.code,{children:'socket.emit("myEvent", \u2026)'})," on the client and ",(0,l.jsx)(t.code,{children:'kws.EmitEvent("myEvent", data)'})," on the server."]}),"\n",(0,l.jsx)(t.h2,{id:"event-payload-object",children:"Event Payload object"}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Variable"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Type"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Kws"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"*Websocket"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The connection object"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Name"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"The name of the event"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"SocketUUID"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Unique connection UUID"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"SocketAttributes"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"map[string]any"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Snapshot of the connection's attributes at dispatch time; nil when none were set"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Error"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"error"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"(optional) Fired from disconnection or error events"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Data"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"[]byte"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Raw JSON of the event payload (first argument of ",(0,l.jsx)(t.code,{children:"socket.emit"}),"); a sub-slice of the frame the event arrived in, safe to retain"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Args"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"[][]byte"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"All raw JSON arguments after the event name; useful when the client emits multiple values"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"AckID"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"uint64"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Ack id assigned by the client when it emitted with a callback (0 if ",(0,l.jsx)(t.code,{children:"HasAck"})," is false)"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"HasAck"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"bool"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["True when the inbound event expects an ack reply; respond via ",(0,l.jsx)(t.code,{children:"EventPayload.Ack(args...)"})]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"HandshakeAuth"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"json.RawMessage"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Raw JSON auth payload from the Socket.IO handshake; populated on ",(0,l.jsx)(t.code,{children:"EventConnect"})," listeners (use ",(0,l.jsx)(t.code,{children:"Kws.HandshakeAuth()"})," elsewhere)"]})]})]})]}),"\n",(0,l.jsx)(t.h2,{id:"socket-instance-functions",children:"Socket instance functions"}),"\n",(0,l.jsxs)(t.table,{children:[(0,l.jsx)(t.thead,{children:(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Name"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Type"}),(0,l.jsx)(t.th,{style:{textAlign:"left"},children:"Description"})]})}),(0,l.jsxs)(t.tbody,{children:[(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"SetAttribute"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Set a specific attribute for the specific socket connection"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"GetUUID"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Get socket connection UUID"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"SetUUID"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"error"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Set socket connection UUID"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"GetAttribute"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"string"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Get a specific attribute from the socket attributes"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitToList"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Emit the message to a specific socket uuids list"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitTo"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"error"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Emit to a specific socket connection"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Broadcast"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Broadcast to all the active connections except broadcasting the message to itself"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Fire"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Fire custom event"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Emit"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Send data as a ",(0,l.jsx)(t.code,{children:'"message"'})," socket.io event; valid JSON is passed through, raw text is JSON-encoded"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitEvent"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Send a named socket.io event; valid JSON is passed through, raw text is JSON-encoded"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitArgs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Emit a named event with multiple arguments; valid JSON is passed through, raw text is JSON-encoded"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitWithAck"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Emit an event and invoke ",(0,l.jsx)(t.code,{children:"cb(ack)"})," when the client acks (uses ",(0,l.jsx)(t.code,{children:"OutboundAckTimeout"}),")"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitWithAckTimeout"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Like ",(0,l.jsx)(t.code,{children:"EmitWithAck"})," but with a per-call timeout and a structured ",(0,l.jsx)(t.code,{children:"AckCallback"})]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"EmitWithAckArgs"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Multi-arg variant; ",(0,l.jsx)(t.code,{children:"cb([][]byte, error)"})," receives the ack tuple (uses ",(0,l.jsx)(t.code,{children:"OutboundAckTimeout"}),")"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"HandshakeAuth"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"json.RawMessage"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Raw JSON auth payload sent by the client at connect time (nil if absent)"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"IsAlive"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"bool"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Reports whether the underlying connection is still open and the heartbeat loop is running"})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"IsPolling"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"bool"})}),(0,l.jsxs)(t.td,{style:{textAlign:"left"},children:["Reports whether the session is bound to HTTP long-polling rather than WebSocket; when true, ",(0,l.jsx)(t.code,{children:"Conn"})," is nil"]})]}),(0,l.jsxs)(t.tr,{children:[(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Close"}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:(0,l.jsx)(t.code,{children:"void"})}),(0,l.jsx)(t.td,{style:{textAlign:"left"},children:"Actively close the connection from the server"})]})]})]}),"\n",(0,l.jsx)(t.p,{children:(0,l.jsx)(t.strong,{children:"Note: the FastHTTP connection can be accessed directly from the instance"})}),"\n",(0,l.jsx)(t.pre,{children:(0,l.jsx)(t.code,{className:"language-go",children:"kws.Conn\n"})}),"\n",(0,l.jsxs)(t.p,{children:[(0,l.jsx)(t.code,{children:"kws.Conn"})," is ",(0,l.jsx)(t.code,{children:"nil"})," for HTTP long-polling sessions. Code that touches the underlying WebSocket directly should guard with ",(0,l.jsx)(t.code,{children:"if kws.Conn != nil"})," or check the transport via the absence of ",(0,l.jsx)(t.code,{children:"kws.Conn"}),". Listener APIs (",(0,l.jsx)(t.code,{children:"Emit"}),", ",(0,l.jsx)(t.code,{children:"Ack"}),", ",(0,l.jsx)(t.code,{children:"Close"}),", ",(0,l.jsx)(t.code,{children:"Broadcast"}),", ",(0,l.jsx)(t.code,{children:"EmitWithAck"}
1),", etc.) work transparently on both transports."]})]})}function h(e={}){let{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,l.jsx)(t,{...e,children:(0,l.jsx)(a,{...e})}):a(e)}},28453(e,t,n){n.d(t,{R:()=>r,x:()=>d});var s=n(296540);let l={},i=s.createContext(l);function r(e){let t=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:r(e.components),s.createElement(i.Provider,{value:t},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.