Conversant Ruby Gem
A Ruby client library for interacting with Conversant/SwiftFederation CDN services, providing clean interfaces for Portal, CDN, and LMS APIs with V3 authentication support.
Features
- Zero Configuration - Automatically uses existing CONVERSANT environment variables
- V3 SSO Authentication - Handles complex authentication flow with session management
- Service-Oriented Architecture - Separate classes for Portal, CDN, LMS, VMS, and OSS services
- Clean Service Structure - Each service organized with focused sub-services (analytics, domain, dashboard, etc.)
- 79 API Methods - Complete coverage across 4 main services (CDN: 46, LMS: 19, VMS: 9, Portal: 5)
- Comprehensive CDN Management - Full suite of CDN operations including domains, certificates, analytics
- Inheritance-Friendly - Easy to extend with your custom business logic
- Redis Session Caching - Automatic session management with configurable TTL
- Thread-Safe - Proper cookie jar management for concurrent requests
- Drop-in Replacement - Works with existing
Utils::Conversant::V3::Privateclasses - YARD Documentation - Complete API documentation with examples
- RBS Type Signatures - Full type coverage for better IDE support
Installation
Add this line to your application's Gemfile:
gem 'conversant'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install conversant
Quick Start
Zero Configuration (If ENV Variables Already Set)
If you're already using the CONVERSANT system, the gem works immediately:
require 'conversant'
# No configuration needed! The gem automatically uses:
# - ENV['PRIVATE_CDN_ENDPOINT']
# - ENV['PRIVATE_LMS_ENDPOINT']
# - ENV['SWIFTSERVE_IDENTIFIER_ID']
# - ENV['SWIFTSERVE_IDENTIFIER_HASH']
# - All other CONVERSANT ENV variables
# Just start using it:
portal = Conversant::V3.portal(customer_id)
cdn = Conversant::V3.cdn(customer_id)
lms = Conversant::V3.lms(customer_id)
vms = Conversant::V3.vms(customer_id)
oss = Conversant::V3.oss(customer_id)
Configuration
Environment Variables
The gem automatically uses these CONVERSANT environment variables:
| Variable | Description | Default | Required |
|---|---|---|---|
PORTAL_ROOT_HOSTNAME |
Portal hostname | console.swiftfederation.com |
No |
PORTAL_SSO_HOSTNAME |
SSO hostname | sso.swiftfederation.com |
No |
PRIVATE_PORTAL_ENDPOINT |
Portal API endpoint | https://{PORTAL_ROOT_HOSTNAME} |
No |
PRIVATE_SSO_ENDPOINT |
SSO endpoint | https://{PORTAL_SSO_HOSTNAME} |
No |
PRIVATE_CDN_ENDPOINT |
CDN API endpoint | - | Yes |
PRIVATE_LMS_ENDPOINT |
LMS API endpoint | - | Yes |
PRIVATE_VMS_ENDPOINT |
VMS API endpoint | - | No |
PRIVATE_OSS_ENDPOINT |
OSS API endpoint | - | No |
CONVERSANT_CDN_API_URL |
CDN API URL for http_code | - | No |
SWIFTSERVE_IDENTIFIER_ID |
Authentication ID | - | Yes |
SWIFTSERVE_IDENTIFIER_HASH |
Authentication hash | - | Yes |
DEFAULT_UA |
User agent string | Mozilla/5.0... | No |
DEFAULT_CONTENT_TYPE |
Content type header | application/json |
No |
CONVERSANT_DEBUG |
Enable debug logging | false |
No |
CONVERSANT_CACHE_TTL |
Session cache TTL (seconds) | 1200 |
No |
REDIS_URL |
Redis connection URL | - | No* |
RAILS_ENV |
Rails environment (affects SSL) | - | No |
*Redis is auto-detected from $redis global or ENV['REDIS_URL'].
Auto-Configuration (With Validation)
To ensure all required variables are present:
# In config/initializers/conversant.rb (Rails)
require 'conversant'
# Validate that all required ENV variables are set
Conversant.auto_configure!
Manual Configuration
Override specific settings if needed:
Conversant.configure do |config|
# Override only what you need
config.cache_ttl = 3600 # 1 hour instead of default 20 minutes
config.logger = Rails.logger
config.debug_mode = true
end
Usage
Basic Usage
require 'conversant'
# Initialize services
customer_id = 12345
cdn = Conversant::V3.cdn(customer_id)
lms = Conversant::V3.lms(customer_id)
vms = Conversant::V3.vms(customer_id)
CDN Domain Management
# List all domains
domains = cdn.domain.all
domains.each do |domain|
puts "Domain: #{domain.name} (ID: #{domain.id})"
puts " Type: #{domain.businessScenarioType == 1 ? 'CDN' : 'Storage'}"
puts " Status: #{domain.status == 1 ? 'Active' : 'Inactive'}"
puts " Usage: #{domain.current_disk_usage / 1_073_741_824.0} GB"
end
# Find specific domain
domain = cdn.domain.find_by(name: 'cdn.example.com')
if domain
puts "Found domain: #{domain.name} with ID: #{domain.id}"
end
# Create new CDN domain
domain_id = cdn.domain.create({
businessScenarioType: 1,
name: "cdn.example.com",
origins: [{ url_prefix: "203.0.113.10" }],
redirect_http_to_https: true,
http2_enabled: true
})
puts "Created domain with ID: #{domain_id}" if domain_id
CDN Analytics & Reporting
# Get bandwidth usage for the current month
require 'date'
start_time = Date.today.beginning_of_month.strftime("%Y-%m-%dT00:00:00Z")
end_time = Date.today.end_of_month.strftime("%Y-%m-%dT23:59:59Z")
bandwidth = cdn.analytics.bandwidths({
startTime: start_time,
endTime: end_time,
pageSize: 1000
})
total_bandwidth = bandwidth.sum { |b| b['bandwidth'] || 0 }
puts "Total bandwidth this month: #{total_bandwidth / 1_099_511_627_776.0} TB"
# Get 95th percentile for billing
bandwidth_95th = cdn.business.bandwidth95th(year: 2025)
annual_95th = bandwidth_95th.map { |m| m['bandwidth_95th'] }.max
puts "Annual 95th percentile: #{annual_95th / 1_000_000.0} Mbps"
# Top URLs analysis
top_urls = cdn.analytics.top_urls({
startTime: start_time,
endTime: end_time,
limit: 10
})
puts "Top 10 URLs:"
top_urls.each_with_index do |url, i|
puts " #{i+1}. #{url['url']} - #{url['hits']} hits"
end
Certificate Management
# List all certificates
certificates = cdn.certificate.all
certificates.each do |cert|
puts "Certificate: #{cert['name']}"
puts " Status: #{cert['status']}"
puts " Expires: #{cert['expiry_date']}"
puts " Domains: #{cert['domains']&.join(', ')}"
end
# Deploy Let's Encrypt certificate
success = cdn.certificate.deploy_auto_certificate(12345, "example.com", {
enable: true,
domains: ["example.com", "www.example.com"],
validation_method: "http",
auto_renewal: true
})
if success
puts "Auto-certificate deployed successfully"
else
puts "Failed to deploy auto-certificate"
end
Monitoring & Alerts
# Monitor domain health
domain_list = cdn.monitoring.delivery_domain_list
domain_list.each do |domain|
if domain['status'] != 'healthy'
puts "Alert: Domain #{domain['name']} status is #{domain['status']}"
end
end
# Get detailed monitoring for specific domain
domain_detail = cdn.monitoring.delivery_domain_detail(domain_id)
if domain_detail
puts "Domain: #{domain_detail['name']}"
puts " Response Time: #{domain_detail['response_time']}ms"
puts " Cache Hit Rate: #{domain_detail['cache_hit_rate']}%"
puts " Error Rate: #{domain_detail['error_rate']}%"
end
Audit Logging
# Get audit logs for the last 24 hours
yesterday = (Time.now - 86400).strftime("%Y-%m-%dT%H:%M:%SZ")
now = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
audit_logs = cdn.audit.delivery_domain_operations({
startTime: yesterday,
endTime: now,
pageSize: 100
})
audit_logs.each do |log|
puts "[#{log['timestamp']}] #{log['action']} by #{log['user']}"
puts " Domain: #{log['domain_name']}"
puts " Details: #{log['details']}"
end
Inheritance Pattern (Extend with Your Logic)
Create your own classes that inherit from the gem's services:
module Utils
module Conversant
module V3
class CDN < ::Conversant::V3::Services::CDN
# Add your custom methods
def daily_summary(date = Date.today)
start_time = date.strftime("%Y-%m-%dT00:00:00Z")
end_time = date.strftime("%Y-%m-%dT23:59:59Z")
{
bandwidth: analytics.bandwidths({startTime: start_time, endTime: end_time}),
volumes: analytics.volumes({startTime: start_time, endTime: end_time}),
spots: monitoring.spots({startTime: start_time, endTime: end_time})
}
end
end
end
end
end
# Use your extended class
cdn = Utils::Conversant::V3::CDN.new(customer_id)
summary = cdn.daily_summary # Your custom method
bandwidth = cdn.analytics.bandwidths(payload) # Parent's method
Integration with Existing Code
Option 1: Drop-in Replacement for ConversantHttpClientV3
Replace your existing authentication module:
# app/models/concerns/conversant_http_client_v3.rb
require 'conversant'
Conversant.auto_configure! unless Conversant.configured?
module ConversantHttpClientV3
def self.included(base)
base.class_eval do
include ::Conversant::V3::Mixins::Authentication
use_conversant_auth!
end
end
def authenticate
conversant_authenticate
end
def signature
sessions = authenticate
sessions && sessions[:session]
end
end
Option 2: Update Only Authorization Headers
Keep your existing classes, just update the auth:
class Utils::Conversant::V3::Private::Operation
def
# Only change this method - use gem's auth
::Conversant::V3.portal(@customer_id).send(:authorized_headers)
end
# All your existing methods remain unchanged
def appliances(gte = nil, lte = nil)
# Your existing implementation
end
end
Option 3: Using the Mixin
For classes that can't change inheritance:
class Utils::Conversant::V3::Private::Operation
include Conversant::V3::Mixins::Authentication
use_conversant_auth!
def initialize(customer_id, type = 2)
@customer_id = customer_id
@type = type
initialize_conversant_auth(customer_id, type)
end
def your_existing_method
headers = # Use gem's auth
# Your existing logic...
end
end
API Reference
Portal Service
portal = Conversant::V3.portal(customer_id)
# Get appliances
appliances = portal.appliances(start_date, end_date)
# Returns: Array of appliance hashes with ip, hostname, deleted, pop, volume, federation
CDN Service
The CDN service provides comprehensive domain, certificate, analytics, and monitoring capabilities.
cdn = Conversant::V3.cdn(customer_id)
# Domain Management
domains = cdn.domain.all # Get all domains
domain = cdn.domain.find(domain_id) # Find by ID
domain = cdn.domain.find_by(name: 'example.com') # Find by name
total_usage = cdn.domain.total_current_disk_usage # Storage usage in bytes
# Create new domain
domain_id = cdn.domain.create({
businessScenarioType: 1, # 1=CDN, 2=Storage
name: "cdn.example.com",
origins: [{ url_prefix: "203.0.113.10" }],
redirect_http_to_https: true,
http2_enabled: true,
streaming_service: false,
disabled: false
})
# Certificate Management
certificates = cdn.certificate.all # List all certificates
cert_info = cdn.certificate.auto_certificate_info(cert_id, domain_name)
# Deploy Let's Encrypt certificate
success = cdn.certificate.deploy_auto_certificate(cert_id, domain_name, {
enable: true,
domains: ["example.com", "www.example.com"],
validation_method: "http",
auto_renewal: true
})
# Analytics
bandwidth = cdn.analytics.bandwidths({
startTime: "2025-01-01T00:00:00Z",
endTime: "2025-01-31T23:59:59Z",
pageSize: 1000,
pageNumber: 0
})
hits = cdn.analytics.hits({
startTime: start_time,
endTime: end_time,
groupBy: "hour"
})
status_codes = cdn.analytics.status_codes({
startTime: start_time,
endTime: end_time,
statusCode: "200,404,500"
})
top_urls = cdn.analytics.top_urls({
startTime: start_time,
endTime: end_time,
limit: 100
})
# Business Metrics (Billing)
usage = cdn.business.bandwidth({
startTime: "2025-01-01T00:00:00Z",
endTime: "2025-01-31T23:59:59Z",
pageSize: 1000
})
# Get 95th percentile bandwidth
bandwidth_95th = cdn.business.bandwidth95th(year: 2025)
# Monitoring
domain_list = cdn.monitoring.delivery_domain_list
domain_detail = cdn.monitoring.delivery_domain_detail(domain_id)
customer_details = cdn.monitoring.customer_details
# Audit Logs
audit_logs = cdn.audit.delivery_domain_operations({
startTime: start_time,
endTime: end_time,
pageSize: 50
})
LMS Service (Live Media Streaming)
lms = Conversant::V3.lms(customer_id)
# Job Management - Query streaming jobs
jobs = lms.job.where(
page_number: 1,
page_size: 50,
status: 'active'
)
jobs.each do |job|
puts "Job #{job[:id]}: #{job[:name]}"
puts " Status: #{job[:status]}"
puts " Created: #{job[:created_at]}"
end
# Domain Management
domains = lms.domain.all(page_number: 1, page_size: 50)
domains.each do |domain|
puts "Domain: #{domain['name']} (ID: #{domain['id']})"
end
# Get specific domain details
domain = lms.domain.show(domain_id)
# Create streaming domain
domain_id = lms.domain.create({
name: "stream.example.com",
type: "live",
protocol: "rtmp"
})
# Update streaming domain
lms.domain.update({
id: domain_id,
name: "updated-stream.example.com"
})
# Delete streaming domain
lms.domain.delete(domain_id)
# Dashboard Metrics
live_duration = lms.dashboard.live
vod_duration = lms.dashboard.vod
dvr_duration = lms.dashboard.dvr
recent_jobs = lms.dashboard.recent_jobs
# Transcoding Presets
presets = lms.preset.all
presets.each do |preset|
puts "Preset: #{preset['name']} - #{preset['resolution']}"
end
VMS Service (Video Management System)
vms = Conversant::V3.vms(customer_id)
# Transcoding Jobs and Presets
jobs = vms.transcoding.jobs(page_size: 50, job_status: 2)
jobs.each do |job|
puts "Job #{job['id']}: #{job['file_name']} - Status: #{job['status']}"
end
# Get available transcoding presets
presets = vms.transcoding.presets(page_size: 50)
presets.each do |preset|
puts "Preset: #{preset['name']} - #{preset['description']}"
end
# Analytics - Transcoding duration and volume
duration = vms.analytics.transcoding({
startTime: "2025-01-01",
endTime: "2025-01-31"
})
volume = vms.analytics.volume({
startTime: "2025-01-01",
endTime: "2025-01-31"
})
# Business Metrics - Detailed transcoding breakdown
transcoding_breakdown = vms.business.transcoding({
startTime: Time.now.beginning_of_month
})
puts "SD: #{transcoding_breakdown[:vms_transcoding_sd]} minutes"
puts "HD: #{transcoding_breakdown[:vms_transcoding_hd]} minutes"
puts "UHD: #{transcoding_breakdown[:vms_transcoding_uhd]} minutes"
puts "Total: #{transcoding_breakdown[:vms_transcoding]} minutes"
# Duration and volume for billing
duration_metrics = vms.business.duration({
startTime: Time.now.beginning_of_month
})
volume_metrics = vms.business.volume({
startTime: Time.now.beginning_of_month
})
OSS Service (Object Storage Service)
oss = Conversant::V3.oss(customer_id)
# Bucket operations
buckets = oss.list_buckets
bucket = oss.get_bucket(bucket_name)
oss.create_bucket(bucket_name, region: 'us-east-1')
# Object operations
objects = oss.list_objects(bucket_name, prefix: 'videos/')
object = oss.get_object(bucket_name, object_key)
oss.put_object(bucket_name, object_key, file_content)
oss.delete_object(bucket_name, object_key)
# Presigned URLs
url = oss.presigned_url(bucket_name, object_key, expires_in: 3600)
Error Handling
begin
portal = Conversant::V3.portal(customer_id)
appliances = portal.appliances
rescue Conversant::AuthenticationError => e
# Handle authentication failures
puts "Authentication failed: #{e.message}"
rescue Conversant::ApiError => e
# Handle API errors
puts "API error: #{e.message}"
rescue Conversant::Error => e
# Handle general errors
puts "Error: #{e.message}"
end
Migration Guide
From Existing Utils::Conversant::V3::Private
Phase 1: Authentication Only
- Add
gem 'conversant'to Gemfile - Replace
ConversantHttpClientV3module with gem's wrapper - Keep all business logic unchanged
Phase 2: Gradual Migration (Optional)
- Migrate services one by one
- Start with least critical service
- Keep custom business logic where needed
Phase 3: Full Integration (Optional)
- Use inheritance pattern for all services
- Add custom methods as needed
Rollback Plan
If you need to rollback:
- Restore original
ConversantHttpClientV3module - Remove gem from Gemfile
- No changes needed to business logic
Authentication Architecture
The gem implements a multi-tier authentication system with automatic session management using a consistent Authorization module pattern across all services.
Authentication Flow
Root Authentication:
- Logs into SSO with master credentials (SWIFTSERVE_IDENTIFIER_ID/HASH)
- Obtains SESSION and SSO_GW_SESSION2 cookies
- Cached in Redis with TTL management
Service-Specific Sessions: All services follow the same authentication pattern via the Authorization module:
- Portal: Exchanges root sessions for customer-specific SESSION cookie
- CDN: Exchanges root sessions for customer-specific SESSION cookie
- LMS: Exchanges root sessions for Java-based JSESSIONID cookie
- VMS: Exchanges root sessions for Java-based JSESSIONID cookie
- OSS: Uses root sessions with S3-compatible authentication
Authorization Module Pattern: Each service implements the Authorization module which provides:
- Automatic session fetching and caching
- Session validation before API calls
- Consistent error handling
- Redis-based session storage
Session Caching
All sessions are cached in Redis with automatic refresh using a standardized key format:
# Sessions are cached with these keys:
"CONVERSANT.V3.PORTAL.SESSION.{customer_id}" # Portal SESSION cookie
"CONVERSANT.V3.PORTAL.SSO_GW_SESSION2.{customer_id}" # SSO session (shared)
"CONVERSANT.V3.CDN.SESSION.{customer_id}" # CDN SESSION cookie
"CONVERSANT.V3.LMS.JSESSIONID.{customer_id}" # LMS JSESSIONID cookie
"CONVERSANT.V3.VMS.JSESSIONID.{customer_id}" # VMS JSESSIONID cookie
# Default TTL: 20 minutes (configurable)
Session Validation
All services automatically validate session cookies before making API calls:
# Each service checks for its required cookie before making requests:
# - Portal, CDN: Validates SESSION cookie presence
# - LMS, VMS: Validates JSESSIONID cookie presence
# - If missing, automatically fetches new session
# - If fetch fails, raises AuthenticationError
Using Custom Authentication
If you need access to raw authentication headers:
# Get authorized headers for direct API calls
cdn = Conversant::V3.cdn(customer_id)
headers = cdn.send(:authorized_headers)
# Make custom API call
response = RestClient.get("#{cdn_endpoint}/custom-endpoint", headers)
Development
See DEVELOPER_GUIDE.md for detailed information on:
- Building and testing the gem
- Publishing to RubyGems
- Version management
- Documentation generation
- GitLab CI/CD setup
- Troubleshooting
Quick start:
bundle install # Install dependencies
bundle exec rspec # Run tests
bundle exec yard doc # Generate documentation
./publish.sh # Publish to RubyGems
Debugging
Enable debug mode for verbose logging:
Conversant.configure do |config|
config.debug_mode = true
config.logger = Logger.new($stdout)
end
Examples
See the examples/ directory for:
inheritance_integration.rb- Complete inheritance patternsrails_initializer.rb- Rails setup example
Troubleshooting
Common Issues
Authentication Errors
# Error: Conversant::AuthenticationError
# Solution: Check environment variables
ENV['SWIFTSERVE_IDENTIFIER_ID'] # Must be set
ENV['SWIFTSERVE_IDENTIFIER_HASH'] # Must be set
ENV['PRIVATE_CDN_ENDPOINT'] # Must be set
ENV['PRIVATE_LMS_ENDPOINT'] # Must be set
# Clear cache if credentials changed
redis.del("CONVERSANT.V3.PORTAL.SESSION.#{customer_id}")
redis.del("CONVERSANT.V3.PORTAL.SSO_GW_SESSION2.#{customer_id}")
Redis Connection Issues
# Error: Redis::CannotConnectError
# Solution: Configure Redis properly
Conversant.configure do |config|
config.redis = Redis.new(url: ENV['REDIS_URL'])
end
# Or use global $redis
$redis = Redis.new(url: ENV['REDIS_URL'])
Domain Creation Failures
# Common issues:
# 1. Domain name already exists
# 2. Invalid origin configuration
# 3. Missing FTP password for storage domains (businessScenarioType: 2)
# Check response for details:
domain_id = cdn.domain.create(payload)
if domain_id.nil?
# Check logs for error details
# Enable debug mode for more info
Conversant.configure { |c| c.debug_mode = true }
end
Certificate Deployment Issues
# Let's Encrypt validation failures:
# 1. Domain must be publicly accessible
# 2. HTTP validation requires port 80 open
# 3. DNS validation requires DNS control
# Check certificate status:
cert_info = cdn.certificate.auto_certificate_info(cert_id, domain)
puts cert_info['validation_status'] # Should be 'valid'
puts cert_info['error_message'] # If failed
Debug Mode
Enable detailed logging for troubleshooting:
Conversant.configure do |config|
config.debug_mode = true
config.logger = Logger.new($stdout)
config.log_level = :debug
end
# This will log:
# - All API requests and responses
# - Authentication attempts
# - Session cache hits/misses
# - Error details with stack traces
Performance Tips
- Use Redis connection pooling: ```ruby require 'connection_pool'
redis_pool = ConnectionPool.new(size: 5) { Redis.new } Conversant.configure do |config| config.redis = redis_pool end
2. **Batch API calls when possible**:
```ruby
# Instead of multiple calls:
domains.each { |d| cdn.domain.find(d.id) }
# Use single call with filtering:
all_domains = cdn.domain.all
my_domains = all_domains.select { |d| domain_ids.include?(d.id) }
- Increase cache TTL for stable data:
ruby Conversant.configure do |config| config.cache_ttl = 3600 # 1 hour for stable environments end
Version Notes
Latest Release: 1.0.19 (2026-02-06)
RBS Type Signature Fix: Updated LMS Job RBS signatures to match actual implementation
- Fixed
wherereturn type toArray[Hash[Symbol, untyped]]? - Fixed
build_manifest_urlparameter signature - Removed non-existent
build_profile_urlmethod signature
See CHANGELOG.md for complete version history.
Contributing
Bug reports and pull requests are welcome on GitLab at https://gitlab.vnetwork.dev/gems/conversant.
License
The gem is available as open source under the terms of the MIT License.