Official Ruby server SDK for Sockudo — a fast, self-hosted WebSocket server with full Pusher HTTP API compatibility.
- Ruby 3.0 or greater
- Rails and other Ruby frameworks are supported provided you are running a supported Ruby version.
Add to your Gemfile:
gem 'sockudo'Then run bundle install, or install directly:
gem install sockudorequire 'sockudo'
sockudo = Sockudo::Client.new(
app_id: 'your-app-id',
key: 'your-app-key',
secret: 'your-app-secret',
host: '127.0.0.1',
port: 6001,
use_tls: false
)
sockudo.trigger('my-channel', 'my-event', { message: 'hello world' })sockudo = Sockudo::Client.new(
app_id: 'your-app-id',
key: 'your-app-key',
secret: 'your-app-secret',
host: '127.0.0.1',
port: 6001,
use_tls: false
)use_tls is optional and defaults to true. It sets the scheme and port accordingly. A custom port value takes precedence over use_tls.
Sockudo.app_id = 'your-app-id'
Sockudo.key = 'your-app-key'
Sockudo.secret = 'your-app-secret'
Sockudo.host = '127.0.0.1'
Sockudo.port = 6001
Sockudo.use_tls = falseIf SOCKUDO_URL is set in the environment, use from_env to configure automatically:
# Reads SOCKUDO_URL in the form: http://KEY:SECRET@HOST:PORT/apps/APP_ID
sockudo = Sockudo::Client.from_envGlobal configuration is also automatically read from SOCKUDO_URL when set.
Sockudo.http_proxy = 'http://user:password@proxy-host:8080'sockudo.trigger('my-channel', 'my-event', { message: 'hello world' })sockudo.trigger(['channel-1', 'channel-2'], 'my-event', { message: 'hello world' })Up to 10 channels per call.
Pass a socket_id option to prevent the triggering connection from receiving its own event:
sockudo.trigger('my-channel', 'my-event', { message: 'hello' }, { socket_id: '123.456' })Send multiple events in a single HTTP request (up to 10 per call):
sockudo.trigger_batch([
{ channel: 'channel-1', name: 'event-1', data: { x: 1 } },
{ channel: 'channel-2', name: 'event-2', data: { x: 2 } },
{ channel: 'channel-3', name: 'event-3', data: { x: 3 } },
])Pass an idempotency_key to safely retry publishes without causing duplicate deliveries:
sockudo.trigger(
'my-channel',
'my-event',
{ message: 'hello' },
{ idempotency_key: 'order-shipped-order-789' }
)The server deduplicates publishes with the same key within the configured window.
auth = sockudo.authenticate('private-my-channel', params[:socket_id])
# Returns JSON: {"auth":"key:signature"}auth = sockudo.authenticate(
'presence-my-channel',
params[:socket_id],
user_id: 'user-123',
user_info: { name: 'Jane Doe', role: 'admin' }
)
# Returns JSON: {"auth":"key:signature","channel_data":"..."}auth = sockudo.authenticate_user(params[:socket_id], { id: 'user-123', name: 'Jane Doe' })Create a webhook object from a Rack::Request (available as request in Rails controllers and Sinatra handlers):
webhook = sockudo.webhook(request)
if webhook.valid?
webhook.events.each do |event|
case event['name']
when 'channel_occupied'
puts "Channel occupied: #{event['channel']}"
when 'channel_vacated'
puts "Channel vacated: #{event['channel']}"
when 'client_event'
puts "Client event: #{event['event']} on #{event['channel']}"
end
end
render plain: 'ok'
else
render plain: 'invalid', status: 401
end# Info about a channel
info = sockudo.channel_info('my-channel')
occupied = info[:occupied]
# User count for a presence channel
info = sockudo.channel_info('presence-my-channel', info: 'user_count')
user_count = info[:user_count]
# List channels (optionally filtered)
result = sockudo.channels(filter_by_prefix: 'presence-')
# Read channel history with newest-first pagination
page = sockudo.channel_history('my-channel', limit: 50, direction: 'newest_first')
next_cursor = page[:next_cursor]
# Continue pagination with an opaque cursor
page_2 = sockudo.channel_history('my-channel', cursor: next_cursor)
# Read presence history for a presence channel
presence_page = sockudo.channel_presence_history(
'presence-my-channel',
limit: 50,
direction: 'newest_first'
)
# Reconstruct effective members at a point in time
snapshot = sockudo.channel_presence_snapshot(
'presence-my-channel',
at_serial: 4
)
# Users in a presence channel
result = sockudo.channel_users('presence-my-channel')There are two reasons to use async methods: avoiding blocking in a request-response cycle, or running inside an event loop.
Add em-http-request to your Gemfile and run with the EventMachine reactor active (e.g. inside Thin):
sockudo.get_async('/channels').callback { |response|
puts response[:channels].inspect
}.errback { |error|
puts "Error: #{error}"
}
sockudo.trigger_async('my-channel', 'my-event', { message: 'hello' }).callback { |response|
puts 'Triggered'
}If the EventMachine reactor is not running, async requests are made using threads managed by httpclient. An HTTPClient::Connection object is returned immediately.
sockudo.trigger_async('my-channel', 'my-event', { message: 'hello' })All errors are descendants of Sockudo::Error:
begin
sockudo.trigger('my-channel', 'my-event', { message: 'hello' })
rescue Sockudo::AuthenticationError => e
# invalid credentials
rescue Sockudo::HTTPError => e
# network or HTTP-level error
rescue Sockudo::Error => e
# catch-all
endAssign any logger compatible with Ruby's standard Logger interface:
# Rails
Sockudo.logger = Rails.logger
# Default: logs at INFO level to STDOUTbundle install
bundle exec rake specSockudo implements the full Pusher HTTP API. If you prefer to use the official pusher gem or are migrating from Pusher, point it at your Sockudo instance:
require 'pusher'
pusher = Pusher::Client.new(
app_id: 'your-app-id',
key: 'your-app-key',
secret: 'your-app-secret',
host: '127.0.0.1',
port: 6001
)All standard Pusher SDK calls work against a self-hosted Sockudo server without modification.
MIT