streamforce 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -8
- data/README.md +69 -1
- data/lib/streamforce/client.rb +55 -55
- data/lib/streamforce/extension/logging.rb +88 -9
- data/lib/streamforce/extension/replay.rb +22 -0
- data/lib/streamforce/message.rb +49 -0
- data/lib/streamforce/version.rb +1 -1
- data/lib/streamforce.rb +0 -1
- metadata +54 -12
- data/lib/streamforce/extension/subscription_tracking.rb +0 -58
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 5d03ff265d884babb70b8db70637c9814a5819834ed83109b2b26537cb5c992b
         | 
| 4 | 
            +
              data.tar.gz: b7b87b62347f92e9a27139536655ee95339e8a3b22bf2c1980db1eca00fa66e9
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c4dc737d70d28c061ee3c89379b97274983c14008184acbe7c99139b59c3ed79b3ef79893ba3d486d3f142172d30461260237d171bdb7a230c6f92471769a605
         | 
| 7 | 
            +
              data.tar.gz: 6a3bbd281947060861c34e5cceab6814cfad52a80e964c80ef4da4956a3c40287e1315ef05419c69b31e60ac2bd20b394336e85a087c8fd41e22691543f6f7d7
         | 
    
        data/.rubocop.yml
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,8 +1,76 @@ | |
| 1 1 | 
             
            # Streamforce
         | 
| 2 2 |  | 
| 3 | 
            +
            In most cases, processing events received from the Salesforce Streaming API can be
         | 
| 4 | 
            +
            broken down into three very specific steps:
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            1. Connecting to the Salesforce API and listening for messaging
         | 
| 7 | 
            +
            2. Ingesting received messages into some sort of internal event bus (e.g. RabbitMQ,
         | 
| 8 | 
            +
               Kafka, Redis, etc)
         | 
| 9 | 
            +
            3. Processing the stored messages
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Streamforce aims to handle #1 and simplify the work done for #2.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            [Restforce](https://github.com/restforce/restforce) provides a simple API to connect
         | 
| 14 | 
            +
            to the Streaming API and consume messages:
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ```ruby
         | 
| 17 | 
            +
            # Restforce uses faye as the underlying implementation for CometD.
         | 
| 18 | 
            +
            require 'faye'
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            # Initialize a client with your username/password/oauth token/etc.
         | 
| 21 | 
            +
            client = Restforce.new(username: 'foo',
         | 
| 22 | 
            +
                                   password: 'bar',
         | 
| 23 | 
            +
                                   security_token: 'security token',
         | 
| 24 | 
            +
                                   client_id: 'client_id',
         | 
| 25 | 
            +
                                   client_secret: 'client_secret')
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            EM.run do
         | 
| 28 | 
            +
              # Subscribe to the PushTopic.
         | 
| 29 | 
            +
              client.subscription '/topic/AllAccounts' do |message|
         | 
| 30 | 
            +
                puts message.inspect
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
            ```
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            However, the above code is usable in a production environment because:
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            * The interactions with the Streaming API need to be logged using the correct severity
         | 
| 38 | 
            +
              (e.g. handshakes should use `Logger::DEBUG` while subscription errors should use
         | 
| 39 | 
            +
              `Logger::ERROR` for better visibility)
         | 
| 40 | 
            +
            * Replay IDs need to be stored using a persistent storage like Redis and not in-memory
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            Streamforce comes with all the batteries included.
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ## Usage
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            A very simple client, which automatically connects based on the following environment
         | 
| 47 | 
            +
            variables:
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            * `SALESFORCE_USERNAME`
         | 
| 50 | 
            +
            * `SALESFORCE_PASSWORD`
         | 
| 51 | 
            +
            * `SALESFORCE_SECURITY_TOKEN`
         | 
| 52 | 
            +
            * `SALESFORCE_CLIENT_ID`
         | 
| 53 | 
            +
            * `SALESFORCE_CLIENT_SECRET`
         | 
| 54 | 
            +
            * `REDIS_URL`
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ```ruby
         | 
| 57 | 
            +
            require "streamforce"
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            client = Streamforce::Client.new
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            subscriptions = %w[
         | 
| 62 | 
            +
              /topic/account-monitor
         | 
| 63 | 
            +
              /event/AccountUpdated__e
         | 
| 64 | 
            +
            ]
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            client.subscribe(subscriptions) do |subscription, message|
         | 
| 67 | 
            +
              # Your code
         | 
| 68 | 
            +
            end
         | 
| 69 | 
            +
            ```
         | 
