SkipSocketIO
Real-time bidirectional communication with Socket.IO for Skip apps on both iOS and Android.
About
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)
Setup
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") ]) ] )
Usage
Connecting to a Server
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()
Listening for Events
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)") }
Removing Event Handlers
// Remove handlers for a specific event socket.off("chat message") // Remove all handlers socket.removeAllHandlers()
Catch-All Handler
Listen for every incoming event:
socket.onAny { eventName, data in print("Event '\(eventName)' received with data: \(data)") }
Emitting Events
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") }
Acknowledgements
Emit an event and receive an acknowledgement from the server:
socket.emitWithAck("get-users", ["room-1"]) { ackData in print("Server responded with: \(ackData)") }
Connection Status
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") }
Connect with Timeout
socket.connect(timeoutAfter: 5.0) { print("Connection timed out after 5 seconds") }
Namespaces
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()
Configuration Options
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 ])
SwiftUI Integration
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() } } }
API Reference
SkipSocketIOClient
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 |
SkipSocketIOManager
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 |
SocketIOStatus
| Case | Description |
|---|---|
.notConnected |
Has never been connected |
.connecting |
Connection in progress |
.connected |
Currently connected |
.disconnected |
Was connected, now disconnected |
SocketIOEvent
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 |
SkipSocketIOClientOption
| 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 |
Building
This project is a Swift Package Manager module that uses the Skip plugin to build the package for both iOS and Android.
Testing
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.
License
This software is licensed under the Mozilla Public License 2.0.