koala 2.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +32 -0
- data/Gemfile +5 -3
- data/ISSUE_TEMPLATE +25 -0
- data/PULL_REQUEST_TEMPLATE +11 -0
- data/changelog.md +161 -4
- data/code_of_conduct.md +64 -12
- data/koala.gemspec +5 -1
- data/lib/koala/api/batch_operation.rb +3 -6
- data/lib/koala/api/{graph_api.rb → graph_api_methods.rb} +29 -104
- data/lib/koala/api/graph_batch_api.rb +112 -65
- data/lib/koala/api/graph_collection.rb +19 -12
- data/lib/koala/api/graph_error_checker.rb +4 -3
- data/lib/koala/api.rb +65 -26
- data/lib/koala/configuration.rb +56 -0
- data/lib/koala/errors.rb +22 -2
- data/lib/koala/http_service/request.rb +133 -0
- data/lib/koala/http_service/response.rb +6 -4
- data/lib/koala/http_service/uploadable_io.rb +0 -5
- data/lib/koala/http_service.rb +29 -76
- data/lib/koala/oauth.rb +8 -8
- data/lib/koala/realtime_updates.rb +26 -21
- data/lib/koala/test_users.rb +9 -8
- data/lib/koala/version.rb +1 -1
- data/lib/koala.rb +7 -9
- data/readme.md +83 -109
- data/spec/cases/api_spec.rb +176 -69
- data/spec/cases/configuration_spec.rb +11 -0
- data/spec/cases/error_spec.rb +16 -3
- data/spec/cases/graph_api_batch_spec.rb +75 -44
- data/spec/cases/graph_api_spec.rb +15 -29
- data/spec/cases/graph_collection_spec.rb +47 -34
- data/spec/cases/graph_error_checker_spec.rb +31 -2
- data/spec/cases/http_service/request_spec.rb +250 -0
- data/spec/cases/http_service/response_spec.rb +24 -0
- data/spec/cases/http_service_spec.rb +126 -286
- data/spec/cases/koala_spec.rb +7 -5
- data/spec/cases/oauth_spec.rb +41 -2
- data/spec/cases/realtime_updates_spec.rb +51 -13
- data/spec/cases/test_users_spec.rb +56 -2
- data/spec/cases/uploadable_io_spec.rb +31 -31
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/facebook_data.yml +4 -6
- data/spec/fixtures/mock_facebook_responses.yml +41 -78
- data/spec/fixtures/vcr_cassettes/app_test_accounts.yml +97 -0
- data/spec/integration/graph_collection_spec.rb +8 -5
- data/spec/spec_helper.rb +2 -2
- data/spec/support/graph_api_shared_examples.rb +152 -337
- data/spec/support/koala_test.rb +11 -13
- data/spec/support/mock_http_service.rb +11 -14
- data/spec/support/uploadable_io_shared_examples.rb +4 -4
- metadata +47 -48
- data/.autotest +0 -12
- data/.travis.yml +0 -17
- data/Guardfile +0 -6
- data/autotest/discover.rb +0 -1
- data/lib/koala/api/rest_api.rb +0 -135
- data/lib/koala/http_service/multipart_request.rb +0 -41
- data/spec/cases/multipart_request_spec.rb +0 -65
- data/spec/support/rest_api_shared_examples.rb +0 -168
@@ -1,16 +1,18 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "koala/api"
|
2
|
+
require "koala/api/batch_operation"
|
3
3
|
|
4
4
|
module Koala
|
5
5
|
module Facebook
|
6
6
|
# @private
|
7
|
-
class GraphBatchAPI
|
7
|
+
class GraphBatchAPI
|
8
8
|
# inside a batch call we can do anything a regular Graph API can do
|
9
9
|
include GraphAPIMethods
|
10
10
|
|
11
|
+
# Limits from @see https://developers.facebook.com/docs/marketing-api/batch-requests/v2.8
|
12
|
+
MAX_CALLS = 50
|
13
|
+
|
11
14
|
attr_reader :original_api
|
12
15
|
def initialize(api)
|
13
|
-
super(api.access_token, api.app_secret)
|
14
16
|
@original_api = api
|
15
17
|
end
|
16
18
|
|
@@ -18,7 +20,9 @@ module Koala
|
|
18
20
|
@batch_calls ||= []
|
19
21
|
end
|
20
22
|
|
21
|
-
|
23
|
+
# Enqueue a call into the batch for later processing.
|
24
|
+
# See API#graph_call
|
25
|
+
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
22
26
|
# normalize options for consistency
|
23
27
|
options = Koala::Utils.symbolize_hash(options)
|
24
28
|
|
@@ -34,74 +38,117 @@ module Koala
|
|
34
38
|
nil # batch operations return nothing immediately
|
35
39
|
end
|
36
40
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
|
40
|
-
alias_method :graph_call, :graph_call_in_batch
|
41
|
-
|
42
|
-
# execute the queued batch calls
|
41
|
+
# execute the queued batch calls. limits it to 50 requests per call.
|
42
|
+
# NOTE: if you use `name` and JsonPath references, you should ensure to call `execute` for each
|
43
|
+
# co-reference group and that the group size is not greater than the above limits.
|
43
44
|
def execute(http_options = {})
|
44
|
-
return []
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
args
|
49
|
-
|
50
|
-
|
45
|
+
return [] if batch_calls.empty?
|
46
|
+
|
47
|
+
batch_results = []
|
48
|
+
batch_calls.each_slice(MAX_CALLS) do |batch|
|
49
|
+
# Turn the call args collected into what facebook expects
|
50
|
+
args = {"batch" => batch_args(batch)}
|
51
|
+
batch.each do |call|
|
52
|
+
args.merge!(call.files || {})
|
53
|
+
end
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
raise BadFacebookResponse.new(200, '', "Facebook returned an empty body")
|
55
|
+
original_api.graph_call("/", args, "post", http_options) do |response|
|
56
|
+
raise bad_response if response.nil?
|
57
|
+
|
58
|
+
batch_results += generate_results(response, batch)
|
57
59
|
end
|
60
|
+
end
|
61
|
+
|
62
|
+
batch_results
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_results(response, batch)
|
66
|
+
index = 0
|
67
|
+
response.map do |call_result|
|
68
|
+
batch_op = batch[index]
|
69
|
+
index += 1
|
70
|
+
post_process = batch_op.post_processing
|
58
71
|
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if call_result
|
68
|
-
parsed_headers = if call_result.has_key?('headers')
|
69
|
-
call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
|
70
|
-
else
|
71
|
-
{}
|
72
|
-
end
|
73
|
-
|
74
|
-
if (error = check_response(call_result['code'], call_result['body'].to_s, parsed_headers))
|
75
|
-
raw_result = error
|
76
|
-
else
|
77
|
-
# (see note in regular api method about JSON parsing)
|
78
|
-
body = MultiJson.load("[#{call_result['body'].to_s}]")[0]
|
79
|
-
|
80
|
-
# Get the HTTP component they want
|
81
|
-
raw_result = case batch_op.http_options[:http_component]
|
82
|
-
when :status
|
83
|
-
call_result["code"].to_i
|
84
|
-
when :headers
|
85
|
-
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
86
|
-
parsed_headers
|
87
|
-
else
|
88
|
-
body
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# turn any results that are pageable into GraphCollections
|
94
|
-
# and pass to post-processing callback if given
|
95
|
-
result = GraphCollection.evaluate(raw_result, @original_api)
|
96
|
-
if batch_op.post_processing
|
97
|
-
batch_op.post_processing.call(result)
|
98
|
-
else
|
99
|
-
result
|
100
|
-
end
|
72
|
+
# turn any results that are pageable into GraphCollections
|
73
|
+
result = result_from_response(call_result, batch_op)
|
74
|
+
|
75
|
+
# and pass to post-processing callback if given
|
76
|
+
if post_process
|
77
|
+
post_process.call(result)
|
78
|
+
else
|
79
|
+
result
|
101
80
|
end
|
102
81
|
end
|
103
82
|
end
|
104
83
|
|
84
|
+
def bad_response
|
85
|
+
# Facebook sometimes reportedly returns an empty body at times
|
86
|
+
BadFacebookResponse.new(200, "", "Facebook returned an empty body")
|
87
|
+
end
|
88
|
+
|
89
|
+
def result_from_response(response, options)
|
90
|
+
return nil if response.nil?
|
91
|
+
|
92
|
+
headers = headers_from_response(response)
|
93
|
+
error = error_from_response(response, headers)
|
94
|
+
component = options.http_options[:http_component]
|
95
|
+
|
96
|
+
error || desired_component(
|
97
|
+
component: component,
|
98
|
+
response: response,
|
99
|
+
headers: headers
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def headers_from_response(response)
|
104
|
+
headers = response.fetch("headers", [])
|
105
|
+
|
106
|
+
headers.inject({}) do |compiled_headers, header|
|
107
|
+
compiled_headers.merge(header.fetch("name") => header.fetch("value"))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def error_from_response(response, headers)
|
112
|
+
code = response["code"]
|
113
|
+
body = response["body"].to_s
|
114
|
+
|
115
|
+
GraphErrorChecker.new(code, body, headers).error_if_appropriate
|
116
|
+
end
|
117
|
+
|
118
|
+
def batch_args(calls_for_batch)
|
119
|
+
calls = calls_for_batch.map do |batch_op|
|
120
|
+
batch_op.to_batch_params(access_token, app_secret)
|
121
|
+
end
|
122
|
+
|
123
|
+
JSON.dump calls
|
124
|
+
end
|
125
|
+
|
126
|
+
def json_body(response)
|
127
|
+
# quirks_mode is needed because Facebook sometimes returns a raw true or false value --
|
128
|
+
# in Ruby 2.4 we can drop that.
|
129
|
+
JSON.parse(response.fetch("body"), quirks_mode: true)
|
130
|
+
end
|
131
|
+
|
132
|
+
def desired_component(component:, response:, headers:)
|
133
|
+
result = Koala::HTTPService::Response.new(response['status'], response['body'], headers)
|
134
|
+
|
135
|
+
# Get the HTTP component they want
|
136
|
+
case component
|
137
|
+
when :status then response["code"].to_i
|
138
|
+
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
139
|
+
when :headers then headers
|
140
|
+
# (see note in regular api method about JSON parsing)
|
141
|
+
else GraphCollection.evaluate(result, original_api)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def access_token
|
146
|
+
original_api.access_token
|
147
|
+
end
|
148
|
+
|
149
|
+
def app_secret
|
150
|
+
original_api.app_secret
|
151
|
+
end
|
105
152
|
end
|
106
153
|
end
|
107
154
|
end
|
@@ -16,30 +16,41 @@ module Koala
|
|
16
16
|
attr_reader :api
|
17
17
|
# The entire raw response from Facebook.
|
18
18
|
attr_reader :raw_response
|
19
|
+
# The headers from the Facebook response
|
20
|
+
attr_reader :headers
|
19
21
|
|
20
22
|
# Initialize the array of results and store various additional paging-related information.
|
21
23
|
#
|
22
|
-
# @param
|
24
|
+
# @param [Koala::HTTPService::Response] response object wrapping the raw Facebook response
|
23
25
|
# @param api the Graph {Koala::Facebook::API API} instance to use to make calls
|
24
26
|
# (usually the API that made the original call).
|
25
27
|
#
|
26
|
-
# @return [Koala::Facebook::GraphCollection] an initialized GraphCollection
|
28
|
+
# @return [Koala::Facebook::API::GraphCollection] an initialized GraphCollection
|
27
29
|
# whose paging, summary, raw_response, and api attributes are populated.
|
28
30
|
def initialize(response, api)
|
29
|
-
super response["data"]
|
30
|
-
@paging = response["paging"]
|
31
|
-
@summary = response["summary"]
|
32
|
-
@raw_response = response
|
31
|
+
super response.data["data"]
|
32
|
+
@paging = response.data["paging"]
|
33
|
+
@summary = response.data["summary"]
|
34
|
+
@raw_response = response.data
|
33
35
|
@api = api
|
36
|
+
@headers = response.headers
|
34
37
|
end
|
35
38
|
|
36
39
|
# @private
|
37
40
|
# Turn the response into a GraphCollection if they're pageable;
|
38
|
-
# if not, return the original response.
|
41
|
+
# if not, return the data of the original response.
|
39
42
|
# The Ads API (uniquely so far) returns a hash rather than an array when queried
|
40
43
|
# with get_connections.
|
41
44
|
def self.evaluate(response, api)
|
42
|
-
|
45
|
+
return nil if response.nil?
|
46
|
+
|
47
|
+
is_pageable?(response) ? self.new(response, api) : response.data
|
48
|
+
end
|
49
|
+
|
50
|
+
# response will always be an instance of Koala::HTTPService::Response
|
51
|
+
# since that is what we get from Koala::Facebook::API#api
|
52
|
+
def self.is_pageable?(response)
|
53
|
+
response.data.is_a?(Hash) && response.data["data"].is_a?(Array)
|
43
54
|
end
|
44
55
|
|
45
56
|
# Retrieve the next page of results.
|
@@ -113,9 +124,5 @@ module Koala
|
|
113
124
|
end
|
114
125
|
end
|
115
126
|
end
|
116
|
-
|
117
|
-
# @private
|
118
|
-
# legacy support for when GraphCollection lived directly under Koala::Facebook
|
119
|
-
GraphCollection = API::GraphCollection
|
120
127
|
end
|
121
128
|
end
|
@@ -17,7 +17,7 @@ module Koala
|
|
17
17
|
|
18
18
|
# Facebook can return debug information in the response headers -- see
|
19
19
|
# https://developers.facebook.com/docs/graph-api/using-graph-api#bugdebug
|
20
|
-
DEBUG_HEADERS = [
|
20
|
+
DEBUG_HEADERS = %w[x-fb-debug x-fb-rev x-fb-trace-id x-business-use-case-usage x-ad-account-usage x-app-usage]
|
21
21
|
|
22
22
|
def error_if_appropriate
|
23
23
|
if http_status >= 400
|
@@ -61,8 +61,9 @@ module Koala
|
|
61
61
|
# Normally, we start with the response body. If it isn't valid JSON, we start with an empty
|
62
62
|
# hash and fill it with error data.
|
63
63
|
@response_hash ||= begin
|
64
|
-
|
65
|
-
|
64
|
+
parsed_body = JSON.parse(body)
|
65
|
+
parsed_body.is_a?(Hash) ? parsed_body : {}
|
66
|
+
rescue JSON::ParserError
|
66
67
|
{}
|
67
68
|
end
|
68
69
|
end
|
data/lib/koala/api.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# graph_batch_api and legacy are required at the bottom, since they depend on API being defined
|
2
|
-
require 'koala/api/
|
3
|
-
require 'koala/api/
|
2
|
+
require 'koala/api/graph_api_methods'
|
3
|
+
require 'koala/api/graph_collection'
|
4
4
|
require 'openssl'
|
5
5
|
|
6
6
|
module Koala
|
@@ -13,23 +13,74 @@ module Koala
|
|
13
13
|
# signed by default, unless you pass appsecret_proof:
|
14
14
|
# false as an option to the API call. (See
|
15
15
|
# https://developers.facebook.com/docs/graph-api/securing-requests/)
|
16
|
+
# @param [Block] rate_limit_hook block called with limits received in facebook response headers
|
16
17
|
# @note If no access token is provided, you can only access some public information.
|
17
18
|
# @return [Koala::Facebook::API] the API client
|
18
|
-
def initialize(access_token =
|
19
|
+
def initialize(access_token = Koala.config.access_token, app_secret = Koala.config.app_secret, rate_limit_hook = Koala.config.rate_limit_hook)
|
19
20
|
@access_token = access_token
|
20
21
|
@app_secret = app_secret
|
22
|
+
@rate_limit_hook = rate_limit_hook
|
21
23
|
end
|
22
24
|
|
23
|
-
attr_reader :access_token, :app_secret
|
25
|
+
attr_reader :access_token, :app_secret, :rate_limit_hook
|
24
26
|
|
25
27
|
include GraphAPIMethods
|
26
|
-
|
28
|
+
|
29
|
+
# Make a call directly to the Graph API.
|
30
|
+
# (See any of the other methods for example invocations.)
|
31
|
+
#
|
32
|
+
# @param path the Graph API path to query (no leading / needed)
|
33
|
+
# @param args (see #get_object)
|
34
|
+
# @param verb the type of HTTP request to make (get, post, delete, etc.)
|
35
|
+
# @options (see #get_object)
|
36
|
+
#
|
37
|
+
# @yield response when making a batch API call, you can pass in a block
|
38
|
+
# that parses the results, allowing for cleaner code.
|
39
|
+
# The block's return value is returned in the batch results.
|
40
|
+
# See the code for {#get_picture} for examples.
|
41
|
+
# (Not needed in regular calls; you'll probably rarely use this.)
|
42
|
+
#
|
43
|
+
# @raise [Koala::Facebook::APIError] if Facebook returns an error
|
44
|
+
#
|
45
|
+
# @return the result from Facebook
|
46
|
+
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
47
|
+
# enable appsecret_proof by default
|
48
|
+
options = {:appsecret_proof => true}.merge(options) if @app_secret
|
49
|
+
response = api(path, args, verb, options)
|
50
|
+
|
51
|
+
error = GraphErrorChecker.new(response.status, response.body, response.headers).error_if_appropriate
|
52
|
+
raise error if error
|
53
|
+
|
54
|
+
# if we want a component other than the body (e.g. redirect header for images), provide that
|
55
|
+
http_component = options[:http_component]
|
56
|
+
desired_data = if options[:http_component]
|
57
|
+
http_component == :response ? response : response.send(http_component)
|
58
|
+
else
|
59
|
+
# turn this into a GraphCollection if it's pageable
|
60
|
+
API::GraphCollection.evaluate(response, self)
|
61
|
+
end
|
62
|
+
|
63
|
+
if rate_limit_hook
|
64
|
+
limits = %w(x-business-use-case-usage x-ad-account-usage x-app-usage).each_with_object({}) do |key, hash|
|
65
|
+
value = response.headers.fetch(key, nil)
|
66
|
+
next unless value
|
67
|
+
hash[key] = JSON.parse(response.headers[key])
|
68
|
+
rescue JSON::ParserError => e
|
69
|
+
Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{key} = #{value}")
|
70
|
+
end
|
71
|
+
|
72
|
+
rate_limit_hook.call(limits) if limits.keys.any?
|
73
|
+
end
|
74
|
+
|
75
|
+
# now process as appropriate for the given call (get picture header, etc.)
|
76
|
+
post_processing ? post_processing.call(desired_data) : desired_data
|
77
|
+
end
|
78
|
+
|
27
79
|
|
28
80
|
# Makes a request to the appropriate Facebook API.
|
29
81
|
# @note You'll rarely need to call this method directly.
|
30
82
|
#
|
31
83
|
# @see GraphAPIMethods#graph_call
|
32
|
-
# @see RestAPIMethods#rest_call
|
33
84
|
#
|
34
85
|
# @param path the server path for this request (leading / is prepended if not present)
|
35
86
|
# @param args arguments to be sent to Facebook
|
@@ -44,14 +95,10 @@ module Koala
|
|
44
95
|
# @option options [Boolean] :beta use Facebook's beta tier
|
45
96
|
# @option options [Boolean] :use_ssl force SSL for this request, even if it's tokenless.
|
46
97
|
# (All API requests with access tokens use SSL.)
|
47
|
-
# @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
|
48
|
-
#
|
49
|
-
# @yield The response for evaluation
|
50
|
-
#
|
51
98
|
# @raise [Koala::Facebook::ServerError] if Facebook returns an error (response status >= 500)
|
52
99
|
#
|
53
|
-
# @return
|
54
|
-
def api(path, args = {}, verb = "get", options = {}
|
100
|
+
# @return a Koala::HTTPService::Response object representing the returned Facebook data
|
101
|
+
def api(path, args = {}, verb = "get", options = {})
|
55
102
|
# we make a copy of args so the modifications (added access_token & appsecret_proof)
|
56
103
|
# do not affect the received argument
|
57
104
|
args = args.dup
|
@@ -60,6 +107,7 @@ module Koala
|
|
60
107
|
# This is explicitly needed in batch requests so GraphCollection
|
61
108
|
# results preserve any specific access tokens provided
|
62
109
|
args["access_token"] ||= @access_token || @app_access_token if @access_token || @app_access_token
|
110
|
+
|
63
111
|
if options.delete(:appsecret_proof) && args["access_token"] && @app_secret
|
64
112
|
args["appsecret_proof"] = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), @app_secret, args["access_token"])
|
65
113
|
end
|
@@ -68,7 +116,7 @@ module Koala
|
|
68
116
|
args = sanitize_request_parameters(args) unless preserve_form_arguments?(options)
|
69
117
|
|
70
118
|
# add a leading / if needed...
|
71
|
-
path = "/#{path}" unless path =~ /^\//
|
119
|
+
path = "/#{path}" unless path.to_s =~ /^\//
|
72
120
|
|
73
121
|
# make the request via the provided service
|
74
122
|
result = Koala.make_request(path, args, verb, options)
|
@@ -77,17 +125,7 @@ module Koala
|
|
77
125
|
raise Koala::Facebook::ServerError.new(result.status.to_i, result.body)
|
78
126
|
end
|
79
127
|
|
80
|
-
|
81
|
-
|
82
|
-
# if we want a component other than the body (e.g. redirect header for images), return that
|
83
|
-
if component = options[:http_component]
|
84
|
-
component == :response ? result : result.send(options[:http_component])
|
85
|
-
else
|
86
|
-
# parse the body as JSON and run it through the error checker (if provided)
|
87
|
-
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
88
|
-
# and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
|
89
|
-
MultiJson.load("[#{result.body.to_s}]")[0]
|
90
|
-
end
|
128
|
+
result
|
91
129
|
end
|
92
130
|
|
93
131
|
private
|
@@ -113,8 +151,9 @@ module Koala
|
|
113
151
|
def preserve_form_arguments?(options)
|
114
152
|
options[:format] == :json || options[:preserve_form_arguments] || Koala.config.preserve_form_arguments
|
115
153
|
end
|
154
|
+
|
155
|
+
def check_response(http_status, body, headers)
|
156
|
+
end
|
116
157
|
end
|
117
158
|
end
|
118
159
|
end
|
119
|
-
|
120
|
-
require 'koala/api/graph_batch_api'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Global configuration for Koala.
|
2
|
+
class Koala::Configuration
|
3
|
+
# The default access token to be used if none is otherwise supplied.
|
4
|
+
attr_accessor :access_token
|
5
|
+
|
6
|
+
# The default app secret value to be used if none is otherwise supplied.
|
7
|
+
attr_accessor :app_secret
|
8
|
+
|
9
|
+
# The default application ID to use if none is otherwise supplied.
|
10
|
+
attr_accessor :app_id
|
11
|
+
|
12
|
+
# The default app access token to be used if none is otherwise supplied.
|
13
|
+
attr_accessor :app_access_token
|
14
|
+
|
15
|
+
# The default API version to use if none is otherwise specified.
|
16
|
+
attr_accessor :api_version
|
17
|
+
|
18
|
+
# The default value to use for the oauth_callback_url if no other is provided.
|
19
|
+
attr_accessor :oauth_callback_url
|
20
|
+
|
21
|
+
# Whether to preserve arrays in arguments, which are expected by certain FB APIs (see the ads API
|
22
|
+
# in particular, https://developers.facebook.com/docs/marketing-api/adgroup/v2.4)
|
23
|
+
attr_accessor :preserve_form_arguments
|
24
|
+
|
25
|
+
# The server to use for Graph API requests
|
26
|
+
attr_accessor :graph_server
|
27
|
+
|
28
|
+
# The server to use when constructing dialog URLs.
|
29
|
+
attr_accessor :dialog_host
|
30
|
+
|
31
|
+
# Whether or not to mask tokens
|
32
|
+
attr_accessor :mask_tokens
|
33
|
+
|
34
|
+
# Called with the info for the rate limits in the response header
|
35
|
+
attr_accessor :rate_limit_hook
|
36
|
+
|
37
|
+
# Certain Facebook services (beta, video) require you to access different
|
38
|
+
# servers. If you're using your own servers, for instance, for a proxy,
|
39
|
+
# you can change both the matcher (what value to change when updating the URL) and the
|
40
|
+
# replacement values (what to add).
|
41
|
+
#
|
42
|
+
# So, for instance, to use the beta stack, we match on .facebook and change it to .beta.facebook.
|
43
|
+
# If you're talking to fbproxy.mycompany.com, you could set up beta.fbproxy.mycompany.com for
|
44
|
+
# FB's beta tier, and set the matcher to /\.fbproxy/ and the beta_replace to '.beta.fbproxy'.
|
45
|
+
attr_accessor :host_path_matcher
|
46
|
+
attr_accessor :video_replace
|
47
|
+
attr_accessor :beta_replace
|
48
|
+
|
49
|
+
def initialize
|
50
|
+
# Default to our default values.
|
51
|
+
Koala::HTTPService::DEFAULT_SERVERS.each_pair do |key, value|
|
52
|
+
self.public_send("#{key}=", value)
|
53
|
+
end
|
54
|
+
self.mask_tokens = true
|
55
|
+
end
|
56
|
+
end
|
data/lib/koala/errors.rb
CHANGED
@@ -23,7 +23,10 @@ module Koala
|
|
23
23
|
:fb_error_user_title,
|
24
24
|
:fb_error_trace_id,
|
25
25
|
:fb_error_debug,
|
26
|
-
:fb_error_rev
|
26
|
+
:fb_error_rev,
|
27
|
+
:fb_buc_usage,
|
28
|
+
:fb_ada_usage,
|
29
|
+
:fb_app_usage
|
27
30
|
|
28
31
|
# Create a new API Error
|
29
32
|
#
|
@@ -50,7 +53,7 @@ module Koala
|
|
50
53
|
else
|
51
54
|
unless error_info
|
52
55
|
begin
|
53
|
-
error_info =
|
56
|
+
error_info = JSON.parse(response_body)['error'] if response_body
|
54
57
|
rescue
|
55
58
|
end
|
56
59
|
error_info ||= {}
|
@@ -66,6 +69,9 @@ module Koala
|
|
66
69
|
self.fb_error_trace_id = error_info["x-fb-trace-id"]
|
67
70
|
self.fb_error_debug = error_info["x-fb-debug"]
|
68
71
|
self.fb_error_rev = error_info["x-fb-rev"]
|
72
|
+
self.fb_buc_usage = json_parse_for(error_info, "x-business-use-case-usage")
|
73
|
+
self.fb_ada_usage = json_parse_for(error_info, "x-ad-account-usage")
|
74
|
+
self.fb_app_usage = json_parse_for(error_info, "x-app-usage")
|
69
75
|
|
70
76
|
error_array = []
|
71
77
|
%w(type code error_subcode message error_user_title error_user_msg x-fb-trace-id).each do |key|
|
@@ -82,6 +88,20 @@ module Koala
|
|
82
88
|
|
83
89
|
super(message)
|
84
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# refs: https://developers.facebook.com/docs/graph-api/overview/rate-limiting/#headers
|
95
|
+
# NOTE: The header will contain a JSON-formatted string that describes current application rate limit usage.
|
96
|
+
def json_parse_for(error_info, key)
|
97
|
+
string = error_info[key]
|
98
|
+
return if string.nil?
|
99
|
+
|
100
|
+
JSON.parse(string)
|
101
|
+
rescue JSON::ParserError => e
|
102
|
+
Koala::Utils.logger.error("#{e.class}: #{e.message} while parsing #{key} = #{string}")
|
103
|
+
nil
|
104
|
+
end
|
85
105
|
end
|
86
106
|
|
87
107
|
# Facebook returned an invalid response body
|