koala 1.2.1 → 1.3.0rc2

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.
Files changed (55) hide show
  1. data/.gitignore +3 -1
  2. data/.rspec +1 -0
  3. data/.travis.yml +4 -0
  4. data/.yardopts +3 -0
  5. data/CHANGELOG +25 -0
  6. data/Gemfile +14 -0
  7. data/Guardfile +6 -0
  8. data/koala.gemspec +3 -3
  9. data/lib/koala/api/batch_operation.rb +83 -0
  10. data/lib/koala/api/graph_api.rb +476 -0
  11. data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +22 -17
  12. data/lib/koala/api/graph_collection.rb +107 -0
  13. data/lib/koala/api/legacy.rb +26 -0
  14. data/lib/koala/{rest_api.rb → api/rest_api.rb} +32 -13
  15. data/lib/koala/api.rb +93 -0
  16. data/lib/koala/http_service/multipart_request.rb +41 -0
  17. data/lib/koala/http_service/response.rb +18 -0
  18. data/lib/koala/http_service/uploadable_io.rb +187 -0
  19. data/lib/koala/http_service.rb +69 -20
  20. data/lib/koala/oauth.rb +150 -27
  21. data/lib/koala/realtime_updates.rb +89 -51
  22. data/lib/koala/test_users.rb +109 -33
  23. data/lib/koala/utils.rb +11 -4
  24. data/lib/koala/version.rb +1 -1
  25. data/lib/koala.rb +16 -96
  26. data/readme.md +9 -9
  27. data/spec/cases/api_spec.rb +19 -12
  28. data/spec/cases/error_spec.rb +10 -0
  29. data/spec/cases/graph_api_batch_spec.rb +100 -58
  30. data/spec/cases/graph_collection_spec.rb +23 -7
  31. data/spec/cases/http_service_spec.rb +5 -26
  32. data/spec/cases/koala_spec.rb +22 -4
  33. data/spec/cases/legacy_spec.rb +107 -0
  34. data/spec/cases/multipart_request_spec.rb +7 -7
  35. data/spec/cases/oauth_spec.rb +114 -44
  36. data/spec/cases/realtime_updates_spec.rb +154 -47
  37. data/spec/cases/test_users_spec.rb +268 -219
  38. data/spec/cases/uploadable_io_spec.rb +1 -1
  39. data/spec/cases/utils_spec.rb +29 -5
  40. data/spec/fixtures/mock_facebook_responses.yml +40 -29
  41. data/spec/spec_helper.rb +3 -0
  42. data/spec/support/custom_matchers.rb +28 -0
  43. data/spec/support/graph_api_shared_examples.rb +192 -14
  44. data/spec/support/koala_test.rb +10 -1
  45. data/spec/support/mock_http_service.rb +2 -2
  46. data/spec/support/rest_api_shared_examples.rb +5 -165
  47. metadata +76 -100
  48. data/lib/koala/batch_operation.rb +0 -74
  49. data/lib/koala/graph_api.rb +0 -270
  50. data/lib/koala/graph_collection.rb +0 -59
  51. data/lib/koala/multipart_request.rb +0 -35
  52. data/lib/koala/uploadable_io.rb +0 -181
  53. data/spec/cases/graph_and_rest_api_spec.rb +0 -22
  54. data/spec/cases/graph_api_spec.rb +0 -22
  55. data/spec/cases/rest_api_spec.rb +0 -22
@@ -1,30 +1,37 @@
1
1
  require 'faraday'
2
+ require 'koala/http_service/multipart_request'
3
+ require 'koala/http_service/uploadable_io'
4
+ require 'koala/http_service/response'
2
5
 
3
6
  module Koala
4
- class Response
5
- attr_reader :status, :body, :headers
6
- def initialize(status, body, headers)
7
- @status = status
8
- @body = body
9
- @headers = headers
10
- end
11
- end
12
-
13
- module HTTPService
14
- # common functionality for all HTTP services
15
-
7
+ module HTTPService
16
8
  class << self
17
- attr_accessor :faraday_middleware, :http_options
9
+ # A customized stack of Faraday middleware that will be used to make each request.
10
+ attr_accessor :faraday_middleware
11
+ # A default set of HTTP options (see https://github.com/arsduo/koala/wiki/HTTP-Services)
12
+ attr_accessor :http_options
18
13
  end