| 70 | 
            +
             | 
| 3 71 | 
             
            ## Contributing
         | 
| 4 72 |  | 
| 5 | 
            -
            Bug reports and pull requests are welcome on GitHub at https://github.com/andreimaxim/streamforce | 
| 73 | 
            +
            Bug reports and pull requests are welcome on GitHub at <https://github.com/andreimaxim/streamforce>.
         | 
| 6 74 |  | 
| 7 75 | 
             
            ## License
         | 
| 8 76 |  | 
    
        data/lib/streamforce/client.rb
    CHANGED
    
    | @@ -1,79 +1,79 @@ | |
| 1 1 | 
             
            class Streamforce::Client
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
                 | 
| 8 | 
            -
             | 
| 9 | 
            -
                 | 
| 10 | 
            -
             | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                 | 
| 16 | 
            -
             | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                def password
         | 
| 20 | 
            -
                  ENV["SALESFORCE_PASSWORD"]
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                def security_token
         | 
| 24 | 
            -
                  ENV["SALESFORCE_SECURITY_TOKEN"]
         | 
| 25 | 
            -
                end
         | 
| 2 | 
            +
              attr_reader :host, :username, :password, :client_id, :client_secret, :security_token,
         | 
| 3 | 
            +
                :api_version
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def initialize(opts = {})
         | 
| 6 | 
            +
                @host           = opts.fetch(:host, ENV["SALESFORCE_HOST"])
         | 
| 7 | 
            +
                @username       = opts.fetch(:username, ENV["SALESFORCE_USERNAME"])
         | 
| 8 | 
            +
                @password       = opts.fetch(:password, ENV["SALESFORCE_PASSWORD"])
         | 
| 9 | 
            +
                @client_id      = opts.fetch(:client_id, ENV["SALESFORCE_CLIENT_ID"])
         | 
| 10 | 
            +
                @client_secret  = opts.fetch(:client_secret, ENV["SALESFORCE_CLIENT_SECRET"])
         | 
| 11 | 
            +
                @security_token = opts.fetch(:security_token, ENV["SALESFORCE_SECURITY_TOKEN"])
         | 
| 12 | 
            +
                @api_version    = opts.fetch(:api_version, ENV["SALESFORCE_API_VERSION"])
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                @logger = opts.fetch(:logger, Logger.new($stdout))
         | 
| 15 | 
            +
                @logger.level = ENV.fetch("STREAMFORCE_LOG_LEVEL", Logger::INFO)
         | 
| 16 | 
            +
              end
         | 
| 26 17 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 18 | 
            +
              def subscribe(channels = [], &blk)
         | 
| 19 | 
            +
                EM.run { subscribe_to_channels(faye, Array(channels), &blk) }
         | 
| 20 | 
            +
              end
         | 
| 30 21 |  | 
| 31 | 
            -
             | 
| 32 | 
            -
                  URI.parse("https://#{host}/services/oauth2/token")
         | 
| 33 | 
            -
                end
         | 
| 22 | 
            +
              private
         | 
| 34 23 |  | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                    grant_type: "password",
         | 
| 38 | 
            -
                    client_id: client_id,
         | 
| 39 | 
            -
                    client_secret: client_secret,
         | 
| 40 | 
            -
                    username: username,
         | 
| 41 | 
            -
                    password: "#{password}#{security_token}"
         | 
| 42 | 
            -
                  }
         | 
| 43 | 
            -
                end
         | 
| 24 | 
            +
              def instance_url
         | 
| 25 | 
            +
                authentication["instance_url"]
         | 
| 44 26 | 
             
              end
         | 
| 45 27 |  | 
| 46 | 
            -
              def  | 
| 47 | 
            -
                 | 
| 48 | 
            -
             | 
| 28 | 
            +
              def access_token
         | 
| 29 | 
            +
                authentication["access_token"]
         | 
| 30 | 
            +
              end
         | 
| 49 31 |  | 
| 50 | 
            -
             | 
| 51 | 
            -
                 | 
| 32 | 
            +
              def authentication_url
         | 
| 33 | 
            +
                URI.parse("https://#{host}/services/oauth2/token")
         | 
