class Facebook::Messenger::Server

This module holds the server that processes incoming messages from the Facebook Messenger Platform.

Public Class Methods

call(env) click to toggle source
# File lib/facebook/messenger/server.rb, line 20
def self.call(env)
  new.call(env)
end

Public Instance Methods

call(env) click to toggle source

Rack handler for request.

# File lib/facebook/messenger/server.rb, line 25
def call(env)
  @request = Rack::Request.new(env)
  @response = Rack::Response.new

  if @request.get?
    verify
  elsif @request.post?
    receive
  else
    @response.status = 405
  end

  @response.finish
end

Private Instance Methods

app_secret_for(facebook_page_id) click to toggle source

Returns a String describing the bot's configured app secret.

# File lib/facebook/messenger/server.rb, line 125
def app_secret_for(facebook_page_id)
  Facebook::Messenger.config.provider.app_secret_for(facebook_page_id)
end
body() click to toggle source

Returns a String describing the request body.

# File lib/facebook/messenger/server.rb, line 135
def body
  @body ||= @request.body.read
end
check_integrity() click to toggle source

Check the integrity of the request. @see developers.facebook.com/docs/messenger-platform/webhook#security

@raise BadRequestError if the request has been tampered with.

# File lib/facebook/messenger/server.rb, line 74
def check_integrity
  # If app secret is not found in environment, return.
  # So for the security purpose always add provision in
  #   configuration provider to return app secret.
  return unless app_secret_for(parsed_body['entry'][0]['id'])

  unless signature.start_with?('sha1='.freeze)
    warn X_HUB_SIGNATURE_MISSING_WARNING

    raise BadRequestError, 'Error getting integrity signature'.freeze
  end

  raise BadRequestError, 'Error checking message integrity'.freeze \
    unless valid_signature?
end
generate_hmac(content) click to toggle source

Generate a HMAC signature for the given content.

# File lib/facebook/messenger/server.rb, line 115
def generate_hmac(content)
  content_json = JSON.parse(content, symbolize_names: true)
  facebook_page_id = content_json[:entry][0][:id]

  OpenSSL::HMAC.hexdigest('sha1'.freeze,
                          app_secret_for(facebook_page_id),
                          content)
end
parsed_body() click to toggle source

Returns a Hash describing the parsed request body. @raise JSON::ParserError if body hash is not valid.

@return [JSON] Parsed body hash.

# File lib/facebook/messenger/server.rb, line 145
def parsed_body
  @parsed_body ||= JSON.parse(body)
rescue JSON::ParserError
  raise BadRequestError, 'Error parsing request body format'
end
receive() click to toggle source

Function handles the webhook events. @raise BadRequestError if the request is tampered.

# File lib/facebook/messenger/server.rb, line 60
def receive
  check_integrity

  trigger(parsed_body)
rescue BadRequestError => error
  respond_with_error(error)
end
respond_with_error(error) click to toggle source

If received request is tampered, sent 400 code in response.

@param [Object] error Error object.

# File lib/facebook/messenger/server.rb, line 177
def respond_with_error(error)
  @response.status = 400
  @response.write(error.message)
  @response.headers['Content-Type'.freeze] = 'text/plain'.freeze
end
signature() click to toggle source

Returns a String describing the X-Hub-Signature header.

# File lib/facebook/messenger/server.rb, line 91
def signature
  @request.env['HTTP_X_HUB_SIGNATURE'.freeze].to_s
end
signature_for(string) click to toggle source

Sign the given string.

@return [String] A string describing its signature.

# File lib/facebook/messenger/server.rb, line 110
def signature_for(string)
  format('sha1=%<string>s'.freeze, string: generate_hmac(string))
end
trigger(events) click to toggle source

Function hand over the webhook event to handlers.

@param [Hash] events Parsed body hash in webhook event.

# File lib/facebook/messenger/server.rb, line 156
def trigger(events)
  # Facebook may batch several items in the 'entry' array during
  # periods of high load.
  events['entry'.freeze].each do |entry|
    # If the application has subscribed to webhooks other than Messenger,
    # 'messaging' won't be available and it is not relevant to us.
    next unless entry['messaging'.freeze]

    # Facebook may batch several items in the 'messaging' array during
    # periods of high load.
    entry['messaging'.freeze].each do |messaging|
      Facebook::Messenger::Bot.receive(messaging)
    end
  end
end
valid_signature?() click to toggle source

Verify that the signature given in the X-Hub-Signature header matches that of the body.

@return [Boolean] true if request is valid else false.

# File lib/facebook/messenger/server.rb, line 101
def valid_signature?
  Rack::Utils.secure_compare(signature, signature_for(body))
end
valid_verify_token?(token) click to toggle source

Checks whether a verify token is valid.

# File lib/facebook/messenger/server.rb, line 130
def valid_verify_token?(token)
  Facebook::Messenger.config.provider.valid_verify_token?(token)
end
verify() click to toggle source

Function validates the verification request which is sent by Facebook

to validate the entered endpoint.

@see developers.facebook.com/docs/graph-api/webhooks#callback

# File lib/facebook/messenger/server.rb, line 48
def verify
  if valid_verify_token?(@request.params['hub.verify_token'])
    @response.write @request.params['hub.challenge']
  else
    @response.write 'Error; wrong verify token'
  end
end