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::Private
classes - 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.}"
rescue Conversant::ApiError => e
# Handle API errors
puts "API error: #{e.}"
rescue Conversant::Error => e
# Handle general errors
puts "Error: #{e.}"
end
Migration Guide
From Existing Utils::Conversant::V3::Private
Phase 1: Authentication Only
- Add
gem 'conversant'
to Gemfile - Replace
ConversantHttpClientV3
module 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
ConversantHttpClientV3
module - 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.10 (2025-09-30)
Documentation Update: Enhanced documentation for v1.0.9 critical fixes
Version 1.0.9 - Critical Bug Fix
Fixed authentication issues with LMS and VMS services:
- ✅ Fixed JSESSIONID authentication for LMS service (resolves "Missing JSESSIONID" error)
- ✅ Fixed JSESSIONID authentication for VMS service
- ✅ Improved RestClient cookie handling (switched from manual
'Cookie'
header to:cookies
option) - ✅ Fixed method visibility issues in Authorization module
Upgrade recommended if you're using LMS or VMS services. CDN and Portal services are unaffected.
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.