| 52 34 | 
             
              end
         | 
| 53 35 |  | 
| 54 | 
            -
              def  | 
| 55 | 
            -
                 | 
| 36 | 
            +
              def authentication_params
         | 
| 37 | 
            +
                {
         | 
| 38 | 
            +
                  grant_type: "password",
         | 
| 39 | 
            +
                  username: username,
         | 
| 40 | 
            +
                  password: "#{password}#{security_token}",
         | 
| 41 | 
            +
                  client_id: client_id,
         | 
| 42 | 
            +
                  client_secret: client_secret
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
              end
         | 
| 56 45 |  | 
| 57 | 
            -
             | 
| 46 | 
            +
              def authentication
         | 
| 47 | 
            +
                @authentication ||= fetch_authentication_credentials
         | 
| 48 | 
            +
              end
         | 
| 58 49 |  | 
| 59 | 
            -
             | 
| 50 | 
            +
              def fetch_authentication_credentials
         | 
| 51 | 
            +
                response = Net::HTTP.post_form(authentication_url, authentication_params)
         | 
| 52 | 
            +
                JSON.parse(response.body)
         | 
| 60 53 | 
             
              end
         | 
| 61 54 |  | 
| 62 55 | 
             
              def faye
         | 
| 63 | 
            -
                @faye ||= Faye::Client.new( | 
| 64 | 
            -
                  client.set_header "Authorization", "OAuth #{ | 
| 56 | 
            +
                @faye ||= Faye::Client.new("#{instance_url}/cometd/#{api_version}").tap do |client|
         | 
| 57 | 
            +
                  client.set_header "Authorization", "OAuth #{access_token}"
         | 
| 65 58 |  | 
| 66 59 | 
             
                  client.add_extension Streamforce::Extension::Replay.new
         | 
| 67 | 
            -
                  client.add_extension Streamforce::Extension:: | 
| 68 | 
            -
                  client.add_extension Streamforce::Extension::Logging.new
         | 
| 60 | 
            +
                  client.add_extension Streamforce::Extension::Logging.new(@logger)
         | 
| 69 61 | 
             
                end
         | 
| 70 62 | 
             
              end
         | 
| 71 63 |  | 
| 72 64 | 
             
              def subscribe_to_channels(client, channels, &blk)
         | 
| 73 65 | 
             
                return if channels.empty?
         | 
| 74 66 |  | 
| 75 | 
            -
                 | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 67 | 
            +
                # Subscribe to a single channel, otherwise Salesforce will return a 403 Unknown Client error
         | 
| 68 | 
            +
                subscription = client.subscribe(channels.shift)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                # Allow clients to receive [ channel, message ] block params
         | 
| 71 | 
            +
                subscription.with_channel(&blk)
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                # Continue subscribing to the rest of the channels, regadless of the current subscription
         | 
| 74 | 
            +
                # status
         | 
| 75 | 
            +
                subscription
         | 
| 76 | 
            +
                  .callback { subscribe_to_channels(client, channels, &blk) }
         | 
| 77 | 
            +
                  .errback { subscribe_to_channels(client, channels, &blk) }
         | 
| 78 78 | 
             
              end
         | 
| 79 79 | 
             
            end
         | 
| @@ -1,18 +1,97 @@ | |
| 1 1 | 
             
            class Streamforce::Extension::Logging
         | 
| 2 | 
            -
              def initialize( | 
| 3 | 
            -
                @logger =  | 
| 4 | 
            -
                @logger.level = log_level
         | 
| 2 | 
            +
              def initialize(logger)
         | 
| 3 | 
            +
                @logger = logger
         | 
| 5 4 | 
             
              end
         | 
| 6 5 |  | 
| 7 | 
            -
              def incoming( | 
| 8 | 
            -
                 | 
| 6 | 
            +
              def incoming(payload, callback)
         | 
| 7 | 
            +
                message = Streamforce::Message.new(payload)
         | 
| 8 | 
            +
                log_incoming_message(message)
         | 
| 9 9 |  | 
| 10 | 
            -
                callback.call( | 
| 10 | 
            +
                callback.call(payload)
         | 
| 11 11 | 
             
              end
         | 
| 12 12 |  | 
| 13 | 
            -
              def outgoing( | 
| 14 | 
            -
                 | 
| 13 | 
            +
              def outgoing(payload, callback)
         | 
| 14 | 
            +
                message = Streamforce::Message.new(payload)
         | 
| 15 | 
            +
                log_outgoing_message(message)
         | 
| 15 16 |  | 
| 16 | 
            -
                callback.call( | 
| 17 | 
            +
                callback.call(payload)
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def log_incoming_message(message)
         | 
| 21 | 
            +
                if message.channel_type == "meta"
         | 
| 22 | 
            +
                  public_send("log_incoming_#{message.channel_name}", message)
         | 
| 23 | 
            +
                else
         | 
| 24 | 
            +
                  @logger.debug "[#{message.channel_name}] #{message.data}"
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def log_outgoing_message(message)
         | 
| 29 | 
            +
                if message.channel_type == "meta"
         | 
| 30 | 
            +
                  public_send("log_outgoing_#{message.channel_name}", message)
         | 
| 31 | 
            +
                else
         | 
| 32 | 
            +
                  @logger.debug "[#{message.channel_name}] #{message.data}"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def log_outgoing_handshake(message)
         | 
| 37 | 
            +
                @logger.debug "[Client] Handshake requested..."
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def log_incoming_handshake(message)
         | 
| 41 | 
            +
                if message.success?
         | 
| 42 | 
            +
                  @logger.debug "[Server] Handshake accepted, assigning client_id=#{message.client_id}"
         | 
| 43 | 
            +
                else
         | 
| 44 | 
            +
                  @logger.error "[Server] Connection was refused: #{message.error_message}"
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              def log_outgoing_connect(message)
         | 
| 49 | 
            +
                debug message, "Sending connection request"
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def log_incoming_connect(message)
         | 
| 53 | 
            +
                if message.success?
         | 
| 54 | 
            +
                  debug message, "Connection successful!"
         | 
| 55 | 
            +
                else
         | 
| 56 | 
            +
                  error message, "Connection failed: #{message.error_message}"
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              def log_outgoing_subscribe(message)
         | 
| 61 | 
            +
                replay_id = message.replay_id
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                replay_info = if replay_id == -1
         | 
| 64 | 
            +
                  "for all new messages"
         | 
| 65 | 
            +
                elsif replay_id == -2
         | 
| 66 | 
            +
                  "and requesting all stored messages"
         | 
| 67 | 
            +
                else
         | 
| 68 | 
            +
                  "and requesting all messages newer than ##{replay_id}"
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                info message, "Subscribing to #{message.subscription} #{replay_info}"
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              def log_incoming_subscribe(message)
         | 
| 75 | 
            +
                if message.success?
         | 
| 76 | 
            +
                  info message, "Successfully subscribed to #{message.subscription}"
         | 
| 77 | 
            +
                else
         | 
| 78 | 
            +
                  error message, "Subscription for #{message.subscription} failed: #{message.error_message}"
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              def debug(message, text)
         | 
| 83 | 
            +
                @logger.debug "[#{message.client_id}##{message.id}] #{text}"
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              def info(message, text)
         | 
| 87 | 
            +
                @logger.info "[#{message.client_id}##{message.id}] #{text}"
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              def warn(message, text)
         | 
| 91 | 
            +
                @logger.warn "[#{message.client_id}##{message.id}] #{text}"
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              def error(message, text)
         | 
| 95 | 
            +
                @logger.error "[#{message.client_id}##{message.id}] #{text}"
         | 
| 17 96 | 
             
              end
         | 
| 18 97 | 
             
            end
         | 
| @@ -1,14 +1,36 @@ | |
| 1 | 
            +
            require "redis"
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            class Streamforce::Extension::Replay
         | 
| 2 4 | 
             
              def initialize(log_level = Logger::INFO)
         | 
| 3 5 | 
             
                @logger = Logger.new($stdout)
         | 
| 4 6 | 
             
                @logger.level = log_level
         | 
| 7 | 
            +
                @redis = Redis.new
         | 
| 5 8 | 
             
              end
         | 
| 6 9 |  | 
| 7 10 | 
             
              def incoming(message, callback)
         | 
| 11 | 
            +
                replay_id = message.dig "data", "event", "replayId"
         | 
| 12 | 
            +
                channel   = message["channel"]
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                store(channel, replay_id)
         | 
| 8 15 | 
             
                callback.call(message)
         | 
| 9 16 | 
             
              end
         | 
| 10 17 |  | 
| 11 18 | 
             
              def outgoing(message, callback)
         | 
| 19 | 
            +
                return callback.call(message) unless message["channel"] == "/meta/subscribe"
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                channel = message["subscription"]
         | 
| 22 | 
            +
                message["ext"] = { "replay" => { channel => replay_id(channel).to_i } }
         | 
| 23 | 
            +
             | 
| 12 24 | 
             
                callback.call(message)
         | 
| 13 25 | 
             
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              def store(channel, replay_id)
         | 
| 28 | 
            +
                return if channel.nil? || replay_id.nil?
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                @redis.set channel, replay_id, ex: 86400
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def replay_id(channel)
         | 
| 34 | 
            +
                @redis.get(channel) || -1
         | 
| 35 | 
            +
              end
         | 
| 14 36 | 
             
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            class Streamforce::Message
         | 
| 2 | 
            +
              def initialize(payload)
         | 
| 3 | 
            +
                @payload = payload
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def success?
         | 
| 7 | 
            +
                @payload["successful"]
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def id
         | 
| 11 | 
            +
                @payload["id"]
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def replay_id
         | 
| 15 | 
            +
                @payload.dig "ext", "replay", subscription
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def channel
         | 
| 19 | 
            +
                @payload["channel"]
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def client_id
         | 
| 23 | 
            +
                @payload["clientId"]
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def channel_type
         | 
| 27 | 
            +
                channel.split("/")[1]
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def channel_name
         | 
| 31 | 
            +
                channel.split("/")[2]
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def data
         | 
| 35 | 
            +
                @payload["data"]
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def subscription
         | 
| 39 | 
            +
                @payload["subscription"]
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def subscription?
         | 
| 43 | 
            +
                channel == "/meta/subscribe"
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def error_message
         | 
| 47 | 
            +
                @payload["error"]
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
    
        data/lib/streamforce/version.rb
    CHANGED
    
    
    
        data/lib/streamforce.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,17 +1,17 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: streamforce
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Andrei Maxim
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024-06- | 
| 11 | 
            +
            date: 2024-06-17 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name:  | 
| 14 | 
            +
              name: zeitwerk
         | 
| 15 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - ">="
         | 
| @@ -25,7 +25,7 @@ dependencies: | |
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 26 | 
             
                    version: '0'
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            -
              name:  | 
| 28 | 
            +
              name: faye
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - ">="
         | 
| @@ -39,7 +39,7 @@ dependencies: | |
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 40 | 
             
                    version: '0'
         | 
| 41 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            -
              name:  | 
| 42 | 
            +
              name: relaxed_cookiejar
         | 
| 43 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 44 | 
             
                requirements:
         | 
| 45 45 | 
             
                - - ">="
         | 
| @@ -53,7 +53,7 @@ dependencies: | |
| 53 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 54 54 | 
             
                    version: '0'
         | 
| 55 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            -
              name:  | 
| 56 | 
            +
              name: redis
         | 
| 57 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 58 | 
             
                requirements:
         | 
| 59 59 | 
             
                - - ">="
         | 
| @@ -66,7 +66,49 @@ dependencies: | |
| 66 66 | 
             
                - - ">="
         | 
| 67 67 | 
             
                  - !ruby/object:Gem::Version
         | 
| 68 68 | 
             
                    version: '0'
         | 
| 69 | 
            -
             | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rake
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '13.0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '13.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: minitest
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - ">="
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '0'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - ">="
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: rubocop-rails-omakase
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - ">="
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - ">="
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0'
         | 
| 111 | 
            +
            description:
         | 
| 70 112 | 
             
            email:
         | 
| 71 113 | 
             
            - andrei@andreimaxim.ro
         | 
| 72 114 | 
             
            executables: []
         | 
| @@ -82,7 +124,7 @@ files: | |
| 82 124 | 
             
            - lib/streamforce/client.rb
         | 
| 83 125 | 
             
            - lib/streamforce/extension/logging.rb
         | 
| 84 126 | 
             
            - lib/streamforce/extension/replay.rb
         | 
| 85 | 
            -
            - lib/streamforce/ | 
| 127 | 
            +
            - lib/streamforce/message.rb
         | 
| 86 128 | 
             
            - lib/streamforce/version.rb
         | 
| 87 129 | 
             
            homepage: https://github.com/andreimaxim/streamforce
         | 
| 88 130 | 
             
            licenses:
         | 
| @@ -91,7 +133,7 @@ metadata: | |
| 91 133 | 
             
              homepage_uri: https://github.com/andreimaxim/streamforce
         | 
| 92 134 | 
             
              source_code_uri: https://github.com/andreimaxim/streamforce
         | 
| 93 135 | 
             
              changelog_uri: https://github.com/andreimaxim/streamforce/blob/main/CHANGELOG.md
         | 
| 94 | 
            -
            post_install_message: | 
| 136 | 
            +
            post_install_message:
         | 
| 95 137 | 
             
            rdoc_options: []
         | 
| 96 138 | 
             
            require_paths:
         | 
| 97 139 | 
             
            - lib
         | 
| @@ -99,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 99 141 | 
             
              requirements:
         | 
| 100 142 | 
             
              - - ">="
         | 
| 101 143 | 
             
                - !ruby/object:Gem::Version
         | 
| 102 | 
            -
                  version: 3. | 
| 144 | 
            +
                  version: 3.0.0
         | 
| 103 145 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 104 146 | 
             
              requirements:
         | 
| 105 147 | 
             
              - - ">="
         | 
| @@ -107,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 107 149 | 
             
                  version: '0'
         | 
| 108 150 | 
             
            requirements: []
         | 
| 109 151 | 
             
            rubygems_version: 3.5.9
         | 
| 110 | 
            -
            signing_key: | 
| 152 | 
            +
            signing_key:
         | 
| 111 153 | 
             
            specification_version: 4
         | 
| 112 154 | 
             
            summary: Small wrapper over the Salesforce Streaming API
         | 
| 113 155 | 
             
            test_files: []
         | 
| @@ -1,58 +0,0 @@ | |
| 1 | 
            -
            class Streamforce::Extension::SubscriptionTracking
         | 
| 2 | 
            -
              def initialize(log_level = Logger::DEBUG)
         | 
| 3 | 
            -
                @logger = Logger.new($stdout)
         | 
| 4 | 
            -
                @logger.level = log_level
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                @subscriptions = []
         | 
| 7 | 
            -
              end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
              def incoming(message, callback)
         | 
| 10 | 
            -
                if subscription?(message)
         | 
| 11 | 
            -
                  log_subscription_status(message)
         | 
| 12 | 
            -
                else
         | 
| 13 | 
            -
                  log_subscription_payload(message)
         | 
| 14 | 
            -
                end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                callback.call(message)
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              def outgoing(message, callback)
         | 
| 20 | 
            -
                @logger.debug "Requested subscription for #{message["subscription"]}" if subscription?(message)
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                callback.call(message)
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
              def log_subscription_status(message)
         | 
| 26 | 
            -
                subscription = message["subscription"]
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                if message["successful"]
         | 
| 29 | 
            -
                  @subscriptions << subscription
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                  @logger.info "Subscription for #{subscription} was successful"
         | 
| 32 | 
            -
                else
         | 
| 33 | 
            -
                  @logger.warn "Subscription for #{subscription} failed: #{message["error"]}"
         | 
| 34 | 
            -
                end
         | 
| 35 | 
            -
              end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
              def log_subscription_payload(message)
         | 
| 38 | 
            -
                channel = message["channel"]
         | 
| 39 | 
            -
                payload = message["data"].to_json
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                type = case channel.split("/")[1]
         | 
| 42 | 
            -
                       when "topic"
         | 
| 43 | 
            -
                         "PushTopic"
         | 
| 44 | 
            -
                       when "event"
         | 
| 45 | 
            -
                         "PlatformEvent"
         | 
| 46 | 
            -
                       else
         | 
| 47 | 
            -
                         "Unknown"
         | 
| 48 | 
            -
                       end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                name = channel.split("/")[2]
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                @logger.info "[#{type}][#{name}]: #{payload}" if @subscriptions.include?(channel)
         | 
| 53 | 
            -
              end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
              def subscription?(message)
         | 
| 56 | 
            -
                message["channel"] == "/meta/subscribe"
         | 
| 57 | 
            -
              end
         | 
| 58 | 
            -
            end
         |