19
14
 
20
15
  @http_options ||= {}
21
16
 
17
+ # Koala's default middleware stack.
18
+ # We encode requests in a Facebook-compatible multipart request,
19
+ # and use whichever adapter has been configured for this application.
22
20
  DEFAULT_MIDDLEWARE = Proc.new do |builder|
23
- builder.use Koala::MultipartRequest
21
+ builder.use Koala::HTTPService::MultipartRequest
24
22
  builder.request :url_encoded
25
23
  builder.adapter Faraday.default_adapter
26
24
  end
27
25
 
26
+ # The address of the appropriate Facebook server.
27
+ #
28
+ # @param options various flags to indicate which server to use.
29
+ # @option options :rest_api use the old REST API instead of the Graph API
30
+ # @option options :video use the server designated for video uploads
31
+ # @option options :beta use the beta tier
32
+ # @option options :use_ssl force https, even if not needed
33
+ #
34
+ # @return a complete server address with protocol
28
35
  def self.server(options = {})
29
36
  server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
30
37
  server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
@@ -32,6 +39,23 @@ module Koala
32
39
  "#{options[:use_ssl] ? "https" : "http"}://#{server}"
33
40
  end
34
41
 
42
+ # Makes a request directly to Facebook.
43
+ # @note You'll rarely need to call this method directly.
44
+ #
45
+ # @see Koala::Facebook::API#api
46
+ # @see Koala::Facebook::GraphAPIMethods#graph_call
47
+ # @see Koala::Facebook::RestAPIMethods#rest_call
48
+ #
49
+ # @param path the server path for this request
50
+ # @param args (see Koala::Facebook::API#api)
51
+ # @param verb the HTTP method to use.
52
+ # If not get or post, this will be turned into a POST request with the appropriate :method
53
+ # specified in the arguments.
54
+ # @param options (see Koala::Facebook::API#api)
55
+ #
56
+ # @raise an appropriate connection error if unable to make the request to Facebook
57
+ #
58
+ # @return [Koala::HTTPService::Response] a response object representing the results from Facebook
35
59
  def self.make_request(path, args, verb, options = {})
36
60
  # if the verb isn't get or post, send it as a post argument
37
61
  args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
@@ -48,13 +72,20 @@ module Koala
48
72
  conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
49
73
 
50
74
  response = conn.send(verb, path, (verb == "post" ? params : {}))
51
- Koala::Response.new(response.status.to_i, response.body, response.headers)
52
- end
53
-
75
+ Koala::HTTPService::Response.new(response.status.to_i, response.body, response.headers)
76
+ end
77
+
78
+ # Encodes a given hash into a query string.
79
+ # This is used mainly by the Batch API nowadays, since Faraday handles this for regular cases.
80
+ #
81
+ # @param params_hash a hash of values to CGI-encode and appropriately join
82
+ #
83
+ # @example
84
+ # Koala.http_service.encode_params({:a => 2, :b => "My String"})
85
+ # => "a=2&b=My+String"
86
+ #
87
+ # @return the appropriately-encoded string
54
88
  def self.encode_params(param_hash)
