Class: Rack::Superfeedr

Inherits:
Object
  • Object
show all
Defined in:
lib/rack-superfeedr.rb

Overview

This is a Rack Middleware that can be used in your rack-compatible web framework (Rails, Sinatra…) to perform subscriptions over at superfeedr using the PubSubHubbub API.

Constant Summary collapse

SUPERFEEDR_ENDPOINT =
"https://push.superfeedr.com"

Instance Method Summary collapse

Constructor Details

#initialize(app, params = {}, &block) ⇒ Superfeedr

When using this Rack, you need to supply the following params (2nd argument):

  • :host (the host for your web app. Used to build the callback urls.)

  • :login

  • :password

  • :format (atom|json, atom being default)

  • :async (true|false), false is default. You need to set that to false if you’re using platforms like Heroku that may disallow concurrency.

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rack-superfeedr.rb', line 102

def initialize(app, params = {}, &block)
  raise ArgumentError, 'Missing :host in params' unless params[:host]
  raise ArgumentError, 'Missing :login in params' unless params[:login]
  raise ArgumentError, 'Missing :password in params' unless params[:password]
  @callback = Proc.new { |notification, feed_id|
    # Bh default, do nothing
  }
  @verifications = {}
  @params = params
  @params[:port] = 80 unless params[:port]
  @app = app
  @base_path = params[:base_path] || '/superfeedr/feed/'
  block.call(self)
  self
end

Instance Method Details

#call(env) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/rack-superfeedr.rb', line 129

def call(env)
  req = Rack::Request.new(env)
  if env['REQUEST_METHOD'] == 'GET' && feed_id = env['PATH_INFO'].match(/#{@base_path}(.*)/)
    # Verification of intent!
    if @verifications[feed_id[1]] && verification = @verifications[feed_id[1]][req.params['hub.mode']]
      # Check with the user
      if verification.call(req.params['hub.topic'], feed_id[1])
        Rack::Response.new(req.params['hub.challenge'], 200).finish
      else
        Rack::Response.new("not valid", 404).finish
      end
    else
      # By default, we accept all
      Rack::Response.new(req.params['hub.challenge'], 200).finish
    end
  elsif env['REQUEST_METHOD'] == 'POST' && feed_id = env['PATH_INFO'].match(/#{@base_path}(.*)/)
    # Notification
    content = nil
    # Body is a stream, not a string, so capture it once and save it
    body = req.body.read
    if env["CONTENT_TYPE"]
      content_type = env["CONTENT_TYPE"].split(";").first
    else
      content_type = "application/atom+xml" #default?
    end
    if content_type == "application/json"
      # Let's parse the body as JSON
      content = JSON.parse(body)
    elsif content_type == "application/atom+xml"
      # Let's parse the body as ATOM using nokogiri
      content = Nokogiri.XML(body)
    end
    # Let's now send that data back to the user.
    info = Hashie::Mash.new(req: req, body: body)
    if !@callback.call(content, feed_id[1], info)
      # We need to unsubscribe the user
    end
    Rack::Response.new("Thanks!", 200).finish
  else
    @app.call(env)
  end
end

#errorObject

Shows the latest error received by the API. Useful when a subscription or unsubscription request fails.



20
21
22
# File 'lib/rack-superfeedr.rb', line 20

def error
  @error
end

#on_notification(&block) ⇒ Object

This allows you to define what happens with the notifications. The block passed in argument is called for each notification, with 2 arguments

  • the payload itself (ATOM or JSON, based on what you selected in the params)

  • the id for the feed, if you used any upon subscription



91
92
93
# File 'lib/rack-superfeedr.rb', line 91

def on_notification(&block)
  @callback = block
end

#paramsObject



125
126
127
# File 'lib/rack-superfeedr.rb', line 125

def params
  @params
end

#reset(params = {}) ⇒ Object

Raises:

  • (ArgumentError)


118
119
120
121
122
123
# File 'lib/rack-superfeedr.rb', line 118

def reset(params = {})
  raise ArgumentError, 'Missing :host in params' unless params[:host]
  raise ArgumentError, 'Missing :login in params' unless params[:login]
  raise ArgumentError, 'Missing :password in params' unless params[:password]
  @params = params
end

#subscribe(url, id = nil, opts = {}, &block) ⇒ Object

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 is concerned by a notification. The optional block will be called to let you confirm the subscription (or not). It returns true if the subscription was successful (or will be confirmed if you used async => true in the options), false otherwise. You can also pass an opts third argument that will be merged with the options used in Typhoeus’s Request (github.com/dbalatero/typhoeus) A useful option is :verbose => true for example.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/rack-superfeedr.rb', line 31

def subscribe(url, id = nil, opts = {}, &block)
  feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
  if block
    @verifications[feed_id] ||= {}
    @verifications[feed_id]['subscribe'] = block
  end
  endpoint = opts[:hub] || SUPERFEEDR_ENDPOINT
  opts.delete(:hub)

  if endpoint == SUPERFEEDR_ENDPOINT
    opts[:userpwd] = "#{@params[:login]}:#{@params[:password]}"
  end

  response = ::Typhoeus::Request.post(endpoint,
  opts.merge({
    :params => {
      :'hub.mode' => 'subscribe',
      :'hub.verify' => @params[:async] ? 'async' : 'sync',
      :'hub.topic' => url,
      :'hub.callback' =>  generate_callback(url, feed_id)
    },
    :headers => {
      :Accept => @params[:format] == "json" ? "application/json" : "application/atom+xml"
    }
  }))

  @error = response.body
  @params[:async] && response.code == 202 || response.code == 204 # We return true to indicate the status.
end

#unsubscribe(url, id = nil, opts = {}, &block) ⇒ Object

Unsubscribes a url. If you used an id for the susbcription, you need to use _the same_. The optional block will be called to let you confirm the subscription (or not). This is not applicable for if you use params => true It returns true if the unsubscription was successful (or will be confirmed if you used async => true in the options), false otherwise You can also pass an opts third argument that will be merged with the options used in Typhoeus’s Request (github.com/dbalatero/typhoeus) A useful option is :verbose => true for example.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rack-superfeedr.rb', line 67

def unsubscribe(url, id = nil, opts = {}, &block)
  feed_id = "#{id ? id : Base64.urlsafe_encode64(url)}"
  if block
    @verifications[feed_id] ||= {}
    @verifications[feed_id]['unsubscribe'] = block
  end
  response = ::Typhoeus::Request.post(SUPERFEEDR_ENDPOINT,
  opts.merge({
    :params => {
      :'hub.mode' => 'unsubscribe',
      :'hub.verify' => @params[:async] ? 'async' : 'sync',
      :'hub.topic' => url,
      :'hub.callback' =>  generate_callback(url, feed_id)
    },
    :userpwd => "#{@params[:login]}:#{@params[:password]}"
  }))
  @error = response.body
  @params[:async] && response.code == 202 || response.code == 204 # We return true to indicate the status.
end