├── .DS_Store
├── .gitignore
├── CHANGELOG
├── LICENSE
├── Manifest
├── README.rdoc
├── Rakefile
├── bin
└── tunnel
├── lib
└── frankie.rb
└── test
├── frankie_test.rb
└── helper.rb
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deadprogram/frankie/0acfaf1f669f24858fa703c249657f7285961d82/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | doc
2 | pkg
3 | *.log
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | v0.3.0 Updated to work with Sinatra 0.9.x and facebooker 1.0.2, thanks to mjfreshyfresh
2 |
3 | v0.2.5. Bugfix for fb_url_for helper method
4 |
5 | v0.2.4. Add fb_url_for helper method to create link URL's that have proper Facebook application reference included
6 |
7 | v0.2.3. Correct README and fix Sinatra version dependency in gem
8 |
9 | v0.2.2. Updated to deal with session change in Sinatra, plus misc bug fixes with Facebooker integration
10 |
11 | v0.2.0. Converted codebase to become Ruby Gem
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008 Ron Evans
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Manifest:
--------------------------------------------------------------------------------
1 | bin/tunnel
2 | CHANGELOG
3 | lib/frankie.rb
4 | LICENSE
5 | Manifest
6 | Rakefile
7 | README.rdoc
8 | test/frankie_test.rb
9 | test/helper.rb
10 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Frankie
2 |
3 | Frankie (http://facethesinatra.com) is a plugin for the Sinatra web framework (http://sinatrarb.com) that allows you to easily create a Facebook application by using the Facebooker gem.
4 |
5 | Written by Ron Evans (http://www.deadprogrammersociety.com)
6 |
7 | Based on merb_facebooker (http://github.com/vanpelt/merb_facebooker) by Chris Van Pelt, which was based on
8 | the Rails classes in Facebooker (http://facebooker.rubyforge.org/) by Mike Mangino, Shane Vitarana, & Chad Fowler
9 |
10 | 2/20/2009 - Now updated to Sinatra 0.9 and facebooker thanks to mjfreshyfresh.
11 |
12 | Thanks, everyone!
13 |
14 | = Here is a very simple example application:
15 |
16 | require 'rubygems'
17 | require 'frankie'
18 |
19 | configure do
20 | set_option :sessions, true
21 | load_facebook_config "./config/facebooker.yml", Sinatra.env
22 | end
23 |
24 | ## facebooker helpers
25 | before do
26 | ensure_authenticated_to_facebook
27 | ensure_application_is_installed_by_facebook_user
28 | end
29 |
30 | ## the site
31 | get '/' do
32 | body "
Hello #{session[:facebook_session].user.name} and welcome to frankie!
"
33 | end
34 |
35 |
36 | = How to use frankie
37 | - Install the frankie gem (which will install both Sinatra and Facebooker if you do not already have them)
38 | sudo gem install frankie
39 |
40 | - Create the application directories for your new app
41 | mkdir myapp
42 | cd myapp
43 | mkdir config
44 |
45 | - Put your facebooker.yml file into the /myapp/config directory, and set the values to your information. Here is a simple example of the file:
46 |
47 | development:
48 | api_key: apikeyhere
49 | secret_key: secretkeyhere
50 | canvas_page_name: yourcanvashere
51 | callback_url: http://localhost:4567
52 | test:
53 | api_key: apikeyhere
54 | secret_key: secretkeyhere
55 | canvas_page_name: yourcanvashere
56 | callback_url: http://localhost:4567
57 | production:
58 | api_key: apikeyhere
59 | secret_key: secretkeyhere
60 | canvas_page_name: yourcanvashere
61 | callback_url: http://yourrealserver.com
62 |
63 |
64 | - Make sure you have setup your Facebook application on the facebook site. Google "setup new facebook application" if you are unsure how to do this. I recommend starting with an IFrame application. A more advanced and cooler approach uses a tunneling script, which is included with Frankie. You do need to have "autossh" installed in order to use it, as well as a publicly addressable server. From a command prompt type tunnel like this:
65 |
66 | tunnel app.myhost.com 10000 4567
67 |
68 | You will also need to make sure your server's /etc/ssh/sshd_config contains the following line:
69 |
70 | GatewayPorts clientspecified
71 |
72 | Thanks to the many people like Evan Weaver, Blake Mizerany, and whoever else that have provided the code used in this tunneling script.
73 |
74 | - Create your application, based on the sample above, and then run it:
75 | ruby sample.rb
76 |
77 | - Test your app by going to http://apps.facebook.com/yourappname
78 |
79 | Have fun!
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake/testtask'
3 | require 'rake/rdoctask'
4 | require 'echoe'
5 |
6 | task :default => :test
7 |
8 | Rake::RDocTask.new do |rd|
9 | rd.main = "README.rdoc"
10 | rd.rdoc_files += ["README.rdoc"]
11 | rd.rdoc_files += Dir.glob("lib/**/*.rb")
12 | rd.rdoc_dir = 'doc'
13 | end
14 |
15 | Rake::TestTask.new do |t|
16 | ENV['SINATRA_ENV'] = 'test'
17 | t.pattern = File.dirname(__FILE__) + "/test/*_test.rb"
18 | end
19 |
20 | Echoe.new("frankie") do |p|
21 | p.author = "Ron Evans"
22 | p.summary = "Easy creation of Facebook applications in Ruby using plugin for Sinatra web framework that integrates with Facebooker gem."
23 | p.url = "http://facethesinatra.com/"
24 | p.dependencies = ["sinatra >=0.2.2", "facebooker >=0.9.5"]
25 | p.install_message = "*** Frankie was installed ***"
26 | p.include_rakefile = true
27 | end
28 |
29 |
--------------------------------------------------------------------------------
/bin/tunnel:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | host, remote_port, local_port = ARGV
4 | raise 'You must specify at least a hostname - i.e. tunnel myhost.com' unless host
5 | remote_port ||= 10000
6 | local_port ||= 4567
7 | puts "Tunneling #{host}:#{remote_port} to 0.0.0.0:#{local_port}"
8 | begin
9 | exec "autossh -M 48484 -nNT -g -R *:#{remote_port}:0.0.0.0:#{local_port} #{host}"
10 | rescue
11 | raise "Tunnel failed to start. Do you have autossh installed?"
12 | end
13 |
--------------------------------------------------------------------------------
/lib/frankie.rb:
--------------------------------------------------------------------------------
1 | # frankie - a plugin for sinatra that integrates with the facebooker gem
2 | #
3 | # written by Ron Evans (http://www.deadprogrammersociety.com)
4 | #
5 | # based on merb_facebooker (http://github.com/vanpelt/merb_facebooker)
6 | # and the Rails classes in Facebooker (http://facebooker.rubyforge.org/) from Mike Mangino, Shane Vitarana, & Chad Fowler
7 | #
8 |
9 | require 'sinatra'
10 |
11 | module Frankie
12 |
13 | module Loader
14 |
15 | require 'yaml'
16 | require 'uri'
17 |
18 | def load_facebook_config(file, env=:development)
19 | if File.exist?(file)
20 | yaml = YAML.load_file(file)[env.to_s]
21 | ENV['FACEBOOK_API_KEY'] = yaml['api_key']
22 | ENV['FACEBOOK_SECRET_KEY'] = yaml['secret_key']
23 | ENV['FACEBOOKER_RELATIVE_URL_ROOT'] = yaml['canvas_page_name']
24 | end
25 | end
26 |
27 | end
28 |
29 | module EventContext
30 |
31 | # pin it to newer version of Facebooker
32 | gem 'mmangino-facebooker', '>=1.0.2'
33 | require 'facebooker'
34 |
35 | def facebook_session
36 | @facebook_session
37 | end
38 |
39 | def facebook_session_parameters
40 | {:fb_sig_session_key=>params["fb_sig_session_key"]}
41 | end
42 |
43 | def set_facebook_session
44 | session_set = session_already_secured? || secure_with_token! || secure_with_facebook_params!
45 | if session_set
46 | capture_facebook_friends_if_available!
47 | Facebooker::Session.current = facebook_session
48 | end
49 | session_set
50 | end
51 |
52 | def facebook_params
53 | @facebook_params ||= verified_facebook_params
54 | end
55 |
56 | private
57 |
58 | def session_already_secured?
59 | (@facebook_session = session[:facebook_session]) && session[:facebook_session].secured?
60 | end
61 |
62 | def secure_with_token!
63 | if params['auth_token']
64 | @facebook_session = new_facebook_session
65 | @facebook_session.auth_token = params['auth_token']
66 | @facebook_session.secure!
67 | session[:facebook_session] = @facebook_session
68 | end
69 | end
70 |
71 | def secure_with_facebook_params!
72 | return unless request_is_for_a_facebook_canvas?
73 |
74 | if ['user', 'session_key'].all? {|element| facebook_params[element]}
75 | @facebook_session = new_facebook_session
76 | @facebook_session.secure_with!(facebook_params['session_key'], facebook_params['user'], facebook_params['expires'])
77 | session[:facebook_session] = @facebook_session
78 | end
79 | end
80 |
81 | def create_new_facebook_session_and_redirect!
82 | session[:facebook_session] = new_facebook_session
83 | throw :halt, do_redirect(session[:facebook_session].login_url) unless @installation_required
84 | end
85 |
86 | def new_facebook_session
87 | Facebooker::Session.create(Facebooker::Session.api_key, Facebooker::Session.secret_key)
88 | end
89 |
90 | def capture_facebook_friends_if_available!
91 | return unless request_is_for_a_facebook_canvas?
92 | if friends = facebook_params['friends']
93 | facebook_session.user.friends = friends.map do |friend_uid|
94 | Facebooker::User.new(friend_uid, facebook_session)
95 | end
96 | end
97 | end
98 |
99 | def blank?(value)
100 | (value == '0' || value.nil? || value == '')
101 | end
102 |
103 | def verified_facebook_params
104 | facebook_sig_params = params.inject({}) do |collection, pair|
105 | collection[pair.first.sub(/^fb_sig_/, '')] = pair.last if pair.first[0,7] == 'fb_sig_'
106 | collection
107 | end
108 | verify_signature(facebook_sig_params, params['fb_sig'])
109 | facebook_sig_params.inject(Hash.new) do |collection, pair|
110 | collection[pair.first] = facebook_parameter_conversions[pair.first].call(pair.last)
111 | collection
112 | end
113 | end
114 |
115 | # 48.hours.ago in sinatra
116 | def earliest_valid_session
117 | now = Time.now
118 | now -= (60 * 60 * 48)
119 | now
120 | end
121 |
122 | def verify_signature(facebook_sig_params,expected_signature)
123 | raw_string = facebook_sig_params.map{ |*args| args.join('=') }.sort.join
124 | actual_sig = Digest::MD5.hexdigest([raw_string, Facebooker::Session.secret_key].join)
125 | raise Facebooker::Session::IncorrectSignature if actual_sig != expected_signature
126 | raise Facebooker::Session::SignatureTooOld if Time.at(facebook_sig_params['time'].to_f) < earliest_valid_session
127 | true
128 | end
129 |
130 | def facebook_parameter_conversions
131 | @facebook_parameter_conversions ||= Hash.new do |hash, key|
132 | lambda{|value| value}
133 | end.merge(
134 | 'time' => lambda{|value| Time.at(value.to_f)},
135 | 'in_canvas' => lambda{|value| !blank?(value)},
136 | 'added' => lambda{|value| !blank?(value)},
137 | 'expires' => lambda{|value| blank?(value) ? nil : Time.at(value.to_f)},
138 | 'friends' => lambda{|value| value.split(/,/)}
139 | )
140 | end
141 |
142 | def do_redirect(*args)
143 | if request_is_for_a_facebook_canvas?
144 | fbml_redirect_tag(args[0])
145 | else
146 | redirect args[0]
147 | end
148 | end
149 |
150 | def fbml_redirect_tag(url)
151 | ""
152 | end
153 |
154 | def request_is_for_a_facebook_canvas?
155 | return false if params["fb_sig_in_canvas"].nil?
156 | params["fb_sig_in_canvas"] == "1"
157 | end
158 |
159 | def application_is_installed?
160 | facebook_params['added']
161 | end
162 |
163 | def ensure_authenticated_to_facebook
164 | set_facebook_session || create_new_facebook_session_and_redirect!
165 | end
166 |
167 | def ensure_application_is_installed_by_facebook_user
168 | @installation_required = true
169 | authenticated_and_installed = ensure_authenticated_to_facebook && application_is_installed?
170 | application_is_not_installed_by_facebook_user unless authenticated_and_installed
171 | authenticated_and_installed
172 | end
173 |
174 | def application_is_not_installed_by_facebook_user
175 | throw :halt, do_redirect(session[:facebook_session].install_url)
176 | end
177 |
178 | def set_fbml_format
179 | params['format']="fbml" if request_is_for_a_facebook_canvas?
180 | end
181 |
182 | def fb_url_for(url)
183 | url = "" if url == "/"
184 | url = URI.escape(url)
185 | return url if !request_is_for_a_facebook_canvas?
186 | "http://apps.facebook.com/#{ENV['FACEBOOKER_RELATIVE_URL_ROOT']}/#{url}"
187 | end
188 |
189 | end
190 |
191 | end
192 |
193 | # extend sinatra with frankie methods, changed to work with Sinatra 0.9.x
194 | Sinatra::Default.send(:include, Frankie::EventContext)
195 | include Frankie::Loader
--------------------------------------------------------------------------------
/test/frankie_test.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + '/helper'
2 |
3 | context "frankie" do
4 | it "should be a placeholder for a real test" do
5 | end
6 | end
7 |
8 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'sinatra'
3 | require File.dirname(__FILE__) + '/../lib/frankie'
4 | require 'sinatra/test/spec'
--------------------------------------------------------------------------------