|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require 'logger' |
| 4 | +require 'faraday' |
| 5 | +require 'json' |
| 6 | + |
| 7 | +module Flagsmith |
| 8 | + # Ruby client for realtime access to flagsmith.com |
| 9 | + class RealtimeClient |
| 10 | + attr_accessor :running |
| 11 | + |
| 12 | + def initialize(config) |
| 13 | + @config = config |
| 14 | + @thread = nil |
| 15 | + @running = false |
| 16 | + @main = nil |
| 17 | + end |
| 18 | + |
| 19 | + def endpoint |
| 20 | + "#{@config.realtime_api_url}sse/environments/#{@main.environment.api_key}/stream" |
| 21 | + end |
| 22 | + |
| 23 | + def listen(main, remaining_attempts: Float::INFINITY, retry_interval: 0.5) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength |
| 24 | + last_updated_at = 0 |
| 25 | + @main = main |
| 26 | + @running = true |
| 27 | + @thread = Thread.new do |
| 28 | + while @running && remaining_attempts.positive? |
| 29 | + remaining_attempts -= 1 |
| 30 | + @config.logger.warn 'Beginning to pull down realtime endpoint' |
| 31 | + begin |
| 32 | + sleep retry_interval |
| 33 | + # Open connection to SSE endpoint |
| 34 | + Faraday.new(url: endpoint).get do |req| |
| 35 | + req.options.timeout = nil # Keep connection alive indefinitely |
| 36 | + req.options.open_timeout = 10 |
| 37 | + end.body.each_line do |line| # rubocop:disable Style/MultilineBlockChain |
| 38 | + # SSE protocol: Skip non-event lines |
| 39 | + next if line.strip.empty? || line.start_with?(':') |
| 40 | + |
| 41 | + # Parse SSE fields |
| 42 | + next unless line.start_with?('data: ') |
| 43 | + |
| 44 | + data = JSON.parse(line[6..].strip) |
| 45 | + updated_at = data['updated_at'] |
| 46 | + next unless updated_at > last_updated_at |
| 47 | + |
| 48 | + @config.logger.info "Realtime updating environment from #{last_updated_at} to #{updated_at}" |
| 49 | + @main.update_environment |
| 50 | + last_updated_at = updated_at |
| 51 | + end |
| 52 | + rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e |
| 53 | + @config.logger.warn "Connection failed: #{e.message}. Retrying in #{retry_interval} seconds..." |
| 54 | + rescue StandardError => e |
| 55 | + @config.logger.error "Error: #{e.message}. Retrying in #{retry_interval} seconds..." |
| 56 | + end |
| 57 | + end |
| 58 | + end |
| 59 | + |
| 60 | + @running = false |
| 61 | + end |
| 62 | + end |
| 63 | +end |
0 commit comments