rack-superfeedr 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,16 +1,16 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
5
  gem 'rack'
6
6
  gem 'nokogiri', ">=1.6.1"
7
- gem 'typhoeus', ">=0.6.7"
8
-
9
7
 
10
8
  # Add dependencies to develop your gem here.
11
9
  # Include everything needed to run rake, tests, features, etc.
12
10
  group :development do
13
- gem "shoulda", ">= 0"
14
- gem "bundler", ">= 1.0.0"
15
- gem "jeweler", ">= 1.6.4"
11
+ gem 'shoulda', '>= 0'
12
+ gem 'turn'
13
+ gem 'bundler', '>= 1.0.0'
14
+ gem 'jeweler', '>= 1.6.4'
15
+ gem 'thin'
16
16
  end
data/Gemfile.lock CHANGED
@@ -1,36 +1,34 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activesupport (4.0.2)
5
- i18n (~> 0.6, >= 0.6.4)
4
+ activesupport (4.0.8)
5
+ i18n (~> 0.6, >= 0.6.9)
6
6
  minitest (~> 4.2)
7
7
  multi_json (~> 1.3)
8
8
  thread_safe (~> 0.1)
9
9
  tzinfo (~> 0.3.37)
10
- addressable (2.3.5)
11
- atomic (1.1.14)
10
+ addressable (2.3.6)
11
+ ansi (1.4.3)
12
12
  builder (3.2.2)
13
- descendants_tracker (0.0.3)
14
- ethon (0.6.2)
15
- ffi (>= 1.3.0)
16
- mime-types (~> 1.18)
17
- faraday (0.8.9)
18
- multipart-post (~> 1.2.0)
19
- ffi (1.9.3)
20
- git (1.2.6)
21
- github_api (0.11.1)
13
+ daemons (1.1.9)
14
+ descendants_tracker (0.0.4)
15
+ thread_safe (~> 0.3, >= 0.3.1)
16
+ eventmachine (1.0.3)
17
+ faraday (0.9.0)
18
+ multipart-post (>= 1.2, < 3)
19
+ git (1.2.7)
20
+ github_api (0.12.0)
22
21
  addressable (~> 2.3)
23
- descendants_tracker (~> 0.0.1)
22
+ descendants_tracker (~> 0.0.4)
24
23
  faraday (~> 0.8, < 0.10)
25
- hashie (>= 1.2)
24
+ hashie (>= 3.2)
26
25
  multi_json (>= 1.7.5, < 2.0)
27
- nokogiri (~> 1.6.0)
26
+ nokogiri (~> 1.6.3)
28
27
  oauth2
29
- hashie (2.0.5)
30
- highline (1.6.20)
31
- httpauth (0.2.0)
32
- i18n (0.6.9)
33
- jeweler (2.0.0)
28
+ hashie (3.2.0)
29
+ highline (1.6.21)
30
+ i18n (0.6.11)
31
+ jeweler (2.0.1)
34
32
  builder
35
33
  bundler (>= 1.0)
36
34
  git (>= 1.2.5)
@@ -40,38 +38,39 @@ GEM
40
38
  rake
41
39
  rdoc
42
40
  json (1.8.1)
43
- jwt (0.1.8)
44
- multi_json (>= 1.5)
45
- mime-types (1.25.1)
46
- mini_portile (0.5.2)
41
+ jwt (1.0.0)
42
+ mini_portile (0.6.0)
47
43
  minitest (4.7.5)
48
- multi_json (1.8.4)
44
+ multi_json (1.10.1)
49
45
  multi_xml (0.5.5)
50
- multipart-post (1.2.0)
51
- nokogiri (1.6.1)
52
- mini_portile (~> 0.5.0)
53
- oauth2 (0.9.2)
54
- faraday (~> 0.8)
55
- httpauth (~> 0.2)
56
- jwt (~> 0.1.4)
57
- multi_json (~> 1.0)
46
+ multipart-post (2.0.0)
47
+ nokogiri (1.6.3.1)
48
+ mini_portile (= 0.6.0)
49
+ oauth2 (1.0.0)
50
+ faraday (>= 0.8, < 0.10)
51
+ jwt (~> 1.0)
52
+ multi_json (~> 1.3)
58
53
  multi_xml (~> 0.5)
59
54
  rack (~> 1.2)
60
55
  rack (1.5.2)
61
- rake (10.1.1)
56
+ rake (10.3.2)
62
57
  rdoc (4.1.1)
63
58
  json (~> 1.4)
64
59
  shoulda (3.5.0)
65
60
  shoulda-context (~> 1.0, >= 1.0.1)
66
61
  shoulda-matchers (>= 1.4.1, < 3.0)
