A Swift server SDK for interacting with the Sockudo WebSocket server HTTP API. Publish events, authorize channels, authenticate users, and handle webhooks from your Swift applications.
- Swift 5.9 and above
- iOS 13.0 and above
- macOS 10.15 and above
- tvOS 13.0 and above
- watchOS 6.0 and above
To integrate the library using Swift Package Manager, add it as a dependency in Xcode via File > Add Package Dependencies. The package repository URL is:
https://github.com/sockudo/sockudo-http-swift.git
Alternatively, add it as a dependency in your Package.swift file:
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "YourPackage",
products: [
.library(
name: "YourPackage",
targets: ["YourPackage"]),
],
dependencies: [
.package(url: "https://github.com/sockudo/sockudo-http-swift.git",
.upToNextMajor(from: "1.0.0")),
],
targets: [
.target(
name: "YourPackage",
dependencies: [
.product(name: "Sockudo", package: "sockudo-http-swift")
]),
]
)Then include import Sockudo in any source file where you want to use the native Sockudo module.
This package exports both products:
Sockudo: native Sockudo module and recommended default for new integrationsPusher: compatibility module for existing Pusher-shaped integrations
Examples in this README use the native Sockudo module. If you are migrating an existing Pusher integration, you can depend on .product(name: "Pusher", package: "sockudo-http-swift") and keep import Pusher.
Note: Certain initializers or methods throw an error if invalid parameters are provided or an operation fails. The use of try! in the examples below is for brevity and is not recommended for production code.
Create a Sockudo instance using your app credentials and point it at your self-hosted server:
import Sockudo
let sockudo = Sockudo(options: try! SockudoClientOptions(
appId: 123456,
key: "YOUR_APP_KEY",
secret: "YOUR_APP_SECRET",
host: "127.0.0.1",
port: 6001
))For end-to-end encrypted channels, pass an encryptionMasterKey:
let sockudo = Sockudo(options: try! SockudoClientOptions(
appId: 123456,
key: "YOUR_APP_KEY",
secret: "YOUR_APP_SECRET",
host: "127.0.0.1",
port: 6001,
encryptionMasterKey: "YOUR_BASE64_ENCODED_MASTER_KEY"
))Use the trigger(event:callback:) method to trigger an event on one or more channels.
let publicChannel = Channel(name: "my-channel", type: .public)
let publicEvent = try! Event(name: "my-event",
data: "hello world!",
channel: publicChannel)
sockudo.trigger(event: publicEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}let channelOne = Channel(name: "my-channel", type: .public)
let channelTwo = Channel(name: "my-other-channel", type: .public)
let multichannelEvent = try! Event(name: "my-multichannel-event",
data: "hello world!",
channels: [channelOne, channelTwo])
sockudo.trigger(event: multichannelEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}Send multiple events in a single API call (max 10 per call) using trigger(events:callback:):
let eventOne = try! Event(name: "my-event",
data: "hello world!",
channel: Channel(name: "my-channel", type: .public))
let eventTwo = try! Event(name: "my-other-event",
data: "hello world, again!",
channel: Channel(name: "my-other-channel", type: .public))
sockudo.trigger(events: [eventOne, eventTwo]) { result in
switch result {
case .success(let channelInfoList):
// Inspect `channelInfoList`
case .failure(let error):
// Handle error
}
}Prevent the triggering client from receiving its own event by specifying its socketId:
let excludedClientEvent = try! Event(name: "my-event",
data: "hello world!",
channel: Channel(name: "my-channel", type: .public),
socketId: "123.456")
sockudo.trigger(event: excludedClientEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}Attach an idempotency key so the server deduplicates the event on retries:
let event = try! Event(name: "my-event",
data: "hello world!",
channel: Channel(name: "my-channel", type: .public),
idempotencyKey: "unique-key-for-this-event")
sockudo.trigger(event: event) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}Note: The trigger(…) method is asynchronous. In non-GUI contexts, use a semaphore if you need to wait for the result:
let sema = DispatchSemaphore(value: 0)
sockudo.trigger(event: publicEvent) { result in
// Handle result
sema.signal()
}
sema.wait()Users attempting to subscribe to a private or presence channel must first be authenticated. Generate an authentication token to return to the subscribing client.
let userSocketId = "123.456"
let privateChannel = Channel(name: "my-channel", type: .private)
sockudo.authenticate(channel: privateChannel,
socketId: userSocketId) { result in
switch result {
case .success(let authToken):
// Return `authToken` to the client
case .failure(let error):
// Handle error
}
}For presence channels, include user identity data:
let userData = PresenceUserAuthData(userId: "USER_ID", userInfo: ["name": "Jane Smith"])
let presenceChannel = Channel(name: "my-channel", type: .presence)
sockudo.authenticate(channel: presenceChannel,
socketId: "USER_SOCKET_ID",
userData: userData) { result in
switch result {
case .success(let authToken):
// Return `authToken` to the client
case .failure(let error):
// Handle error
}
}Authenticate a user for server-to-user event delivery:
let userAuthData = UserAuthData(userId: "USER_ID", userInfo: ["name": "Jane Smith"])
sockudo.authenticateUser(socketId: "USER_SOCKET_ID",
userData: userAuthData) { result in
switch result {
case .success(let authToken):
// Return `authToken` to the client
case .failure(let error):
// Handle error
}
}Verify that a received webhook request originated from your Sockudo server. Valid webhooks contain special headers with your application key and an HMAC signature of the payload:
sockudo.verifyWebhook(request: receivedWebhookRequest) { result in
switch result {
case .success(let webhook):
// Inspect `webhook`
case .failure(let error):
// Handle error
}
}This library supports end-to-end encryption of private channels. Only you and your connected clients can read the messages.
-
Set up private channel authentication on your server.
-
Generate a 32-byte master encryption key encoded as Base64. Never share this key.
openssl rand -base64 32
-
Pass the key to
SockudoClientOptions:let options = try! SockudoClientOptions( appId: 123456, key: "YOUR_APP_KEY", secret: "YOUR_APP_SECRET", host: "127.0.0.1", port: 6001, encryptionMasterKey: "<MASTER KEY FROM PREVIOUS COMMAND>" )
-
Use channels of type
encrypted. Encrypted channel names must be prefixed withprivate-encrypted-. -
Subscribe to these channels in your client. Only clients with the matching key can decrypt messages.
Note: You cannot trigger a single event on a mix of encrypted and unencrypted channels in one call. Each requires a separate API request.
// All occupied channels
sockudo.channels { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
// Only occupied presence channels (with user counts)
sockudo.channels(withFilter: .presence,
attributeOptions: .userCount) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}let presenceChannel = Channel(name: "my-channel", type: .presence)
sockudo.channelInfo(for: presenceChannel,
attributeOptions: [.userCount]) { result in
switch result {
case .success(let channelInfo):
// Inspect `channelInfo`
case .failure(let error):
// Handle error
}
}Note: If the channel is not occupied, the returned ChannelInfo will have isOccupied set to false and no attributes will be populated.
let presenceChannel = Channel(name: "my-channel", type: .presence)
sockudo.users(for: presenceChannel) { result in
switch result {
case .success(let users):
// Inspect `users`
case .failure(let error):
// Handle error
}
}let channel = Channel(name: "my-channel", type: .public)
sockudo.history(
for: channel,
options: .init(limit: 50, direction: "newest_first")
) { result in
print(result)
}
sockudo.history(
for: channel,
options: .init(cursor: "opaque-cursor-from-previous-page")
) { result in
print(result)
}let presenceChannel = Channel(name: "my-channel", type: .presence)
sockudo.presenceHistory(
for: presenceChannel,
options: .init(limit: 50, direction: "newest_first")
) { result in
print(result)
}
sockudo.presenceHistory(
for: presenceChannel,
options: .init(cursor: "opaque-cursor-from-previous-page")
) { result in
print(result)
}
sockudo.presenceSnapshot(
for: presenceChannel,
options: .init(atSerial: 4)
) { result in
print(result)
}The library is completely open source and released under the MIT license.