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 authorized_headers
    # 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 = portal_authorized_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

  1. Add gem 'conversant' to Gemfile
  2. Replace ConversantHttpClientV3 module with gem's wrapper
  3. 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:

  1. Restore original ConversantHttpClientV3 module
  2. Remove gem from Gemfile
  3. 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

  1. 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
  2. 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
  3. 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 patterns
  • rails_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

  1. 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) }
  1. 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 where return type to Array[Hash[Symbol, untyped]]?
  • Fixed build_manifest_url parameter signature
  • Removed non-existent build_profile_url method 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.