palidanx-koala 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,98 @@
1
+ v0.9.0
2
+ -- Added parse_signed_request to handle Facebook's new authentication scheme
3
+ -- note: creates dependency on OpenSSL (OpenSSL::HMAC) for decryption
4
+ -- Added GraphCollection class to provide paging support for GraphAPI get_connections and search methods (thanks to jagthedrummer)
5
+ -- Added get_page method to easily fetch pages of results from GraphCollections
6
+ -- Exchanging sessions for tokens now works properly when provided invalid/expired session keys
7
+ -- You can now include a :typhoeus_options key in TyphoeusService#make_request's options hash to control the Typhoeus call (for example, to set :disable_ssl_peer_verification => true)
8
+ -- All paths provided to HTTP services start with leading / to improve compatibility with stubbing libraries
9
+ -- If Facebook returns nil for search or get_connections requests, Koala now returns nil rather than throwing an exception
10
+
11
+ v0.8.0
12
+ -- Breaking interface changes
13
+ -- Removed string overloading for the methods, per 0.7.3, which caused Marshaling issues
14
+ -- Removed ability to provide a string as the second argument to url_for_access_token, per 0.5.0
15
+
16
+ v0.7.4
17
+ -- Fixed bug with get_user_from_cookies
18
+
19
+ v0.7.3
20
+ -- Added support for picture sizes -- thanks thhermansen for the patch!
21
+ -- Adjusted the return values for several methods (get_access_token, get_app_access_token, get_token_from_session_key, get_tokens_from_session_keys, get_user_from_cookies)
22
+ -- These methods now return strings, rather than hashes, which makes more sense
23
+ -- The strings are overloaded with an [] method for backwards compatibility (Ruby is truly amazing)
24
+ -- Using those methods triggers a deprecation warning
25
+ -- This will be removed by 1.0
26
+ -- There are new info methods (get_access_token_info, get_app_access_token_info, get_token_info_from_session_keys, and get_user_info_from_cookies) that natively return hashes, for when you want the expiration date
27
+ -- Responses with HTTP status 500+ now properly throw errors under Net::HTTP
28
+ -- Updated changelog
29
+ -- Added license
30
+
31
+ v0.7.2
32
+ -- Added support for exchanging session keys for OAuth access tokens (get_token_from_session_key for single keys, get_tokens_from_session_keys for multiple)
33
+ -- Moved Koala files into a koala/ subdirectory to minimize risk of name collisions
34
+ -- Added OAuth Playground git submodule as an example
35
+ -- Updated tests, readme, and changelog
36
+
37
+ v0.7.1
38
+ -- Updated RealtimeUpdates#list_subscriptions and GraphAPI#get_connections to now return an
39
+ array of results directly (rather than a hash with one key)
40
+ -- Fixed a bug with Net::HTTP-based HTTP service in which the headers hash was improperly formatted
41
+ -- Updated readme
42
+
43
+ v0.7.0
44
+ -- Added RealtimeUpdates class, which can be used to manage subscriptions for user updates (see http://developers.facebook.com/docs/api/realtime)
45
+ -- Added picture method to graph API, which fetches an object's picture from the redirect headers.
46
+ -- Added _greatly_ improved testing with result mocking, which is now the default set of tests
47
+ -- Renamed live testing spec to koala_spec_without_mocks.rb
48
+ -- Added Koala::Response class, which encapsulates HTTP results since Facebook sometimes sends data in the status or headers
49
+ -- Much internal refactoring
50
+ -- Updated readme, changelog, etc.
51
+
52
+
53
+ v0.6.0
54
+ -- Added support for the old REST API thanks to cbaclig's great work
55
+ -- Updated tests to conform to RSpec standards
56
+ -- Updated changelog, readme, etc.
57
+
58
+ v0.5.1
59
+ -- Documentation is now on the wiki, updated readme accordingly.
60
+
61
+ v0.5.0
62
+ -- Added several new OAuth methods for making and parsing access token requests
63
+ -- Added test suite for the OAuth class
64
+ -- Made second argument to url_for_access_token a hash (strings still work but trigger a deprecation warning)
65
+ -- Added fields to facebook_data.yml
66
+ -- Updated readme
67
+
68
+ v0.4.1
69
+ -- Encapsulated GraphAPI and OAuth classes in the Koala::Facebook module for clarity (and to avoid claiming the global Facebook class)
70
+ -- Moved make_request method to Koala class from GraphAPI instance (for use by future OAuth class functionality)
71
+ -- Renamed request method to api for consistancy with Javascript library
72
+ -- Updated tests and readme
73
+
74
+ v0.4.0
75
+ -- Adopted the Koala name
76
+ -- Updated readme and tests
77
+ -- Fixed cookie verification bug for non-expiring OAuth tokens
78
+
79
+ v0.3.1
80
+ -- Bug fixes.
81
+
82
+ v0.3
83
+ -- Renamed Graph API class from Facebook::GraphAPI to FacebookGraph::API
84
+ -- Created FacebookGraph::OAuth class for tokens and OAuth URLs
85
+ -- Updated method for including HTTP service (think we've got it this time)
86
+ -- Updated tests
87
+ -- Added CHANGELOG and gemspec
88
+
89
+ v0.2
90
+ -- Gemified the project
91
+ -- Split out HTTP services into their own file, and adjusted inclusion method
92
+
93
+ v0.1
94
+ -- Added modular support for Typhoeus
95
+ -- Added tests
96
+
97
+ v0.0
98
+ -- Hi from F8! Basic read/write from the graph is working
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Alex Koppel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,44 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ Rakefile
5
+ examples/oauth_playground/Capfile
6
+ examples/oauth_playground/LICENSE
7
+ examples/oauth_playground/Rakefile
8
+ examples/oauth_playground/config.ru
9
+ examples/oauth_playground/config/deploy.rb
10
+ examples/oauth_playground/config/facebook.yml
11
+ examples/oauth_playground/lib/load_facebook.rb
12
+ examples/oauth_playground/lib/oauth_playground.rb
13
+ examples/oauth_playground/readme.md
14
+ examples/oauth_playground/spec/oauth_playground_spec.rb
15
+ examples/oauth_playground/spec/spec_helper.rb
16
+ examples/oauth_playground/tmp/restart.txt
17
+ examples/oauth_playground/views/index.erb
18
+ examples/oauth_playground/views/layout.erb
19
+ init.rb
20
+ koala.gemspec
21
+ lib/koala.rb
22
+ lib/koala/graph_api.rb
23
+ lib/koala/http_services.rb
24
+ lib/koala/realtime_updates.rb
25
+ lib/koala/rest_api.rb
26
+ readme.md
27
+ spec/facebook_data.yml
28
+ spec/koala/api_base_tests.rb
29
+ spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb
30
+ spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb
31
+ spec/koala/graph_api/graph_api_no_access_token_tests.rb
32
+ spec/koala/graph_api/graph_api_with_access_token_tests.rb
33
+ spec/koala/graph_api/graph_collection_tests.rb
34
+ spec/koala/live_testing_data_helper.rb
35
+ spec/koala/net_http_service_tests.rb
36
+ spec/koala/oauth/oauth_tests.rb
37
+ spec/koala/realtime_updates/realtime_updates_tests.rb
38
+ spec/koala/rest_api/rest_api_no_access_token_tests.rb
39
+ spec/koala/rest_api/rest_api_with_access_token_tests.rb
40
+ spec/koala_spec.rb
41
+ spec/koala_spec_helper.rb
42
+ spec/koala_spec_without_mocks.rb
43
+ spec/mock_facebook_responses.yml
44
+ spec/mock_http_service.rb
@@ -0,0 +1,16 @@
1
+ # Rakefile
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'echoe'
5
+
6
+ # gem management
7
+ Echoe.new('palidanx-koala', '0.9.0') do |p|
8
+ p.summary = "A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation."
9
+ p.description = "Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services."
10
+ p.url = "http://github.com/palidanx/koala"
11
+ p.author = ["Alex Koppel", "Chris Baclig", "Rafi Jacoby", "Context Optional"]
12
+ p.email = "alex@alexkoppel.com"
13
+ p.ignore_pattern = ["tmp/*", "script/*", "pkg/*"]
14
+ p.development_dependencies = []
15
+ p.retain_gemspec = true
16
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # init.rb
2
+ require 'koala'
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{palidanx-koala}
5
+ s.version = "0.9.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional"]
9
+ s.date = %q{2010-09-29}
10
+ s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph API and the older REST API, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
11
+ s.email = %q{alex@alexkoppel.com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "Rakefile", "init.rb", "koala.gemspec", "lib/koala.rb", "lib/koala/graph_api.rb", "lib/koala/http_services.rb", "lib/koala/realtime_updates.rb", "lib/koala/rest_api.rb", "readme.md", "spec/facebook_data.yml", "spec/koala/api_base_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb", "spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb", "spec/koala/graph_api/graph_api_no_access_token_tests.rb", "spec/koala/graph_api/graph_api_with_access_token_tests.rb", "spec/koala/live_testing_data_helper.rb", "spec/koala/net_http_service_tests.rb", "spec/koala/oauth/oauth_tests.rb", "spec/koala/realtime_updates/realtime_updates_tests.rb", "spec/koala/rest_api/rest_api_no_access_token_tests.rb", "spec/koala/rest_api/rest_api_with_access_token_tests.rb", "spec/koala_spec.rb", "spec/koala_spec_helper.rb", "spec/koala_spec_without_mocks.rb", "spec/mock_facebook_responses.yml", "spec/mock_http_service.rb"]
14
+ s.homepage = %q{http://github.com/palidanx/koala}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Koala", "--main", "readme.md"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{koala}
18
+ s.rubygems_version = %q{1.3.6}
19
+ s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the old REST API, realtime updates, and OAuth validation.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,296 @@
1
+ require 'cgi'
2
+ require 'digest/md5'
3
+
4
+ # rubygems is required to support json, how facebook returns data
5
+ require 'rubygems'
6
+ require 'json'
7
+
8
+ # openssl is required to support signed_request
9
+ require 'openssl'
10
+
11
+ # include default http services
12
+ require 'koala/http_services'
13
+
14
+ # add Graph API methods
15
+ require 'koala/graph_api'
16
+
17
+ # add REST API methods
18
+ require 'koala/rest_api'
19
+
20
+ require 'koala/realtime_updates'
21
+
22
+ module Koala
23
+
24
+ module Facebook
25
+ # Ruby client library for the Facebook Platform.
26
+ # Copyright 2010 Facebook
27
+ # Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional
28
+ #
29
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
30
+ # not use this file except in compliance with the License. You may obtain
31
+ # a copy of the License at
32
+ # http://www.apache.org/licenses/LICENSE-2.0
33
+ #
34
+ # Unless required by applicable law or agreed to in writing, software
35
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
36
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
37
+ # License for the specific language governing permissions and limitations
38
+ # under the License.
39
+ #
40
+ # This client library is designed to support the Graph API and the official
41
+ # Facebook JavaScript SDK, which is the canonical way to implement
42
+ # Facebook authentication. Read more about the Graph API at
43
+ # http://developers.facebook.com/docs/api. You can download the Facebook
44
+ # JavaScript SDK at http://github.com/facebook/connect-js/.
45
+
46
+ class API
47
+ # initialize with an access token
48
+ def initialize(access_token = nil)
49
+ @access_token = access_token
50
+ end
51
+
52
+ def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
53
+ # Fetches the given path in the Graph API.
54
+ args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
55
+
56
+ # add a leading /
57
+ path = "/#{path}" unless path =~ /^\//
58
+
59
+ # make the request via the provided service
60
+ result = Koala.make_request(path, args, verb, options)
61
+
62
+ # Check for any 500 errors before parsing the body
63
+ # since we're not guaranteed that the body is valid JSON
64
+ # in the case of a server error
65
+ raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
66
+
67
+ # Parse the body as JSON and check for errors if provided a mechanism to do so
68
+ # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
69
+ # and cause JSON.parse to fail -- so we account for that by wrapping the result in []
70
+ body = response = JSON.parse("[#{result.body.to_s}]")[0]
71
+ if error_checking_block
72
+ yield(body)
73
+ end
74
+
75
+ # now return the desired information
76
+ if options[:http_component]
77
+ result.send(options[:http_component])
78
+ else
79
+ body
80
+ end
81
+ end
82
+ end
83
+
84
+ class GraphAPI < API
85
+ include GraphAPIMethods
86
+ end
87
+
88
+ class RestAPI < API
89
+ include RestAPIMethods
90
+ end
91
+
92
+ class GraphAndRestAPI < API
93
+ include GraphAPIMethods
94
+ include RestAPIMethods
95
+ end
96
+
97
+ class RealtimeUpdates < API
98
+ include RealtimeUpdateMethods
99
+ end
100
+
101
+ class APIError < Exception
102
+ attr_accessor :fb_error_type
103
+ def initialize(details = {})
104
+ self.fb_error_type = details["type"]
105
+ super("#{fb_error_type}: #{details["message"]}")
106
+ end
107
+ end
108
+
109
+
110
+ class OAuth
111
+ attr_reader :app_id, :app_secret, :oauth_callback_url
112
+ def initialize(app_id, app_secret, oauth_callback_url = nil)
113
+ @app_id = app_id
114
+ @app_secret = app_secret
115
+ @oauth_callback_url = oauth_callback_url
116
+ end
117
+
118
+ def get_user_info_from_cookie(cookie_hash)
119
+ # Parses the cookie set by the official Facebook JavaScript SDK.
120
+ #
121
+ # cookies should be a Hash, like the one Rails provides
122
+ #
123
+ # If the user is logged in via Facebook, we return a dictionary with the
124
+ # keys "uid" and "access_token". The former is the user's Facebook ID,
125
+ # and the latter can be used to make authenticated requests to the Graph API.
126
+ # If the user is not logged in, we return None.
127
+ #
128
+ # Download the official Facebook JavaScript SDK at
129
+ # http://github.com/facebook/connect-js/. Read more about Facebook
130
+ # authentication at http://developers.facebook.com/docs/authentication/.
131
+
132
+ if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
133
+ # remove the opening/closing quote
134
+ fb_cookie = fb_cookie.gsub(/\"/, "")
135
+
136
+ # since we no longer get individual cookies, we have to separate out the components ourselves
137
+ components = {}
138
+ fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
139
+
140
+ # generate the signature and make sure it matches what we expect
141
+ auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
142
+ sig = Digest::MD5.hexdigest(auth_string + @app_secret)
143
+ sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
144
+ end
145
+ end
146
+ alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
147
+
148
+ def get_user_from_cookie(cookies)
149
+ if info = get_user_info_from_cookies(cookies)
150
+ string = info["uid"]
151
+ end
152
+ end
153
+ alias_method :get_user_from_cookies, :get_user_from_cookie
154
+
155
+ # URLs
156
+
157
+ def url_for_oauth_code(options = {})
158
+ # for permissions, see http://developers.facebook.com/docs/authentication/permissions
159
+ permissions = options[:permissions]
160
+ scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
161
+
162
+ callback = options[:callback] || @oauth_callback_url
163
+ raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
164
+
165
+ # Creates the URL for oauth authorization for a given callback and optional set of permissions
166
+ "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
167
+ end
168
+
169
+ def url_for_access_token(code, options = {})
170
+ # Creates the URL for the token corresponding to a given code generated by Facebook
171
+ callback = options[:callback] || @oauth_callback_url
172
+ raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
173
+ "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
174
+ end
175
+
176
+ def get_access_token_info(code)
177
+ # convenience method to get a parsed token from Facebook for a given code
178
+ # should this require an OAuth callback URL?
179
+ get_token_from_server(:code => code, :redirect_uri => @oauth_callback_url)
180
+ end
181
+
182
+ def get_access_token(code)
183
+ # upstream methods will throw errors if needed
184
+ if info = get_access_token_info(code)
185
+ string = info["access_token"]
186
+ end
187
+ end
188
+
189
+ def get_app_access_token_info
190
+ # convenience method to get a the application's sessionless access token
191
+ get_token_from_server({:type => 'client_cred'}, true)
192
+ end
193
+
194
+ def get_app_access_token
195
+ if info = get_app_access_token_info
196
+ string = info["access_token"]
197
+ end
198
+ end
199
+
200
+ # signed_request
201
+ def parse_signed_request(request)
202
+ # Facebook's signed requests come in two parts -- the signature and the data payload
203
+ # see http://developers.facebook.com/docs/authentication/canvas
204
+ encoded_sig, payload = request.split(".")
205
+
206
+ sig = base64_url_decode(encoded_sig)
207
+
208
+ # if the signature matches, return the data, decoded and parsed as JSON
209
+ if OpenSSL::HMAC.digest("sha256", @app_secret, payload) == sig
210
+ JSON.parse(base64_url_decode(payload))
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ # from session keys
217
+ def get_token_info_from_session_keys(sessions)
218
+ # fetch the OAuth tokens from Facebook
219
+ response = fetch_token_string({
220
+ :type => 'client_cred',
221
+ :sessions => sessions.join(",")
222
+ }, true, "exchange_sessions")
223
+
224
+ # Facebook returns an empty body in certain error conditions
225
+ if response == ""
226
+ raise APIError.new("ArgumentError", "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!")
227
+ end
228
+
229
+ JSON.parse(response)
230
+ end
231
+
232
+ def get_tokens_from_session_keys(sessions)
233
+ # get the original hash results
234
+ results = get_token_info_from_session_keys(sessions)
235
+ # now recollect them as just the access tokens
236
+ results.collect { |r| r ? r["access_token"] : nil }
237
+ end
238
+
239
+ def get_token_from_session_key(session)
240
+ # convenience method for a single key
241
+ # gets the overlaoded strings automatically
242
+ get_tokens_from_session_keys([session])[0]
243
+ end
244
+
245
+ protected
246
+
247
+ def get_token_from_server(args, post = false)
248
+ # fetch the result from Facebook's servers
249
+ result = fetch_token_string(args, post)
250
+
251
+ # if we have an error, parse the error JSON and raise an error
252
+ raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
253
+
254
+ # otherwise, parse the access token
255
+ parse_access_token(result)
256
+ end
257
+
258
+ def parse_access_token(response_text)
259
+ components = response_text.split("&").inject({}) do |hash, bit|
260
+ key, value = bit.split("=")
261
+ hash.merge!(key => value)
262
+ end
263
+ components
264
+ end
265
+
266
+ def fetch_token_string(args, post = false, endpoint = "access_token")
267
+ Koala.make_request("/oauth/#{endpoint}", {
268
+ :client_id => @app_id,
269
+ :client_secret => @app_secret
270
+ }.merge!(args), post ? "post" : "get").body
271
+ end
272
+
273
+ # base 64
274
+ def base64_url_decode(string)
275
+ # to properly decode what Facebook provides, we need to add == to the end
276
+ # and translate certain characters to others before running the actual decoding
277
+ # see http://developers.facebook.com/docs/authentication/canvas
278
+ "#{string}==".tr("-_", "+/").unpack("m")[0]
279
+ end
280
+ end
281
+ end
282
+
283
+ # finally, set up the http service Koala methods used to make requests
284
+ # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
285
+ def self.http_service=(service)
286
+ self.send(:include, service)
287
+ end
288
+
289
+ # by default, try requiring Typhoeus -- if that works, use it
290
+ begin
291
+ require 'typhoeus'
292
+ Koala.http_service = TyphoeusService
293
+ rescue LoadError
294
+ Koala.http_service = NetHTTPService
295
+ end
296
+ end