Socket.IO v5 server implementation in Swift, built on top of Hummingbird.
This package provides:
SocketIO: the server librarySocketIORedisAdapter: a Redis-backed cluster coordinator for multi-node deploymentsSocketIOTestApp: a small demo server used for protocol testing
The current implementation includes:
- Socket.IO v5 namespace connect/disconnect flow
- event and acknowledgement packets
- binary event and binary acknowledgement packet encoding/decoding
- namespace-scoped rooms with
join,leave, andleaveAll - fluent broadcasting with
to,excluding, andsocket.broadcast - a pluggable adapter layer with a built-in
InMemoryAdapter - cluster-aware broadcasting with
local,fetchSockets, remote socket control, andserverSideEmit - an optional Redis-backed cluster coordinator for horizontal scaling
- Engine.IO-style polling and WebSocket transport support
- a Swift-first API with raw
SocketIOValueaccess and typedCodablehelpers
Add the package dependency to your Package.swift:
.package(url: "https://github.com/thomasauger/swift-server-socket.io.git", exact: "5.0.0-beta.1")Then depend on the SocketIO product:
.product(name: "SocketIO", package: "swift-server-socket.io")For Redis-backed horizontal scaling, also depend on:
.product(name: "SocketIORedisAdapter", package: "swift-server-socket.io")import SocketIO
@main
struct App {
static func main() async throws {
let server = Server(
port: 3000,
configuration: .init(
heartbeat: .init(
pingTimeout: .milliseconds(200),
pingInterval: .milliseconds(300),
connectTimeout: .milliseconds(1_000)
),
transport: .init(maxPayload: 1_000_000)
)
)
server.onConnection { socket in
await socket.emit("auth", arguments: [socket.handshake.auth ?? .null])
socket.on("message") { event, _ in
await socket.emit("message-back", arguments: event.arguments)
}
socket.on("message-with-ack") { event, ack in
try? await ack?.send(arguments: event.arguments)
}
await socket.join("chat")
await socket.broadcast.to("chat").emit("joined", arguments: [.string(socket.id)])
}
let custom = server.namespace("/custom")
custom.onConnection { socket in
await socket.emit("auth", arguments: [socket.handshake.auth ?? .null])
}
await server.to("chat").emit("server-ready", arguments: [.bool(true)])
try await server.run()
}
}Use a custom adapter by providing namespace settings in ServerConfiguration:
let configuration = ServerConfiguration(
namespaces: .init(
adapterFactory: { _ in InMemoryAdapter() }
)
)Use the Redis coordinator to enable cross-node broadcasts and server-side events:
import SocketIO
import SocketIORedisAdapter
let cluster = RedisClusterCoordinator(
nodeID: "api-1",
configuration: try .init(url: "redis://127.0.0.1:6379")
)
let server = Server(
port: 3000,
configuration: .init(
cluster: .init(coordinator: cluster)
)
)Cluster-aware APIs include:
server.local,namespace.local, andsocket.broadcast.localfetchSockets()RemoteSocketoperations:emit,join,leave, anddisconnectserverSideEmit(...)with optional acknowledgement collection
When deploying multiple Socket.IO nodes behind a load balancer, sticky sessions are still required for Engine.IO polling and transport upgrades.
Run the Swift test suite:
swift testRun the Docker-based protocol tests:
docker compose run --build --rm socket-test npm test- Socket.IO protocol specification: https://github.com/socketio/socket.io-protocol
- Socket.IO website: https://socket.io/