67
- shoulda-context (1.1.6)
68
- shoulda-matchers (2.4.0)
62
+ shoulda-context (1.2.1)
63
+ shoulda-matchers (2.6.2)
69
64
  activesupport (>= 3.0.0)
70
- thread_safe (0.1.3)
71
- atomic
72
- typhoeus (0.6.7)
73
- ethon (~> 0.6.2)
74
- tzinfo (0.3.38)
65
+ thin (1.6.2)
66
+ daemons (>= 1.0.9)
67
+ eventmachine (>= 1.0.0)
68
+ rack (>= 1.0.0)
69
+ thread_safe (0.3.4)
70
+ turn (0.9.7)
71
+ ansi
72
+ minitest (~> 4)
73
+ tzinfo (0.3.40)
75
74
 
76
75
  PLATFORMS
77
76
  ruby
@@ -82,4 +81,5 @@ DEPENDENCIES
82
81
  nokogiri (>= 1.6.1)
83
82
  rack
84
83
  shoulda
85
- typhoeus (>= 0.6.7)
84
+ thin
85
+ turn
data/README.rdoc CHANGED
@@ -3,6 +3,8 @@
3
3
  A gem that provides a rack middleware to interact with {Superfeedr}[http://superfeedr.com/]'s PubSubHubbub API. It let you *subscribe*, *unsubscribe* and *receive* incoming feed notifications.
4
4
  This should work with any Rack-compatible framework, as well as fully managed platforms like {Heroku}[http://www.heroku.com/].
5
5
 
6
+ Warning : version 0.3 (July 2014) introduces a lot of breaking changes compared to previous versions. Upgrade carefully!
7
+
6
8
  == Installing
7
9
 
8
10
  gem install rack-superfeedr
@@ -11,7 +13,7 @@ This should work with any Rack-compatible framework, as well as fully managed pl
11
13
 
12
14
  You first need a {subscriber account}[http://superfeedr.com/subscriber] with Superfeedr.
13
15
 
14
- *Warning* : your web application needs to be accessible for Superfeedr to send notifications. If you want to test it locally, we suggest you use it with a tool like {showoff}[https://showoff.io/] which will make your local port accessible from the outside world.
16
+ *Warning* : your web application needs to be accessible for Superfeedr to send notifications. If you want to test it locally, we suggest you use it with a tool like {Passsageway}[https://www.runscope.com/docs/passageway] which will make your local port accessible from the outside world.
15
17
 
16
18
  === Sinatra
17
19
 
@@ -23,12 +25,10 @@ Tested with Rails 3.2, but should work with any Rails version that supports Rack
23
25
 
24
26
  Install the gem (using bundle), and then in <code>config/application.rb</code>, inside the <code>class Application < Rails::Application</code> block, just add the following line.
25
27
 
26
- config.middleware.use Rack::Superfeedr, { :host => DOMAIN, :login => SUPERFEEDR_LOGIN, :password => SUPERFEEDR_PASSWORD} do |superfeedr|
27
- Superfeedr = superfeedr
28
+ config.middleware.use Rack::Superfeedr do |superfeedr|
29
+ ...
28
30
  end
29
31
 
30
- You can then access the Superfeedr object anywhere in your rails application with <code>YOUR_APP::Application::Superfeedr</code>.
31
-
32
32
 
33
33
  == Contributing
34
34
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
@@ -1,30 +1,44 @@
1
1
  require 'sinatra'
2
- require(File.join(File.dirname(__FILE__), '..', 'lib', 'rack-superfeedr.rb'))
2
+ require (File.join(File.dirname(__FILE__), '..', 'lib', 'rack-superfeedr.rb'))
3
3
 
4
4
 
5
- use Rack::Superfeedr, { :host => "pstx.showoff.io", :login => "julien", :password => "f8054b405e68aa2067df25fb21665bab", :format => "json", :async => false } do |superfeedr|
6
- set :superfeedr, superfeedr # so that we can use `settings.superfeedr` to access the superfeedr object in our application.
5
+ # Configure the middleware
6
+ Rack::Superfeedr.host = "37cb3f2fe113.a.passageway.io"
7
+ Rack::Superfeedr.login = "demo"
8
+ Rack::Superfeedr.password = "8ac38a53cc32f71a6445e880f76fc865"
7
9
 
8
- superfeedr.on_notification do |notification|
9
- puts notification.to_s # You probably want to persist that data in some kind of data store...
10
+
11
+ use Rack::Superfeedr do |superfeedr|
12
+
13
+ superfeedr.on_notification do |feed_id, body, url, request|
14
+ puts "------"
15
+ puts feed_id # You need to have supplied one upon subscription
16
+ puts "------"
17
+ puts body # The body of the notification, a JSON or ATOM string, based on the subscription. Use the Rack::Request object for details
18
+ puts "------"
19
+ puts url # The feed url
20
+ puts "------"
10
21
  end
11
22
 
12
23
  end
13
24
 
25
+ # Maybe serve the data you saved from Superfeedr's handler.
14
26
  get '/hi' do
15
- "Hello World!" # Maybe serve the data you saved from Superfeedr's handler.
27
+ "Hello World!"
16
28
  end
17
29
 
30
+ # Subscription
31
+ # Will subscribe to "http://push-pub.appspot.com/feed" and retrieve past items
32
+ # The block will yield the result: its body (useful for error or when retrieveing, a success flag and the Net::HTTP::Post)
18
33
  get '/subscribe' do
19
- subscription = settings.superfeedr.subscribe("http://push-pub.appspot.com/feed", 9999, { verbose: true, retrieve: true })
20
- if !subscription
21
- settings.superfeedr.error
22
- else
23
- 'Subscribed'
34
+ Rack::Superfeedr.subscribe("http://push-pub.appspot.com/feed", 9999, {retrieve: true }) do |body, success, response|
35
+ body
24
36
  end
25
37
  end
26
38
 
39
+ # Unsubscription
40
+ # Wull unsubscribe
27
41
  get '/unsubscribe' do
28
- settings.superfeedr.unsubscribe("http://push-pub.appspot.com/feed")
42
+ Rack::Superfeedr.unsubscribe("http://push-pub.appspot.com/feed")
29
43
  end
30
44
 
@@ -1,8 +1,6 @@
1
1
  require 'base64'
2
- require 'typhoeus'
3
- require 'json'
4
- require 'nokogiri'
5
-
2
+ require 'net/http'
3
+ require 'uri'
6
4
 
7
5
  module Rack
8
6
 
@@ -12,85 +10,79 @@ module Rack
12
10
  # using the PubSubHubbub API.
13
11
  class Superfeedr
14
12
 
15
- SUPERFEEDR_ENDPOINT = "https://push.superfeedr.com"
13
+ @@superfeedr_endpoint = "https://push.superfeedr.com/"
14
+ @@port = 80
15
+ @@host = 'my-app.com'
16
+ @@base_path = '/superfeedr/feed/'
17
+ @@scheme = 'http'
18
+ @@login = nil
19
+ @@password = nil
16
20
 
17
- ##
18
- # Shows the latest error received by the API.
19
- # Useful when a subscription or unsubscription request fails.
20
- def error
21
- @error
21
+ def self.superfeedr_endpoint= _superfeedr_endpoint
22
+ @@superfeedr_endpoint = _superfeedr_endpoint
22
23
  end
23
24
 
24
- ##
25
- # Subscribe you to a url. id is optional, but recommanded has a unique identifier for this url. It will be used to help you identify which feed
26
- # is concerned by a notification.
27
- # The optional block will be called to let you confirm the subscription (or not).
28
- # It returns true if the subscription was successful (or will be confirmed if you used async => true in the options), false otherwise.
29
- # You can also pass an opts third argument that will be merged with the options used in Typhoeus's Request (https://github.com/dbalatero/typhoeus)
30
- # A useful option is :verbose => true for example.
31
- # If you supply a retrieve option, the content of the feed will be retrieved from Superfeedr as well and your on_notification callback will
32
- # will be called with the content of the feed.
33
- def subscribe(url, id = nil, opts = {}, &block)
34
- feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
35
- if block
36
- @verifications[feed_id] ||= {}
37
- @verifications[feed_id]['subscribe'] = block
38
- end
39
- endpoint = opts[:hub] || SUPERFEEDR_ENDPOINT
40
- opts.delete(:hub)
25
+ def self.port= _port
26
+ @@port = _port
27
+ end
41
28
 
42
- retrieve = opts[:retrieve] || false
43
- opts.delete(:retrieve)
29
+ def self.host= _host
30
+ @@host = _host
31
+ end
44
32
 
33
+ def self.base_path= _base_path
34
+ @@base_path = _base_path
35
+ end
45
36
 
46
- if endpoint == SUPERFEEDR_ENDPOINT
47
- opts[:userpwd] = "#{@params[:login]}:#{@params[:password]}"
48
- end
37
+ def self.scheme= _scheme
38
+ @@scheme = _scheme
39
+ end
49
40
 
41
+ def self.login= _login
42
+ @@login = _login
43
+ end
50
44
 
51
- opts = opts.merge({
52
- :params => {
53
- :'hub.mode' => 'subscribe',
54
- :'hub.topic' => url,
55
- :'hub.callback' => generate_callback(url, feed_id)
56
- },
57
- :headers => {
58
- :Accept => @params[:format] == "json" ? "application/json" : "application/atom+xml"
59
- }
60
- })
45
+ def self.password= _password
46
+ @@password = _password
47
+ end
61
48
 
62
- if retrieve
63
- opts[:params][:retrieve] = true
49
+ ##
50
+ # Subscribe you to a url. id is optional but strongly recommanded has a unique identifier for this url. It will be used to help you identify which feed
51
+ # is concerned by a notification.
52
+ # A 3rd options argument can be supplied with
53
+ # - retrive => true if you want to retrieve the previous items in the feed
54
+ # - format => 'json' or 'atom' to specify the format of the notifications, defaults to atom
55
+ # - secret => a secret string used to compyte HMAC signatures so you can check that the data is coming from Superfeedr
56
+ # - sync => true (defaults to false) if you want to perfrom a verification of intent syncrhonously
57
+ # - async => true (defaults to false) if you want to perfrom a verification of intent asyncrhonously
58
+ # - hub => if you want to use an explicit hub, defaults to Superfeedr's http://push.superfeedr.com
59
+ # It yields 3 arguments to a block:
60
+ # - body of the response (useful if you used the retrieve option)
61
+ # - success flag
62
+ # - response (useful to debug failed requests mostly)
63
+ def self.subscribe(url, id = nil, opts = {}, &blk)
64
+ endpoint = opts[:hub] || @@superfeedr_endpoint
65
+ request = prep_request(url, id, endpoint, opts)
66
+
67
+ if opts[:retrieve]
68
+ request['retrieve'] = true
69
+ end
70
+
71
+ if opts[:format] == "json"
72
+ request['format'] = "json"
64
73
  end
65
74
 
66
- opts[:params][:'hub.verify'] = @params[:async] ? 'async' : 'sync'
67
-
68
- response = ::Typhoeus::Request.post(endpoint, opts)
69
-
70
- @error = response.body
71
- if !retrieve
72
- @params[:async] && response.code == 202 || response.code == 204 # We return true to indicate the status.
75
+ if opts[:secret]
76
+ request['hub.secret'] = opts[:secret]
73
77
  else
74
-
75
- if response.code == 200
76
-
77
- if @params[:format] != "json"
78
- content = Nokogiri.XML(response.body)
79
- else
80
- content = JSON.parse(response.body)
81
- end
82
- # Let's now send that data back to the user.
83
- if defined? Hashie::Mash
84
- info = Hashie::Mash.new(req: req, body: body)
85
- end
86
- if !@callback.call(content, feed_id, info)
87
- # We need to unsubscribe the user
88
- end
89
- true
90
- else
91
- false
92
- end
78
+ request['hub.secret'] = "WHAT DO WE PICK? A UNIQUE SCRET THE CALLBACK? SO WE CAN USE THAT ON NOTIFS?"
93
79
  end
80
+
81
+ request['hub.mode'] = 'subscribe'
82
+
83
+ response = http_post(endpoint, request)
84
+
85
+ blk.call(response.body, opts[:async] && Integer(response.code) == 202 || Integer(response.code) == 204 || opts[:retrieve] && Integer(response.code) == 200, response) if blk
94
86
  end
95
87
 
96
88
  ##
@@ -98,108 +90,94 @@ module Rack
98
90
  # The optional block will be called to let you confirm the subscription (or not). This is not applicable for if you use params[:async] => true
99
91
  # It returns true if the unsubscription was successful (or will be confirmed if you used async => true in the options), false otherwise
100
92
  # You can also pass an opts third argument that will be merged with the options used in Typhoeus's Request (https://github.com/dbalatero/typhoeus)
101
- # A useful option is :verbose => true for example.
102
- def unsubscribe(url, id = nil, opts = {}, &block)
103
- feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
104
- if block
105
- @verifications[feed_id] ||= {}
106
- @verifications[feed_id]['unsubscribe'] = block
107
- end
108
- response = ::Typhoeus::Request.post(SUPERFEEDR_ENDPOINT,
109
- opts.merge({
110
- :params => {
111
- :'hub.mode' => 'unsubscribe',
112
- :'hub.verify' => @params[:async] ? 'async' : 'sync',
113
- :'hub.topic' => url,
114
- :'hub.callback' => generate_callback(url, feed_id)
115
- },
116
- :userpwd => "#{@params[:login]}:#{@params[:password]}"
117
- }))
118
- @error = response.body
119
- @params[:async] && response.code == 202 || response.code == 204 # We return true to indicate the status.
93
+
94
+ ##
95
+ # Subscribe you to a url. id needs to match the id you used to subscribe.
96
+ # A 3rd options argument can be supplied with
97
+ # - sync => true (defaults to false) if you want to perfrom a verification of intent syncrhonously
98
+ # - async => true (defaults to false) if you want to perfrom a verification of intent asyncrhonously
99
+ # - hub => if you want to use an explicit hub, defaults to Superfeedr's http://push.superfeedr.com
100
+ # It yields 3 arguments to a block:
101
+ # - body of the response (useful to debug failed notifications)
102
+ # - success flag
103
+ # - response (useful to debug failed requests mostly)
104
+ def self.unsubscribe(url, id = nil, opts = {}, &blk)
105
+ endpoint = opts[:hub] || @@superfeedr_endpoint
106
+ request = prep_request(url, id, endpoint, opts)
107
+
108
+ request['hub.mode'] = 'unsubscribe'
109
+
110
+ response = http_post(endpoint, request)
111
+
112
+ blk.call(response.body, opts[:async] && Integer(response.code) == 202 || Integer(response.code) == 204, response) if blk
120
113
  end
121
114
 
122
115
  ##
123
- # This allows you to define what happens with the notifications. The block passed in argument is called for each notification, with 2 arguments
124
- # - the payload itself (ATOM or JSON, based on what you selected in the params)
125
- # - the id for the feed, if you used any upon subscription
116
+ # This allows you to define what happens with the notifications. The block passed in argument is called for each notification, with 4 arguments
117
+ # - feed_id (used in subscriptions)
118
+ # - body (Atom or JSON) based on subscription
119
+ # - url (optional... if the hub supports that, Superfeedr does)
120
+ # - Rack::Request object. Useful for debugging and checking signatures
126
121
  def on_notification(&block)
127
122
  @callback = block
128
123
  end
129
124
 
130
125
  ##
131
- # When using this Rack, you need to supply the following params (2nd argument):
132
- # - :host (the host for your web app. Used to build the callback urls.)
133
- # - :login
134
- # - :password
135
- # - :format (atom|json, atom being default)
136
- # - :async (true|false), false is default. You need to set that to false if you're using platforms like Heroku that may disallow concurrency.
137
- def initialize(app, params = {}, &block)
138
- raise ArgumentError, 'Missing :host in params' unless params[:host]
139
- raise ArgumentError, 'Missing :login in params' unless params[:login]
140
- raise ArgumentError, 'Missing :password in params' unless params[:password]
141
- @callback = Proc.new { |notification, feed_id|
142
- # Bh default, do nothing
143
- }
144
- @verifications = {}
145
- @params = params
146
- @params[:port] = 80 unless params[:port]
126
+ # This allows you to define what happens with verification of intents
127
+ # It's a block called with
128
+ # - mode: subscribe|unsubscribe
129
+ # - Feed id (if available/supplied upon subscription)
130
+ # - Feed url
131
+ # - request (the Rack::Request object, should probably not be used, except for debugging)
132
+ # If the block returns true, subscription will be confirmed
133
+ # If it returns false, it will be denied
134
+ def on_verification(&block)
135
+ @verification = block
136
+ end
137
+
138
+ ##
139
+ # Initializes the Rack Middleware
140
+ # Make sure you define the following class attribues before that:
141
+ # Rack::Superfeedr.superfeedr_endpoint => https://push.superfeedr.com, defaults (do not change!)
142
+ # Rack::Superfeedr.host => Host for your application, used to build callback urls
143
+ # Rack::Superfeedr.port => Port for your application, used to build callback urls, defaults to
144
+ # Rack::Superfeedr.base_path => Base path for callback urls. Defauls to '/superfeedr/feed/'
145
+ # Rack::Superfeedr.scheme => Scheme to build callback urls, defaults to 'http'
146
+ # Rack::Superfeedr.login => Superfeedr login
147
+ # Rack::Superfeedr.password => Superfeedr password
148
+ def initialize(app, &block)
147
149
  @app = app
148
- @base_path = params[:base_path] || '/superfeedr/feed/'
150
+ reset
149
151
  block.call(self)
150
152
  self
151
153
  end
152
154
 
153
- def reset(params = {})
154
- raise ArgumentError, 'Missing :host in params' unless params[:host]
155
- raise ArgumentError, 'Missing :login in params' unless params[:login]
156
- raise ArgumentError, 'Missing :password in params' unless params[:password]
157
- @params = params
158
- end
159
-
160
- def params
161
- @params
155
+ ##
156
+ # Resets.
157
+ def reset
158
+ @callback = Proc.new { |feed_id, body, url, request|
159
+ # Nothing to do by default!
160
+ }
161
+ @verification = Proc.new { |mode, feed_id, url, request|
162
+ true # Accept all by default!
163
+ }
162
164
  end
163
165
 
166
+ ##
167
+ # Called by Rack!
164
168
  def call(env)
165
169
  req = Rack::Request.new(env)
166
- if env['REQUEST_METHOD'] == 'GET' && feed_id = env['PATH_INFO'].match(/#{@base_path}(.*)/)
167
- # Verification of intent!
168
- if @verifications[feed_id[1]] && verification = @verifications[feed_id[1]][req.params['hub.mode']]
169
- # Check with the user
170
- if verification.call(req.params['hub.topic'], feed_id[1])
171
- Rack::Response.new(req.params['hub.challenge'], 200).finish
172
- else
173
- Rack::Response.new("not valid", 404).finish
174
- end
175
- else
176
- # By default, we accept all
170
+ if env['REQUEST_METHOD'] == 'GET' && feed_id = env['PATH_INFO'].match(/#{@@base_path}(.*)/)
171
+ puts "----"
172
+ puts req.params['hub.mode'], feed_id[1], req.params['hub.topic']
173
+ puts "----"
174
+ if @verification.call(req.params['hub.mode'], feed_id[1], req.params['hub.topic'], req)
177
175
  Rack::Response.new(req.params['hub.challenge'], 200).finish
178
- end
179
- elsif env['REQUEST_METHOD'] == 'POST' && feed_id = env['PATH_INFO'].match(/#{@base_path}(.*)/)
180
- # Notification
181
- content = nil
182
- # Body is a stream, not a string, so capture it once and save it
183
- body = req.body.read
184
- if env["CONTENT_TYPE"]
185
- content_type = env["CONTENT_TYPE"].split(";").first
186
176
  else
187
- content_type = "application/atom+xml" #default?
188
- end
189
- if content_type == "application/json"
190
- # Let's parse the body as JSON
191
- content = JSON.parse(body)
192
- elsif content_type == "application/atom+xml"
193
- # Let's parse the body as ATOM using nokogiri
194
- content = Nokogiri.XML(body)
195
- end
196
- # Let's now send that data back to the user.
197
- if defined? Hashie::Mash
198
- info = Hashie::Mash.new(req: req, body: body)
199
- end
200
- if !@callback.call(content, feed_id[1], info)
201
- # We need to unsubscribe the user
177
+ Rack::Response.new("not valid", 404).finish
202
178
  end
179
+ elsif env['REQUEST_METHOD'] == 'POST' && feed_id = env['PATH_INFO'].match(/#{@@base_path}(.*)/)
180
+ @callback.call(feed_id[1], req.body.read, req.env['HTTP_X_PUBSUBHUBBUB_TOPIC'], req)
203
181
  Rack::Response.new("Thanks!", 200).finish
204
182
  else
205
183
  @app.call(env)
@@ -208,11 +186,45 @@ module Rack
208
186
 
209
187
  protected
210
188
 
211
- def generate_callback(url, feed_id)
212
- scheme = params[:scheme] || 'http'
213
- URI::HTTP.build({:scheme => scheme, :host => @params[:host], :path => "#{@base_path}#{feed_id}", :port => @params[:port] }).to_s
189
+ def self.prep_request(url, id, endpoint, opts)
190
+ feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
191
+
192
+ request = {
193
+ 'hub.topic' => url,
194
+ 'hub.callback' => generate_callback(url, feed_id)
195
+ }
196
+
197
+ if endpoint == @@superfeedr_endpoint && @@login && @@password
198
+ request['authorization'] = Base64.encode64( "#{@@login}:#{@@password}" ).chomp
199
+ end
200
+
201
+ if opts[:async]
202
+ request['hub.verify'] = 'async'
203
+ end
204
+
205
+ if opts[:sync]
206
+ request['hub.verify'] = 'sync'
207
+ end
208
+
209
+ request
214
210
  end
215
211
 
216
- end
212
+ def self.http_post(url, opts)
213
+ uri = URI.parse URI.encode(url)
214
+ uri.path=='/' if uri.path.empty?
215
+ http = Net::HTTP.new(uri.host, uri.port)
216
+ http.use_ssl = true
217
+ request = Net::HTTP::Post.new uri.request_uri
218
+ request.set_form_data (opts||{})
219
+ http.request(request)
220
+ end
217
221
 
222
+ def self.generate_callback(url, feed_id)
223
+ if @@scheme == "https"
224
+ URI::HTTPS.build({:scheme => @@scheme, :host => @@host, :path => "#{@@base_path}#{feed_id}", :port => @@port }).to_s
225
+ else
226
+ URI::HTTP.build({:scheme => @@scheme, :host => @@host, :path => "#{@@base_path}#{feed_id}", :port => @@port }).to_s
227
+ end
228
+ end
229
+ end
218
230
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rack-superfeedr"
8
- s.version = "0.2.1"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["julien51"]
12
- s.date = "2014-06-10"
12
+ s.date = "2014-07-29"
13
13
  s.description = "A gem that provides a rack middleware to interract with Superfeedr's API. "
14
14
  s.email = "julien@superfeedr.com"
15
15
  s.extra_rdoc_files = [
@@ -33,7 +33,7 @@ Gem::Specification.new do |s|
33
33
  s.homepage = "http://rubygems.org/gems/rack-superfeedr"
34
34
  s.licenses = ["MIT"]
35
35
  s.require_paths = ["lib"]
36
- s.rubygems_version = "1.8.25"
36
+ s.rubygems_version = "1.8.23.2"
37
37
  s.summary = "A gem that provides a rack middleware to interract with Superfeedr's API."
38
38
 
39
39
  if s.respond_to? :specification_version then
@@ -42,25 +42,28 @@ Gem::Specification.new do |s|
42
42
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
43
  s.add_runtime_dependency(%q<rack>, [">= 0"])
44
44
  s.add_runtime_dependency(%q<nokogiri>, [">= 1.6.1"])
45
- s.add_runtime_dependency(%q<typhoeus>, [">= 0.6.7"])
46
45
  s.add_development_dependency(%q<shoulda>, [">= 0"])
46
+ s.add_development_dependency(%q<turn>, [">= 0"])
47
47
  s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
48
48
  s.add_development_dependency(%q<jeweler>, [">= 1.6.4"])
49
+ s.add_development_dependency(%q<thin>, [">= 0"])
49
50
  else
50
51
  s.add_dependency(%q<rack>, [">= 0"])
51
52
  s.add_dependency(%q<nokogiri>, [">= 1.6.1"])
52
- s.add_dependency(%q<typhoeus>, [">= 0.6.7"])
53
53
  s.add_dependency(%q<shoulda>, [">= 0"])
54
+ s.add_dependency(%q<turn>, [">= 0"])
54
55
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
55
56
  s.add_dependency(%q<jeweler>, [">= 1.6.4"])
57
+ s.add_dependency(%q<thin>, [">= 0"])
56
58
  end
57
59
  else
58
60
  s.add_dependency(%q<rack>, [">= 0"])
59
61
  s.add_dependency(%q<nokogiri>, [">= 1.6.1"])
60
- s.add_dependency(%q<typhoeus>, [">= 0.6.7"])
61
62
  s.add_dependency(%q<shoulda>, [">= 0"])
63
+ s.add_dependency(%q<turn>, [">= 0"])
62
64
  s.add_dependency(%q<bundler>, [">= 1.0.0"])
63
65
  s.add_dependency(%q<jeweler>, [">= 1.6.4"])
66
+ s.add_dependency(%q<thin>, [">= 0"])
64
67
  end
65
68
  end
66
69
 
data/test/helper.rb CHANGED
@@ -13,6 +13,3 @@ require 'shoulda'
13
13
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
14
  $LOAD_PATH.unshift(File.dirname(__FILE__))
15
15
  require 'rack-superfeedr'
16
-
17
- class Test::Unit::TestCase
18
- end
@@ -1,7 +1,131 @@
1
- require 'helper'
1
+ require 'rack'
2
+ require_relative 'helper.rb'
3
+
4
+ # To run tests locally, we're using runscope's passageway which proxies requests inside the firewall. (make sure you bind to port 4567)
5
+ HOST = '37cb3f2fe113.a.passageway.io'
6
+ PORT = 80
7
+ # Also, we need superfeedr credentials.
8
+ LOGIN = 'demo'
9
+ PASSWORD = '8ac38a53cc32f71a6445e880f76fc865'
10
+
11
+
12
+ class MyRackApp
13
+ def call(env)
14
+ puts env.inspect
15
+ [ 200, {'Content-Type' => 'text/plain'}, ['hello world'] ]
16
+ end
17
+ end
18
+
19
+ def notified(url, feed_id, details)
20
+ puts url, feed_id, details
21
+ end
22
+
23
+ # Run an app in a thread
24
+ Thread.new do
25
+ Rack::Handler::WEBrick.run(Rack::Superfeedr.new(MyRackApp.new) do |superfeedr|
26
+
27
+ superfeedr.on_verification do |mode, feed_id, url, request|
28
+ if mode == 'subscribe' && feed_id == 'accept-subscribe'
29
+ true
30
+ elsif mode == 'unsubscribe' && feed_id == 'accept-unsubscribe'
31
+ true
32
+ else
33
+ false
34
+ end
35
+ end
36
+
37
+ superfeedr.on_notification do |url, feed_id, details|
38
+ notified(url, feed_id, details)
39
+ end
40
+
41
+
42
+ end, :Port => 4567)
43
+ end
44
+ sleep 3
45
+
46
+ # Configure the middleware
47
+ Rack::Superfeedr.host = HOST
48
+ Rack::Superfeedr.port = PORT
49
+ Rack::Superfeedr.login = LOGIN
50
+ Rack::Superfeedr.password = PASSWORD
2
51
 
3
52
  class TestRackSuperfeedr < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
53
+
54
+ context "Subscribing" do
55
+
56
+ should "yield true with a simple subscribe" do
57
+ Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', '12345') do |body, success, response|
58
+ success || flunk("Fail")
59
+ end
60
+ end
61
+
62
+ should "support sync mode and call the verification callback before yielding true" do
63
+ Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', 'accept-subscribe', {:sync => true}) do |body, success, response|
64
+ puts body
65
+ success || flunk("Fail")
66
+ end
67
+ end
68
+
69
+ should "support sync mode and call the verification callback before yielding false if verification fails" do
70
+ Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', 'refuse-subscribe', {:sync => true}) do |body, success, response|
71
+ !success || flunk('Fail')
72
+ end
73
+ end
74
+
75
+ should "support async mode and yield true" do
76
+ Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', 'refuse-subscribe', {:async => true}) do |body, success, response|
77
+ success || flunk("Fail")
78
+ end
79
+ end
80
+
81
+ should "return the content of Atom subscriptions with retrieve" do
82
+ Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', '1234', {:retrieve => true}) do |body, success, response|
83
+ assert_equal "application/atom+xml", response['Content-Type']
84
+ success || flunk("Fail")
85
+ assert body # Some XML
86
+ end
87
+ end
88
+
89
+ should "return the content of Json subscriptions with retrieve" do
90
+ response = Rack::Superfeedr.subscribe('http://push-pub.appspot.com/feed', '1234', {:format => "json", :retrieve => true}) do |body, success, response|
91
+ assert_equal "application/json; charset=utf-8", response['Content-Type']
92
+ success || flunk("Fail")
93
+ assert body
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ context "Unsubscribing" do
100
+ should 'successfully unsubscribe with 204 when not using sync nor asyc' do
101
+ Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', '12345') do |body, success, response|
102
+ success || flunk("Fail")
103
+ end
104
+ end
105
+
106
+ should 'successfully unsubscribe with 204 when using sync when verification yields true' do
107
+ Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', 'accept-unsubscribe', {:sync => true}) do |body, success, response|
108
+ success || flunk("Fail")
109
+ end
110
+ end
111
+
112
+ should 'fail to unsubscribe with 204 when using sync when verification yields false' do
113
+ Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', 'refuse-unsubscribe', {:sync => true}) do |body, success, response|
114
+ !success || flunk('Fail')
115
+ end
116
+ end
117
+
118
+ should 'return 202 when using async' do
119
+ Rack::Superfeedr.unsubscribe('http://push-pub.appspot.com/feed', 'accept-unsubscribe', {:async => true}) do |body, success, response|
120
+ success || flunk("Fail")
121
+ end
122
+ end
123
+ end
124
+
125
+ context "Notifications" do
126
+ should 'handle json notifications'
127
+ should 'handle atom notifications'
128
+ should 'handle error notification (with no entry in them)'
129
+ end
130
+
7
131
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-superfeedr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-10 00:00:00.000000000 Z
12
+ date: 2014-07-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -44,23 +44,23 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: 1.6.1
46
46
  - !ruby/object:Gem::Dependency
47
- name: typhoeus
47
+ name: shoulda
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: 0.6.7
54
- type: :runtime
53
+ version: '0'
54
+ type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: 0.6.7
61
+ version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: shoulda
63
+ name: turn
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: 1.6.4
110
+ - !ruby/object:Gem::Dependency
111
+ name: thin
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
110
126
  description: ! 'A gem that provides a rack middleware to interract with Superfeedr''s
111
127
  API. '
112
128
  email: julien@superfeedr.com
@@ -143,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
143
159
  version: '0'
144
160
  segments:
145
161
  - 0
146
- hash: 48070522357503993
162
+ hash: 1778305000862886852
147
163
  required_rubygems_version: !ruby/object:Gem::Requirement
148
164
  none: false
149
165
  requirements:
@@ -152,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
168
  version: '0'
153
169
  requirements: []
154
170
  rubyforge_project:
155
- rubygems_version: 1.8.25
171
+ rubygems_version: 1.8.23.2
156
172
  signing_key:
157
173
  specification_version: 3
158
174
  summary: A gem that provides a rack middleware to interract with Superfeedr's API.