55
- # unfortunately, we can't use to_query because that's Rails, not Ruby
56
- # if no hash (e.g. no auth token) return empty string
57
- # this is used mainly by the Batch API nowadays
58
89
  ((param_hash || {}).collect do |key_and_value|
59
90
  key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
60
91
  "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
@@ -64,76 +95,92 @@ module Koala
64
95
  # deprecations
65
96
  # not elegant or compact code, but temporary
66
97
 
98
+ # @private
67
99
  def self.always_use_ssl
68
100
  Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
69
101
  http_options[:use_ssl]
70
102
  end
71
103
 
104
+ # @private
72
105
  def self.always_use_ssl=(value)
73
106
  Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
74
107
  http_options[:use_ssl] = value
75
108
  end
76
109
 
110
+ # @private
77
111
  def self.timeout
78
112
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
79
113
  http_options[:timeout]
80
114
  end
81
115
 
116
+ # @private
82
117
  def self.timeout=(value)
83
118
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
84
119
  http_options[:timeout] = value
85
120
  end
86
121
 
122
+ # @private
87
123
  def self.timeout
88
124
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
89
125
  http_options[:timeout]
90
126
  end
91
127
 
128
+ # @private
92
129
  def self.timeout=(value)
93
130
  Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
94
131
  http_options[:timeout] = value
95
132
  end
96
133
 
134
+ # @private
97
135
  def self.proxy
98
136
  Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
99
137
  http_options[:proxy]
100
138
  end
101
139
 
140
+ # @private
102
141
  def self.proxy=(value)
103
142
  Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
104
143
  http_options[:proxy] = value
105
144
  end
106
145
 
146
+ # @private
107
147
  def self.ca_path
108
148
  Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
109
149
  (http_options[:ssl] || {})[:ca_path]
110
150
  end
111
151
 
152
+ # @private
112
153
  def self.ca_path=(value)
113
154
  Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
114
155
  (http_options[:ssl] ||= {})[:ca_path] = value
115
156
  end
116
157
 
158
+ # @private
117
159
  def self.ca_file
118
160
  Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
119
161
  (http_options[:ssl] || {})[:ca_file]
120
162
  end
121
163
 
164
+ # @private
122
165
  def self.ca_file=(value)
123
166
  Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
124
167
  (http_options[:ssl] ||= {})[:ca_file] = value
125
168
  end
126
169
 
170
+ # @private
127
171
  def self.verify_mode
128
172
  Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
129
173
  (http_options[:ssl] || {})[:verify_mode]
130
174
  end
131
175
 
176
+ # @private
132
177
  def self.verify_mode=(value)
133
178
  Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
134
179
  (http_options[:ssl] ||= {})[:verify_mode] = value
135
180
  end
136
181
 
182
+ private
183
+
137
184
  def self.process_options(options)
138
185
  if typhoeus_options = options.delete(:typhoeus_options)
139
186
  Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
@@ -159,6 +206,7 @@ module Koala
159
206
  end
160
207
  end
161
208
 
209
+ # @private
162
210
  module TyphoeusService
163
211
  def self.deprecated_interface
164
212
  # support old-style interface with a warning
@@ -167,6 +215,7 @@ module Koala
167
215
  end
168
216
  end
169
217
 
218
+ # @private
170
219
  module NetHTTPService
171
220
  def self.deprecated_interface
172
221
  # support old-style interface with a warning
data/lib/koala/oauth.rb CHANGED
@@ -1,22 +1,37 @@
1
+ # OpenSSL and Base64 are required to support signed_request
2
+ require 'openssl'
3
+ require 'base64'
4
+
1
5
  module Koala
2
6
  module Facebook
7
+
8
+ DIALOG_HOST = "www.facebook.com"
9
+
3
10
  class OAuth
4
11
  attr_reader :app_id, :app_secret, :oauth_callback_url
12
+
13
+ # Creates a new OAuth client.
14
+ #
15
+ # @param app_id [String, Integer] a Facebook application ID
16
+ # @param app_secret a Facebook application secret
17
+ # @param oauth_callback_url the URL in your app to which users authenticating with OAuth will be sent
5
18
  def initialize(app_id, app_secret, oauth_callback_url = nil)
6
19
  @app_id = app_id
7
20
  @app_secret = app_secret
8
21
  @oauth_callback_url = oauth_callback_url
9
22
  end
10
-
23
+
24
+ # Parses the cookie set Facebook's JavaScript SDK.
25
+ #
26
+ # @note in parsing Facebook's new signed cookie format this method has to make a request to Facebook.
27
+ # We recommend storing authenticated user info in your Rails session (or equivalent) and only
28
+ # calling this when needed.
29
+ #
30
+ # @param cookie_hash a set of cookies that includes the Facebook cookie.
31
+ # You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
32
+ #
33
+ # @return the authenticated user's information as a hash, or nil.
11
34
  def get_user_info_from_cookie(cookie_hash)
12
- # Parses the cookie set Facebook's JavaScript SDK.
13
- # You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
14
- #
15
- # If the user is logged in via Facebook, we return a dictionary with the
16
- # keys "uid" and "access_token". The former is the user's Facebook ID,
17
- # and the latter can be used to make authenticated requests to the Graph API.
18
- # If the user is not logged in, we return None.
19
-
20
35
  if signed_cookie = cookie_hash["fbsr_#{@app_id}"]
21
36
  parse_signed_cookie(signed_cookie)
22
37
  elsif unsigned_cookie = cookie_hash["fbs_#{@app_id}"]
@@ -25,6 +40,13 @@ module Koala
25
40
  end
26
41
  alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
27
42
 
43
+ # Parses the cookie set Facebook's JavaScript SDK and returns only the user ID.
44
+ #
45
+ # @note (see #get_user_info_from_cookie)
46
+ #
47
+ # @param (see #get_user_info_from_cookie)
48
+ #
49
+ # @return the authenticated user's Facebook ID, or nil.
28
50
  def get_user_from_cookie(cookies)
29
51
  if info = get_user_info_from_cookies(cookies)
30
52
  # signed cookie has user_id, unsigned cookie has uid
@@ -34,33 +56,106 @@ module Koala
34
56
  alias_method :get_user_from_cookies, :get_user_from_cookie
35
57
 
36
58
  # URLs
37
-
59
+
60
+ # Builds an OAuth URL, where users will be prompted to log in and for any desired permissions.
61
+ # When the users log in, you receive a callback with their
62
+ # See http://developers.facebook.com/docs/authentication/.
63
+ #
64
+ # @see #url_for_access_token
65
+ #
66
+ # @note The server-side authentication and dialog methods should only be used
67
+ # if your application can't use the Facebook Javascript SDK,
68
+ # which provides a much better user experience.
69
+ # See http://developers.facebook.com/docs/reference/javascript/.
70
+ #
71
+ # @param options any query values to add to the URL, as well as any special/required values listed below.
72
+ # @option options permissions an array or comma-separated string of desired permissions
73
+ #
74
+ # @raise ArgumentError if no OAuth callback was specified in OAuth#new or in options as :redirect_uri
75
+ #
76
+ # @return an OAuth URL you can send your users to
38
77
  def url_for_oauth_code(options = {})
39
78
  # for permissions, see http://developers.facebook.com/docs/authentication/permissions
40
- permissions = options[:permissions]
41
- scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
42
- display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
43
-
44
- callback = options[:callback] || @oauth_callback_url
45
- raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
46
-
79
+ if permissions = options.delete(:permissions)
80
+ options[:scope] = permissions.is_a?(Array) ? permissions.join(",") : permissions
81
+ end
82
+ url_options = {:client_id => @app_id}.merge(options)
83
+
47
84
  # Creates the URL for oauth authorization for a given callback and optional set of permissions
48
- "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
85
+ build_url("https://#{GRAPH_SERVER}/oauth/authorize", true, url_options)
49
86
  end
50
87
 
88
+ # Once you receive an OAuth code, you need to redeem it from Facebook using an appropriate URL.
89
+ # (This is done by your server behind the scenes.)
90
+ # See http://developers.facebook.com/docs/authentication/.
91
+ #
92
+ # @see #url_for_oauth_code
93
+ #
94
+ # @note (see #url_for_oauth_code)
95
+ #
96
+ # @param code an OAuth code received from Facebook
97
+ # @param options any additional query parameters to add to the URL
98
+ #
99
+ # @raise (see #url_for_oauth_code)
100
+ #
101
+ # @return an URL your server can query for the user's access token
51
102
  def url_for_access_token(code, options = {})
52
103
  # Creates the URL for the token corresponding to a given code generated by Facebook
53
- callback = options[:callback] || @oauth_callback_url
54
- raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
55
- "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
104
+ url_options = {
105
+ :client_id => @app_id,
106
+ :code => code,
107
+ :client_secret => @app_secret
108
+ }.merge(options)
109
+ build_url("https://#{GRAPH_SERVER}/oauth/access_token", true, url_options)
56
110
  end
57
111
 
112
+ # Builds a URL for a given dialog (feed, friends, OAuth, pay, send, etc.)
113
+ # See http://developers.facebook.com/docs/reference/dialogs/.
114
+ #
115
+ # @note (see #url_for_oauth_code)
116
+ #
117
+ # @param dialog_type the kind of Facebook dialog you want to show
118
+ # @param options any additional query parameters to add to the URL
119
+ #
120
+ # @return an URL your server can query for the user's access token
121
+ def url_for_dialog(dialog_type, options = {})
122
+ # some endpoints require app_id, some client_id, supply both doesn't seem to hurt
123
+ url_options = {:app_id => @app_id, :client_id => @app_id}.merge(options)
124
+ build_url("http://#{DIALOG_HOST}/dialog/#{dialog_type}", true, url_options)
125
+ end
126
+
127
+ # access tokens
128
+
129
+ # Fetches an access token, token expiration, and other info from Facebook.
130
+ # Useful when you've received an OAuth code using the server-side authentication process.
131
+ # @see url_for_oauth_code
132
+ #
133
+ # @note (see #url_for_oauth_code)
134
+ #
135
+ # @param code (see #url_for_access_token)
136
+ # @param options any additional parameters to send to Facebook when redeeming the token
137
+ #
138
+ # @raise Koala::Facebook::APIError if Facebook returns an error response
139
+ #
140
+ # @return a hash of the access token info returned by Facebook (token, expiration, etc.)
58
141
  def get_access_token_info(code, options = {})
59
142
  # convenience method to get a parsed token from Facebook for a given code
60
143
  # should this require an OAuth callback URL?
61
144
  get_token_from_server({:code => code, :redirect_uri => options[:redirect_uri] || @oauth_callback_url}, false, options)
62
145
  end
63
146
 
147
+
148
+ # Fetches the access token (ignoring expiration and other info) from Facebook.
149
+ # Useful when you've received an OAuth code using the server-side authentication process.
150
+ # @see get_access_token_info
151
+ #
152
+ # @note (see #url_for_oauth_code)
153
+ #
154
+ # @param (see #get_access_token_info)
155
+ #
156
+ # @raise (see #get_access_token_info)
157
+ #
158
+ # @return the access token
64
159
  def get_access_token(code, options = {})
65
160
  # upstream methods will throw errors if needed
66
161
  if info = get_access_token_info(code, options)
@@ -68,22 +163,36 @@ module Koala
68
163
  end
69
164
  end
70
165
 
166
+ # Fetches the application's access token, along with any other information provided by Facebook.
167
+ # See http://developers.facebook.com/docs/authentication/ (search for App Login).
168
+ #
169
+ # @param options any additional parameters to send to Facebook when redeeming the token
170
+ #
171
+ # @return the application access token and other information (expiration, etc.)
71
172
  def get_app_access_token_info(options = {})
72
173
  # convenience method to get a the application's sessionless access token
73
174
  get_token_from_server({:type => 'client_cred'}, true, options)
74
175
  end
75
176
 
177
+ # Fetches the application's access token (ignoring expiration and other info).
178
+ # @see get_app_access_token_info
179
+ #
180
+ # @param (see #get_app_access_token_info)
181
+ #
182
+ # @return the application access token
76
183
  def get_app_access_token(options = {})
77
184
  if info = get_app_access_token_info(options)
78
185
  string = info["access_token"]
79
186
  end
80
187
  end
81
188
 
82
- # Originally provided directly by Facebook, however this has changed
83
- # as their concept of crypto changed. For historic purposes, this is their proposal:
84
- # https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
85
- # Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
86
- # for a more accurate reference implementation strategy.
189
+ # Parses a signed request string provided by Facebook to canvas apps or in a secure cookie.
190
+ #
191
+ # @param input the signed request from Facebook
192
+ #
193
+ # @raise RuntimeError if the signature is incomplete, invalid, or using an unsupported algorithm
194
+ #
195
+ # @return a hash of the validated request information
87
196
  def parse_signed_request(input)
88
197
  encoded_sig, encoded_envelope = input.split('.', 2)
89
198
  raise 'SignedRequest: Invalid (incomplete) signature data' unless encoded_sig && encoded_envelope
@@ -100,8 +209,12 @@ module Koala
100
209
  envelope
101
210
  end
102
211
 
103
- # from session keys
212
+ # Old session key code
213
+
214
+ # @deprecated Facebook no longer provides session keys.
104
215
  def get_token_info_from_session_keys(sessions, options = {})
216
+ Koala::Utils.deprecate("Facebook no longer provides session keys. The relevant OAuth methods will be removed in the next release.")
217
+
105
218
  # fetch the OAuth tokens from Facebook
106
219
  response = fetch_token_string({
107
220
  :type => 'client_cred',
@@ -119,6 +232,7 @@ module Koala
119
232
  MultiJson.decode(response)
120
233
  end
121
234
 
235
+ # @deprecated (see #get_token_info_from_session_keys)
122
236
  def get_tokens_from_session_keys(sessions, options = {})
123
237
  # get the original hash results
124
238
  results = get_token_info_from_session_keys(sessions, options)
@@ -126,6 +240,7 @@ module Koala
126
240
  results.collect { |r| r ? r["access_token"] : nil }
127
241
  end
128
242
 
243
+ # @deprecated (see #get_token_info_from_session_keys)
129
244
  def get_token_from_session_key(session, options = {})
130
245
  # convenience method for a single key
131
246
  # gets the overlaoded strings automatically
@@ -189,6 +304,14 @@ module Koala
189
304
  str += '=' * (4 - str.length.modulo(4))
190
305
  Base64.decode64(str.tr('-_', '+/'))
191
306
  end
307
+
308
+ def build_url(base, require_redirect_uri = false, url_options = {})
309
+ if require_redirect_uri && !(url_options[:redirect_uri] ||= url_options.delete(:callback) || @oauth_callback_url)
310
+ raise ArgumentError, "url_for_dialog must get a callback either from the OAuth object or in the parameters!"
311
+ end
312
+
313
+ "#{base}?#{Koala::HTTPService.encode_params(url_options)}"
314
+ end
192
315
  end
193
316
  end
194
317
  end
@@ -1,36 +1,27 @@
1
1
  module Koala
2
2
  module Facebook
3
- module RealtimeUpdateMethods
4
- # note: to subscribe to real-time updates, you must have an application access token
3
+ class RealtimeUpdates
4
+ # Manage realtime callbacks for changes to users' information.
5
+ # See http://developers.facebook.com/docs/reference/api/realtime.
6
+ #
7
+ # @note: to subscribe to real-time updates, you must have an application access token
8
+ # or provide the app secret when initializing your RealtimeUpdates object.
5
9
 
6
- def self.included(base)
7
- # make the attributes readable
8
- base.class_eval do
9
- attr_reader :api, :app_id, :app_access_token, :secret
10
-
11
- # parses the challenge params and makes sure the call is legitimate
12
- # returns the challenge string to be sent back to facebook if true
13
- # returns false otherwise
14
- # this is a class method, since you don't need to know anything about the app
15
- # saves a potential trip fetching the app access token
16
- def self.meet_challenge(params, verify_token = nil, &verification_block)
17
- if params["hub.mode"] == "subscribe" &&
18
- # you can make sure this is legitimate through two ways
19
- # if your store the token across the calls, you can pass in the token value
20
- # and we'll make sure it matches
21
- (verify_token && params["hub.verify_token"] == verify_token) ||
22
- # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
23
- # you can pass in a block, which we'll call with the verify_token sent by Facebook
24
- # if it's legit, return anything that evaluates to true; otherwise, return nil or false
25
- (verification_block && yield(params["hub.verify_token"]))
26
- params["hub.challenge"]
27
- else
28
- false
29
- end
30
- end
31
- end
32
- end
10
+ # The application API interface used to communicate with Facebook.
11
+ # @return [Koala::Facebook::API]
12
+ attr_reader :api
13
+ attr_reader :app_id, :app_access_token, :secret
33
14
 
15
+ # Create a new RealtimeUpdates instance.
16
+ # If you don't have your app's access token, provide the app's secret and
17
+ # Koala will make a request to Facebook for the appropriate token.
18
+ #
19
+ # @param options initialization options.
20
+ # @option options :app_id the application's ID.
21
+ # @option options :app_access_token an application access token, if known.
22
+ # @option options :secret the application's secret.
23
+ #
24
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
34
25
  def initialize(options = {})
35
26
  @app_id = options[:app_id]
36
27
  @app_access_token = options[:app_access_token]
@@ -45,45 +36,92 @@ module Koala
45
36
  @app_access_token = oauth.get_app_access_token
46
37
  end
47
38
 
48
- @graph_api = API.new(@app_access_token)
39
+ @api = API.new(@app_access_token)
49
40
  end
50
41
 
51
- # subscribes for realtime updates
52
- # your callback_url must be set up to handle the verification request or the subscription will not be set up
53
- # http://developers.facebook.com/docs/api/realtime
54
- def subscribe(object, fields, callback_url, verify_token)
42
+ # Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
43
+ # See {http://developers.facebook.com/docs/reference/api/realtime the realtime updates documentation}
44
+ # for more information on what objects and fields you can register for.
45
+ #
46
+ # @note Your callback_url must be set up to handle the verification request or the subscription will not be set up.
47
+ #
48
+ # @param object a Facebook ID (name or number)
49
+ # @param fields the fields you want your app to be updated about
50
+ # @param callback_url the URL Facebook should ping when an update is available
51
+ # @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
52
+ # (see the docs for more information)
53
+ # @param options (see Koala::HTTPService.make_request)
54
+ #
55
+ # @return true if successful, false (or an APIError) otherwise.
56
+ def subscribe(object, fields, callback_url, verify_token, options = {})
55
57
  args = {
56
58
  :object => object,
57
59
  :fields => fields,
58
60
  :callback_url => callback_url,
59
- :verify_token => verify_token
60
- }
61
+ }.merge(verify_token ? {:verify_token => verify_token} : {})
61
62
  # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
62
- @graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
63
+ @api.graph_call(subscription_path, args, 'post', options.merge(:http_component => :status)) == 200
63
64
  end
64
65
 
65
- # removes subscription for object
66
- # if object is nil, it will remove all subscriptions
67
- def unsubscribe(object = nil)
68
- args = {}
69
- args[:object] = object if object
70
- @graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
66
+ # Unsubscribe from updates for a particular object or from updates.
67
+ #
68
+ # @param object the object whose subscriptions to delete.
69
+ # If no object is provided, all subscriptions will be removed.
70
+ # @param options (see Koala::HTTPService.make_request)
71
+ #
72
+ # @return true if the unsubscription is successful, false (or an APIError) otherwise.
73
+ def unsubscribe(object = nil, options = {})
74
+ @api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options.merge(:http_component => :status)) == 200
71
75
  end
72
76
 
73
- def list_subscriptions
74
- @graph_api.graph_call(subscription_path)
77
+ # List all active subscriptions for this application.
78
+ #
79
+ # @param options (see Koala::HTTPService.make_request)
80
+ #
81
+ # @return [Array] a list of active subscriptions
82
+ def list_subscriptions(options = {})
83
+ @api.graph_call(subscription_path, {}, "get", options)
75
84
  end
76
85
 
77
- def graph_api
78
- Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
79
- @api
86
+ # As a security measure (to prevent DDoS attacks), Facebook sends a verification request to your server
87
+ # after you request a subscription.
88
+ # This method parses the challenge params and makes sure the call is legitimate.
89
+ #
90
+ # @param params the request parameters sent by Facebook. (You can pass in a Rails params hash.)
91
+ # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
92
+ #
93
+ # @yield verify_token if you need to compute the verification token
94
+ # (for instance, if your callback URL includes a record ID, which you look up
95
+ # and use to calculate a hash), you can pass meet_challenge a block, which
96
+ # will receive the verify_token received back from Facebook.
97
+ #
98
+ # @return the challenge string to be sent back to Facebook, or false if the request is invalid.
99
+ def self.meet_challenge(params, verify_token = nil, &verification_block)
100
+ if params["hub.mode"] == "subscribe" &&
101
+ # you can make sure this is legitimate through two ways
102
+ # if your store the token across the calls, you can pass in the token value
103
+ # and we'll make sure it matches
104
+ (verify_token && params["hub.verify_token"] == verify_token) ||
105
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
106
+ # you can pass in a block, which we'll call with the verify_token sent by Facebook
107
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
108
+ (verification_block && yield(params["hub.verify_token"]))
109
+ params["hub.challenge"]
110
+ else
111
+ false
112
+ end
80
113
  end
81
-
82
- protected
83
-
114
+
115
+ # The Facebook subscription management URL for your application.
84
116
  def subscription_path
85
117
  @subscription_path ||= "#{@app_id}/subscriptions"
86
118
  end
119
+
120
+ # @private
121
+ def graph_api
122
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
123
+ @api
124
+ end
87
125
  end
88
126
  end
89
127
  end