rack-superfeedr 0.2.1 → 0.3.0

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.
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.