Real-time bidirectional communication with Socket.IO for Skip apps on both iOS and Android.
SkipSocketIO wraps the native Socket.IO client libraries for each platform:
- iOS/macOS: socket.io-client-swift (v16+)
- Android: socket.io-client-java (v2.1)
Key features inherited from Socket.IO:
- Automatic reconnection with exponential backoff
- Packet buffering and automatic acknowledgments
- Event-driven communication with
emitandon - Multiplexing through namespaces
- Transport fallback (WebSocket with HTTP long-polling fallback)
Add the dependency to your Package.swift file:
let package = Package(
name: "my-package",
products: [
.library(name: "MyProduct", targets: ["MyTarget"]),
],
dependencies: [
.package(url: "https://source.skip.dev/skip-socketio.git", "0.0.0"..<"2.0.0"),
],
targets: [
.target(name: "MyTarget", dependencies: [
.product(name: "SkipSocketIO", package: "skip-socketio")
])
]
)Create a client and connect:
import SkipSocketIO
let socket = SkipSocketIOClient(socketURL: URL(string: "https://example.org")!, options: [
.secure(true),
.reconnects(true),
.reconnectAttempts(5),
])
socket.on(SocketIOEvent.connect) { _ in
print("Connected! Socket ID: \(socket.socketId ?? "unknown")")
}
socket.on(SocketIOEvent.disconnect) { _ in
print("Disconnected")
}
socket.on(SocketIOEvent.connectError) { data in
print("Connection error: \(data)")
}
socket.connect()Use on for persistent listeners, or once for one-time handlers:
// Called every time the event fires
socket.on("chat message") { data in
if let message = data.first as? String {
print("Received: \(message)")
}
}
// Called once, then automatically removed
socket.once("welcome") { data in
print("Server welcome: \(data)")
}// Remove handlers for a specific event
socket.off("chat message")
// Remove all handlers
socket.removeAllHandlers()Listen for every incoming event:
socket.onAny { eventName, data in
print("Event '\(eventName)' received with data: \(data)")
}Send events with data to the server:
// Simple string
socket.emit("chat message", ["Hello, world!"])
// Multiple items of different types
socket.emit("update", ["status", 42, true])
// With a send completion callback
socket.emit("save", ["data to save"]) {
print("Event sent")
}Emit an event and receive an acknowledgement from the server:
socket.emitWithAck("get-users", ["room-1"]) { ackData in
print("Server responded with: \(ackData)")
}Check the current connection state:
if socket.isConnected {
print("Socket ID: \(socket.socketId ?? "")")
}
switch socket.status {
case .connected: print("Connected")
case .connecting: print("Connecting...")
case .disconnected: print("Disconnected")
case .notConnected: print("Never connected")
}socket.connect(timeoutAfter: 5.0) {
print("Connection timed out after 5 seconds")
}Use SkipSocketIOManager to connect to multiple namespaces on the same server:
let manager = SkipSocketIOManager(socketURL: URL(string: "https://example.org")!, options: [
.reconnects(true),
.forceWebsockets(true),
])
let defaultSocket = manager.defaultSocket()
let chatSocket = manager.socket(forNamespace: "/chat")
let adminSocket = manager.socket(forNamespace: "/admin")
chatSocket.on("message") { data in
print("Chat message: \(data)")
}
adminSocket.on("notification") { data in
print("Admin notification: \(data)")
}
defaultSocket.connect()
chatSocket.connect()
adminSocket.connect()let socket = SkipSocketIOClient(socketURL: URL(string: "https://example.org")!, options: [
// Transport
.forceWebsockets(true), // Use only WebSockets (no long-polling fallback)
.forcePolling(true), // Use only HTTP long-polling
.compress, // Enable WebSocket compression
.secure(true), // Use secure transports (wss://)
.path("/custom-path/"), // Custom Socket.IO server path
// Reconnection
.reconnects(true), // Enable auto-reconnection
.reconnectAttempts(10), // Max reconnection attempts (-1 for infinite)
.reconnectWait(1), // Min seconds between reconnection attempts
.reconnectWaitMax(30), // Max seconds between reconnection attempts
.randomizationFactor(0.5), // Jitter factor for reconnection delay
// Headers and parameters
.extraHeaders(["Authorization": "Bearer token123"]),
.connectParams(["userId": "abc"]),
.auth(["token": "secret"]),
// Other
.forceNew(true), // Always create a new engine
.log(true), // Enable debug logging
.selfSigned(true), // Allow self-signed certificates (dev only)
.enableSOCKSProxy(false), // SOCKS proxy support
])import SwiftUI
import SkipSocketIO
struct ChatView: View {
@State var messages: [String] = []
@State var inputText: String = ""
@State var socket = SkipSocketIOClient(
socketURL: URL(string: "https://chat.example.org")!,
options: [.reconnects(true)]
)
var body: some View {
VStack {
List(messages, id: \.self) { message in
Text(message)
}
HStack {
TextField("Message", text: $inputText)
.textFieldStyle(.roundedBorder)
Button("Send") {
socket.emit("chat message", [inputText])
inputText = ""
}
}
.padding()
}
.task {
socket.on("chat message") { data in
if let msg = data.first as? String {
messages.append(msg)
}
}
socket.connect()
}
}
}The primary interface for Socket.IO communication.
| Method / Property | Description |
|---|---|
init(socketURL:options:) |
Create a client for the given server URL |
connect() |
Connect to the server |
connect(timeoutAfter:handler:) |
Connect with a timeout callback |
disconnect() |
Disconnect from the server |
isConnected: Bool |
Whether the client is currently connected |
socketId: String? |
The server-assigned session ID |
status: SocketIOStatus |
Current connection status |
on(_:callback:) |
Register a persistent event handler |
once(_:callback:) |
Register a one-time event handler |
off(_:) |
Remove all handlers for an event |
removeAllHandlers() |
Remove all event handlers |
onAny(_:) |
Register a catch-all handler for all incoming events |
emit(_:_:completion:) |
Emit an event with data |
emitWithAck(_:_:ackCallback:) |
Emit an event and receive a server acknowledgement |
Manages connections and namespaces for a server.
| Method | Description |
|---|---|
init(socketURL:options:) |
Create a manager for the given server URL |
defaultSocket() |
Returns a client for the default namespace (/) |
socket(forNamespace:) |
Returns a client for the given namespace |
| Case | Description |
|---|---|
.notConnected |
Has never been connected |
.connecting |
Connection in progress |
.connected |
Currently connected |
.disconnected |
Was connected, now disconnected |
Standard event name constants.
| Constant | Value | Description |
|---|---|---|
SocketIOEvent.connect |
"connect" |
Fired on successful connection |
SocketIOEvent.disconnect |
"disconnect" |
Fired on disconnection |
SocketIOEvent.connectError |
"connect_error" |
Fired on connection error |
| Option | Description | Android Support |
|---|---|---|
.compress |
WebSocket compression | Ignored |
.connectParams([String: Any]) |
GET parameters for the connect URL | Ignored |
.extraHeaders([String: String]) |
Extra HTTP headers | Supported |
.forceNew(Bool) |
Always create a new engine | Supported |
.forcePolling(Bool) |
HTTP long-polling only | Supported |
.forceWebsockets(Bool) |
WebSockets only | Supported |
.enableSOCKSProxy(Bool) |
SOCKS proxy | Ignored |
.log(Bool) |
Debug logging | Ignored |
.path(String) |
Custom Socket.IO path | Supported |
.reconnects(Bool) |
Auto-reconnection | Supported |
.reconnectAttempts(Int) |
Max reconnection attempts | Supported |
.reconnectWait(Int) |
Min seconds before reconnect | Supported |
.reconnectWaitMax(Int) |
Max seconds before reconnect | Supported |
.randomizationFactor(Double) |
Reconnect jitter | Supported |
.secure(Bool) |
Secure transports | Supported |
.selfSigned(Bool) |
Self-signed certs (dev only) | Ignored |
.auth([String: Any]) |
Authentication payload | Ignored |
This project is a Swift Package Manager module that uses the Skip plugin to transpile Swift into Kotlin.
Building the module requires that Skip be installed using
Homebrew with brew install skiptools/skip/skip.
This will also install the necessary build prerequisites:
Kotlin, Gradle, and the Android build tools.
The module can be tested using the standard swift test command
or by running the test target for the macOS destination in Xcode,
which will run the Swift tests as well as the transpiled
Kotlin JUnit tests in the Robolectric Android simulation environment.
Parity testing can be performed with skip test,
which will output a table of the test results for both platforms.
This software is licensed under the Mozilla Public License 2.0.