pusher 0.8.5 → 0.9.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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 New Bamboo
1
+ Copyright (c) 2010-2011 Pusher
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -66,6 +66,11 @@ The Pusher Gem also deals with signing requests for authenticated private channe
66
66
 
67
67
  Read more about private channels in [the docs](http://pusher.com/docs/client_api_guide/client_channels#subscribe-private-channels) and under {Pusher::Channel#authenticate}.
68
68
 
69
+ WebHooks
70
+ --------
71
+
72
+ See {Pusher::WebHook}
73
+
69
74
  Developing
70
75
  ----------
71
76
 
@@ -73,8 +78,3 @@ Use bundler in order to run specs with the correct dependencies.
73
78
 
74
79
  bundle
75
80
  bundle exec rspec spec/*_spec.rb
76
-
77
- Copyright
78
- ---------
79
-
80
- Copyright (c) 2010 New Bamboo. See LICENSE for details.
data/lib/pusher.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  autoload 'Logger', 'logger'
2
2
  require 'uri'
3
+ require 'pusher/client'
3
4
 
4
5
  # Used for configuring API credentials and creating Channel objects
5
6
  #
@@ -19,89 +20,51 @@ module Pusher
19
20
  class HTTPError < Error; attr_accessor :original_error; end
20
21
 
21
22
  class << self
22
- attr_accessor :scheme, :host, :port
23
- attr_writer :logger
24
- attr_accessor :app_id, :key, :secret
23
+ extend Forwardable
25
24
 
26
- # @private
27
- def logger
28
- @logger ||= begin
29
- log = Logger.new($stdout)
30
- log.level = Logger::INFO
31
- log
32
- end
33
- end
25
+ def_delegators :default_client, :scheme, :host, :port, :app_id, :key, :secret
26
+ def_delegators :default_client, :scheme, :host=, :port=, :app_id=, :key=, :secret=
34
27
 
35
- # @private
36
- def authentication_token
37
- Signature::Token.new(@key, @secret)
38
- end
28
+ def_delegators :default_client, :authentication_token, :url
29
+ def_delegators :default_client, :encrypted=, :url=
39
30
 
40
- # @private Builds a connection url for Pusherapp
41
- def url
42
- URI::Generic.build({
43
- :scheme => self.scheme,
44
- :host => self.host,
45
- :port => self.port,
46
- :path => "/apps/#{self.app_id}"
47
- })
48
- end
31
+ attr_writer :logger
49
32
 
50
- # Configure Pusher connection by providing a url rather than specifying
51
- # scheme, key, secret, and app_id separately.
33
+ # Return a channel by name
52
34
  #
53
35
  # @example
54
- # Pusher.url = http://KEY:SECRET@api.pusherapp.com/apps/APP_ID
55
- #
56
- def url=(url)
57
- uri = URI.parse(url)
58
- self.app_id = uri.path.split('/').last
59
- self.key = uri.user
60
- self.secret = uri.password
61
- self.host = uri.host
62
- self.port = uri.port
36
+ # Pusher['my-channel']
37
+ # @return [Channel]
38
+ # @raise [ConfigurationError] unless key, secret and app_id have been
39
+ # configured
40
+ def [](channel_name)
41
+ begin
42
+ default_client[channel_name]
43
+ rescue ConfigurationError
44
+ raise ConfigurationError, 'Missing configuration: please check that Pusher.key, Pusher.secret and Pusher.app_id are configured.'
45
+ end
63
46
  end
64
47
 
65
- # Configure whether Pusher API calls should be made over SSL
66
- # (default false)
67
- #
68
- # @example
69
- # Pusher.encrypted = true
70
- #
71
- def encrypted=(boolean)
72
- Pusher.scheme = boolean ? 'https' : 'http'
73
- # Configure port if it hasn't already been configured
74
- Pusher.port ||= boolean ? 443 : 80
48
+ def logger
49
+ @logger ||= begin
50
+ log = Logger.new($stdout)
51
+ log.level = Logger::INFO
52
+ log
53
+ end
75
54
  end
76
55
 
77
56
  private
78
57
 
79
- def configured?
80
- host && scheme && key && secret && app_id
58
+ def default_client
59
+ @default_client ||= Pusher::Client.new
81
60
  end
82
61
  end
83
62
 
84
- # Defaults
85
- self.scheme = 'http'
86
- self.host = 'api.pusherapp.com'
87
-
88
63
  if ENV['PUSHER_URL']
89
64
  self.url = ENV['PUSHER_URL']
90
65
  end
91
-
92
- # Return a channel by name
93
- #
94
- # @example
95
- # Pusher['my-channel']
96
- # @return [Channel]
97
- # @raise [ConfigurationError] unless key, secret and app_id have been
98
- # configured
99
- def self.[](channel_name)
100
- raise ConfigurationError, 'Missing configuration: please check that Pusher.key, Pusher.secret and Pusher.app_id are configured.' unless configured?
101
- @channels ||= {}
102
- @channels[channel_name.to_s] ||= Channel.new(url, channel_name)
103
- end
104
66
  end
105
67
 
106
68
  require 'pusher/channel'
107
- require 'pusher/request'
69
+ require 'pusher/request'
70
+ require 'pusher/webhook'
@@ -6,10 +6,11 @@ module Pusher
6
6
  class Channel
7
7
  attr_reader :name
8
8
 
9
- def initialize(base_url, name)
9
+ def initialize(base_url, name, client = Pusher)
10
10
  @uri = base_url.dup
11
11
  @uri.path = @uri.path + "/channels/#{name}/"
12
12
  @name = name
13
+ @client = client
13
14
  end
14
15
 
15
16
  # Trigger event asynchronously using EventMachine::HttpRequest
@@ -69,7 +70,7 @@ module Pusher
69
70
  # @raise [Pusher::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
70
71
  #
71
72
  def stats
72
- request = Pusher::Request.new(:get, @uri + 'stats', {})
73
+ request = Pusher::Request.new(:get, @uri + 'stats', {}, nil, nil, @client)
73
74
  return request.send_sync
74
75
  end
75
76
 
@@ -89,7 +90,7 @@ module Pusher
89
90
 
90
91
  string_to_sign = [socket_id, name, custom_string].compact.map{|e|e.to_s}.join(':')
91
92
  Pusher.logger.debug "Signing #{string_to_sign}"
92
- token = Pusher.authentication_token
93
+ token = @client.authentication_token
93
94
  signature = HMAC::SHA256.hexdigest(token.secret, string_to_sign)
94
95
 
95
96
  return "#{token.key}:#{signature}"
@@ -147,7 +148,7 @@ module Pusher
147
148
  end
148
149
  end
149
150
 
150
- request = Pusher::Request.new(:post, @uri + 'events', params, body)
151
+ request = Pusher::Request.new(:post, @uri + 'events', params, body, nil, @client)
151
152
  end
152
153
  end
153
154
  end
@@ -0,0 +1,81 @@
1
+ require 'signature'
2
+
3
+ module Pusher
4
+ class Client
5
+ attr_accessor :scheme, :host, :port, :app_id, :key, :secret
6
+
7
+ # Initializes the client object.
8
+ def initialize(options = {})
9
+ options = {
10
+ scheme: 'http',
11
+ host: 'api.pusherapp.com',
12
+ port: 80,
13
+ }.merge(options)
14
+ @scheme, @host, @port, @app_id, @key, @secret = options.values_at(
15
+ :scheme, :host, :port, :app_id, :key, :secret
16
+ )
17
+ end
18
+
19
+ # @private Returns the authentication token for the client
20
+ def authentication_token
21
+ Signature::Token.new(@key, @secret)
22
+ end
23
+
24
+ # @private Builds a connection url for Pusherapp
25
+ def url
26
+ URI::Generic.build({
27
+ :scheme => @scheme,
28
+ :host => @host,
29
+ :port => @port,
30
+ :path => "/apps/#{@app_id}"
31
+ })
32
+ end
33
+
34
+ # Configure Pusher connection by providing a url rather than specifying
35
+ # scheme, key, secret, and app_id separately.
36
+ #
37
+ # @example
38
+ # Pusher.url = http://KEY:SECRET@api.pusherapp.com/apps/APP_ID
39
+ #
40
+ def url=(url)
41
+ uri = URI.parse(url)
42
+ @scheme = uri.scheme
43
+ @app_id = uri.path.split('/').last
44
+ @key = uri.user
45
+ @secret = uri.password
46
+ @host = uri.host
47
+ @port = uri.port
48
+ end
49
+
50
+ # Configure whether Pusher API calls should be made over SSL
51
+ # (default false)
52
+ #
53
+ # @example
54
+ # Pusher.encrypted = true
55
+ #
56
+ def encrypted=(boolean)
57
+ @scheme = boolean ? 'https' : 'http'
58
+ # Configure port if it hasn't already been configured
59
+ @port = boolean ? 443 : 80
60
+ end
61
+
62
+ # Return a channel by name
63
+ #
64
+ # @example
65
+ # Pusher['my-channel']
66
+ # @return [Channel]
67
+ # @raise [ConfigurationError] unless key, secret and app_id have been
68
+ # configured
69
+ def [](channel_name)
70
+ raise ConfigurationError, 'Missing client configuration: please check that key, secret and app_id are configured.' unless configured?
71
+ @channels ||= {}
72
+ @channels[channel_name.to_s] ||= Channel.new(url, channel_name, self)
73
+ end
74
+
75
+ private
76
+
77
+ def configured?
78
+ host && scheme && key && secret && app_id
79
+ end
80
+ end
81
+ end
@@ -52,9 +52,12 @@ module Pusher
52
52
 
53
53
  include QueryEncoder
54
54
 
55
- def initialize(verb, uri, params, body = nil, token = nil)
55
+ attr_reader :body, :params
56
+
57
+ def initialize(verb, uri, params, body = nil, token = nil, client = Pusher)
56
58
  @verb = verb
57
59
  @uri = uri
60
+ @client = client
58
61
 
59
62
  if body
60
63
  @body = body
@@ -62,7 +65,7 @@ module Pusher
62
65
  end
63
66
 
64
67
  request = Signature::Request.new(verb.to_s.upcase, uri.path, params)
65
- auth_hash = request.sign(token || Pusher.authentication_token)
68
+ auth_hash = request.sign(token || @client.authentication_token)
66
69
  @params = params.merge(auth_hash)
67
70
  end
68
71
 
@@ -0,0 +1,110 @@
1
+ require 'multi_json'
2
+ require 'hmac-sha2'
3
+
4
+ module Pusher
5
+ # Used to parse and authenticate WebHooks
6
+ #
7
+ # @example
8
+ # post '/webhooks' do
9
+ # webhook = Pusher::WebHook.new(request)
10
+ # if webhook.valid?
11
+ # webhook.events.each do |event|
12
+ # case event["name"]
13
+ # when 'channel_occupied'
14
+ # puts "Channel occupied: #{event["channel"]}"
15
+ # when 'channel_vacated'
16
+ # puts "Channel vacated: #{event["channel"]}"
17
+ # end
18
+ # end
19
+ # else
20
+ # status 401
21
+ # end
22
+ # return
23
+ # end
24
+ #
25
+ class WebHook
26
+ attr_reader :key, :signature
27
+
28
+ # Provide either a Rack::Request or a Hash containing :key, :signature,
29
+ # :body, and :content_type (optional)
30
+ #
31
+ def initialize(request, client = Pusher)
32
+ @client = client
33
+ if request.kind_of?(Rack::Request)
34
+ @key = request.env['HTTP_X_PUSHER_KEY']
35
+ @signature = request.env["HTTP_X_PUSHER_SIGNATURE"]
36
+ @content_type = request.content_type
37
+
38
+ request.body.rewind
39
+ @body = request.body.read
40
+ request.body.rewind
41
+ else
42
+ @key, @signature, @body = request.values_at(:key, :signature, :body)
43
+ @content_type = request[:content_type] || 'application/json'
44
+ end
45
+ end
46
+
47
+ # Returns whether the WebHook is valid by checking that the signature
48
+ # matches the configured key & secret. In the case that the webhook is
49
+ # invalid, the reason is logged
50
+ #
51
+ # @param extra_tokens [Hash] If you have extra tokens for your Pusher
52
+ # app, you can specify them here so that they're used to attempt
53
+ # validation.
54
+ #
55
+ def valid?(extra_tokens = nil)
56
+ extra_tokens = [extra_tokens] if extra_tokens.kind_of?(Hash)
57
+ if @key == @client.key
58
+ return check_signature(@client.secret)
59
+ elsif extra_tokens
60
+ extra_tokens.each do |token|
61
+ return check_signature(token[:secret]) if @key == token[:key]
62
+ end
63
+ end
64
+ Pusher.logger.warn "Received webhook with unknown key: #{key}"
65
+ return false
66
+ end
67
+
68
+ # Array of events (as Hashes) contained inside the webhook
69
+ #
70
+ def events
71
+ data["events"]
72
+ end
73
+
74
+ # The time at which the WebHook was initially triggered by Pusher, i.e.
75
+ # when the event occurred
76
+ #
77
+ # @return [Time]
78
+ #
79
+ def time
80
+ Time.at(data["time_ms"].to_f/1000)
81
+ end
82
+
83
+ # Access the parsed WebHook body
84
+ #
85
+ def data
86
+ @data ||= begin
87
+ case @content_type
88
+ when 'application/json'
89
+ MultiJson.decode(@body)
90
+ else
91
+ raise "Unknown Content-Type (#{@content_type})"
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ # Checks signature against secret and returns boolean
99
+ #
100
+ def check_signature(secret)
101
+ expected = HMAC::SHA256.hexdigest(secret, @body)
102
+ if @signature == expected
103
+ return true
104
+ else
105
+ Pusher.logger.warn "Received WebHook with invalid signature: got #{@signature}, expected #{expected}"
106
+ return false
107
+ end
108
+ end
109
+ end
110
+ end
data/pusher.gemspec CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "pusher"
6
- s.version = "0.8.5"
6
+ s.version = "0.9.0"
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Pusher"]
9
9
  s.email = ["support@pusher.com"]
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.add_development_dependency "webmock"
20
20
  s.add_development_dependency "em-http-request", "~> 1.0.0"
21
21
  s.add_development_dependency "rake"
22
+ s.add_development_dependency "rack"
22
23
 
23
24
  s.files = `git ls-files`.split("\n")
24
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
data/spec/channel_spec.rb CHANGED
@@ -2,12 +2,14 @@ require 'spec_helper'
2
2
 
3
3
  describe Pusher::Channel do
4
4
  before do
5
- Pusher.app_id = '20'
6
- Pusher.key = '12345678900000001'
7
- Pusher.secret = '12345678900000001'
8
- Pusher.host = 'api.pusherapp.com'
9
- Pusher.port = 80
10
- Pusher.encrypted = false
5
+ @client = Pusher::Client.new(
6
+ app_id: '20',
7
+ key: '12345678900000001',
8
+ secret: '12345678900000001',
9
+ host: 'api.pusherapp.com',
10
+ port: 80,
11
+ )
12
+ @client.encrypted = false
11
13
 
12
14
  WebMock.reset!
13
15
  WebMock.disable_net_connect!
@@ -15,17 +17,11 @@ describe Pusher::Channel do
15
17
  @pusher_url_regexp = %r{/apps/20/channels/test_channel/events}
16
18
  end
17
19
 
18
- after do
19
- Pusher.app_id = nil
20
- Pusher.key = nil
21
- Pusher.secret = nil
22
- end
23
-
24
20
  describe 'trigger!' do
25
21
  before :each do
26
22
  WebMock.stub_request(:post, @pusher_url_regexp).
27
23
  to_return(:status => 202)
28
- @channel = Pusher['test_channel']
24
+ @channel = @client['test_channel']
29
25
  end
30
26
 
31
27
  it 'should configure HTTP library to talk to pusher API' do
@@ -34,8 +30,9 @@ describe Pusher::Channel do
34
30
  end
35
31
 
36
32
  it "should POST to https api if ssl enabled" do
37
- Pusher.encrypted = true
38
- Pusher::Channel.new(Pusher.url, 'test_channel').trigger('new_event', 'Some data')
33
+ @client.encrypted = true
34
+ encrypted_channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
35
+ encrypted_channel.trigger('new_event', 'Some data')
39
36
  WebMock.should have_requested(:post, %r{https://api.pusherapp.com})
40
37
  end
41
38
 
@@ -47,7 +44,7 @@ describe Pusher::Channel do
47
44
  WebMock.should have_requested(:post, %r{/apps/20/channels/test_channel/events}).with do |req|
48
45
  query_hash = req.uri.query_values
49
46
  query_hash["name"].should == 'new_event'
50
- query_hash["auth_key"].should == Pusher.key
47
+ query_hash["auth_key"].should == @client.key
51
48
  query_hash["auth_timestamp"].should_not be_nil
52
49
 
53
50
  parsed = MultiJson.decode(req.body)
@@ -75,7 +72,7 @@ describe Pusher::Channel do
75
72
 
76
73
  error_raised = nil
77
74
  begin
78
- Pusher['test_channel'].trigger!('new_event', 'Some data')
75
+ @client['test_channel'].trigger!('new_event', 'Some data')
79
76
  rescue => e
80
77
  error_raised = e
81
78
  end
@@ -90,7 +87,7 @@ describe Pusher::Channel do
90
87
  %r{/apps/20/channels/test_channel/events}
91
88
  ).to_return(:status => 401)
92
89
  lambda {
93
- Pusher['test_channel'].trigger!('new_event', 'Some data')
90
+ @client['test_channel'].trigger!('new_event', 'Some data')
94
91
  }.should raise_error(Pusher::AuthenticationError)
95
92
  end
96
93
 
@@ -99,7 +96,7 @@ describe Pusher::Channel do
99
96
  :post, %r{/apps/20/channels/test_channel/events}
100
97
  ).to_return(:status => 404)
101
98
  lambda {
102
- Pusher['test_channel'].trigger!('new_event', 'Some data')
99
+ @client['test_channel'].trigger!('new_event', 'Some data')
103
100
  }.should raise_error(Pusher::Error, 'Resource not found: app_id is probably invalid')
104
101
  end
105
102
 
@@ -108,7 +105,7 @@ describe Pusher::Channel do
108
105
  :post, %r{/apps/20/channels/test_channel/events}
109
106
  ).to_return(:status => 500, :body => "some error")
110
107
  lambda {
111
- Pusher['test_channel'].trigger!('new_event', 'Some data')
108
+ @client['test_channel'].trigger!('new_event', 'Some data')
112
109
  }.should raise_error(Pusher::Error, 'Unknown error (status code 500): some error')
113
110
  end
114
111
  end
@@ -118,7 +115,8 @@ describe Pusher::Channel do
118
115
  stub_request(:post, @pusher_url_regexp).to_raise(Net::HTTPBadResponse)
119
116
  Pusher.logger.should_receive(:error).with("Exception from WebMock (Net::HTTPBadResponse) (Pusher::HTTPError)")
120
117
  Pusher.logger.should_receive(:debug) #backtrace
121
- Pusher::Channel.new(Pusher.url, 'test_channel').trigger('new_event', 'Some data')
118
+ channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
119
+ channel.trigger('new_event', 'Some data')
122
120
  end
123
121
 
124
122
  it "should log failure if Pusher returns an error response" do
@@ -126,7 +124,8 @@ describe Pusher::Channel do
126
124
  # @http.should_receive(:post).and_raise(Net::HTTPBadResponse)
127
125
  Pusher.logger.should_receive(:error).with(" (Pusher::AuthenticationError)")
128
126
  Pusher.logger.should_receive(:debug) #backtrace
129
- Pusher::Channel.new(Pusher.url, 'test_channel').trigger('new_event', 'Some data')
127
+ channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
128
+ channel.trigger('new_event', 'Some data')
130
129
  end
131
130
  end
132
131
 
@@ -134,7 +133,7 @@ describe Pusher::Channel do
134
133
  it "should by default POST to http api" do
135
134
  EM.run {
136
135
  stub_request(:post, @pusher_url_regexp).to_return(:status => 202)
137
- channel = Pusher::Channel.new(Pusher.url, 'test_channel')
136
+ channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
138
137
  channel.trigger_async('new_event', 'Some data').callback {
139
138
  WebMock.should have_requested(:post, %r{http://api.pusherapp.com})
140
139
  EM.stop
@@ -143,10 +142,10 @@ describe Pusher::Channel do
143
142
  end
144
143
 
145
144
  it "should POST to https api if ssl enabled" do
146
- Pusher.encrypted = true
145
+ @client.encrypted = true
147
146
  EM.run {
148
147
  stub_request(:post, @pusher_url_regexp).to_return(:status => 202)
149
- channel = Pusher::Channel.new(Pusher.url, 'test_channel')
148
+ channel = Pusher::Channel.new(@client.url, 'test_channel', @client)
150
149
  channel.trigger_async('new_event', 'Some data').callback {
151
150
  WebMock.should have_requested(:post, %r{https://api.pusherapp.com})
152
151
  EM.stop
@@ -158,7 +157,7 @@ describe Pusher::Channel do
158
157
  stub_request(:post, @pusher_url_regexp).to_return(:status => 202)
159
158
 
160
159
  EM.run {
161
- d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
160
+ d = @client['test_channel'].trigger_async('new_event', 'Some data')
162
161
  d.callback {
163
162
  WebMock.should have_requested(:post, @pusher_url_regexp)
164
163
  EM.stop
@@ -174,7 +173,7 @@ describe Pusher::Channel do
174
173
  stub_request(:post, @pusher_url_regexp).to_return(:status => 401)
175
174
 
176
175
  EM.run {
177
- d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
176
+ d = @client['test_channel'].trigger_async('new_event', 'Some data')
178
177
  d.callback {
179
178
  fail
180
179
  }
@@ -197,7 +196,7 @@ describe Pusher::Channel do
197
196
  :status => 200,
198
197
  :body => JSON.generate(:user_count => 1)
199
198
  })
200
- @channel = Pusher['presence-test_channel']
199
+ @channel = @client['presence-test_channel']
201
200
 
202
201
  @channel.stats.should == {
203
202
  :user_count => 1
@@ -207,7 +206,7 @@ describe Pusher::Channel do
207
206
 
208
207
  describe "socket_auth" do
209
208
  before :each do
210
- @channel = Pusher['test_channel']
209
+ @channel = @client['test_channel']
211
210
  end
212
211
 
213
212
  it "should return an authentication string given a socket id" do
@@ -247,7 +246,7 @@ describe Pusher::Channel do
247
246
  it "should return an authentication string given a socket id and custom args" do
248
247
  auth = @channel.socket_auth('socketid', 'foobar')
249
248
 
250
- auth.should == "12345678900000001:#{HMAC::SHA256.hexdigest(Pusher.secret, "socketid:test_channel:foobar")}"
249
+ auth.should == "12345678900000001:#{HMAC::SHA256.hexdigest(@client.secret, "socketid:test_channel:foobar")}"
251
250
  end
252
251
 
253
252
  end
@@ -256,7 +255,7 @@ describe Pusher::Channel do
256
255
  describe '#authenticate' do
257
256
 
258
257
  before :each do
259
- @channel = Pusher['test_channel']
258
+ @channel = @client['test_channel']
260
259
  @custom_data = {:uid => 123, :info => {:name => 'Foo'}}
261
260
  end
262
261
 
@@ -266,7 +265,7 @@ describe Pusher::Channel do
266
265
  response = @channel.authenticate('socketid', @custom_data)
267
266
 
268
267
  response.should == {
269
- :auth => "12345678900000001:#{HMAC::SHA256.hexdigest(Pusher.secret, "socketid:test_channel:a json string")}",
268
+ :auth => "12345678900000001:#{HMAC::SHA256.hexdigest(@client.secret, "socketid:test_channel:a json string")}",
270
269
  :channel_data => 'a json string'
271
270
  }
272
271
  end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ require 'em-http'
4
+
5
+ describe Pusher do
6
+ describe 'different clients' do
7
+ before :each do
8
+ @client1 = Pusher::Client.new
9
+ @client2 = Pusher::Client.new
10
+
11
+ @client1.scheme = 'ws'
12
+ @client2.scheme = 'wss'
13
+ @client1.host = 'one'
14
+ @client2.host = 'two'
15
+ @client1.port = 81
16
+ @client2.port = 82
17
+ @client1.app_id = '1111'
18
+ @client2.app_id = '2222'
19
+ @client1.key = 'AAAA'
20
+ @client2.key = 'BBBB'
21
+ @client1.secret = 'aaaaaaaa'
22
+ @client2.secret = 'bbbbbbbb'
23
+ end
24
+
25
+ it "should send scheme messages to different objects" do
26
+ @client1.scheme.should_not == @client2.scheme
27
+ end
28
+
29
+ it "should send host messages to different objects" do
30
+ @client1.host.should_not == @client2.host
31
+ end
32
+
33
+ it "should send port messages to different objects" do
34
+ @client1.port.should_not == @client2.port
35
+ end
36
+
37
+ it "should send app_id messages to different objects" do
38
+ @client1.app_id.should_not == @client2.app_id
39
+ end
40
+
41
+ it "should send app_id messages to different objects" do
42
+ @client1.key.should_not == @client2.key
43
+ end
44
+
45
+ it "should send app_id messages to different objects" do
46
+ @client1.secret.should_not == @client2.secret
47
+ end
48
+
49
+ it "should send app_id messages to different objects" do
50
+ @client1.authentication_token.key.should_not == @client2.authentication_token.key
51
+ @client1.authentication_token.secret.should_not == @client2.authentication_token.secret
52
+ end
53
+
54
+ it "should send url messages to different objects" do
55
+ @client1.url.to_s.should_not == @client2.url.to_s
56
+ @client1.url = 'ws://one/apps/111'
57
+ @client2.url = 'wss://two/apps/222'
58
+ @client1.scheme.should_not == @client2.scheme
59
+ @client1.host.should_not == @client2.host
60
+ @client1.app_id.should_not == @client2.app_id
61
+ end
62
+
63
+ it "should send encrypted messages to different objects" do
64
+ @client1.encrypted = false
65
+ @client2.encrypted = true
66
+ @client1.scheme.should_not == @client2.scheme
67
+ @client1.port.should_not == @client2.port
68
+ end
69
+
70
+ it "should send [] messages to different objects" do
71
+ @client1['test'].should_not == @client2['test']
72
+ end
73
+ end
74
+
75
+ [lambda { Pusher }, lambda { Pusher::Client.new }].each do |client_gen|
76
+ before :each do
77
+ @client = client_gen.call
78
+ end
79
+
80
+ describe 'default configuration' do
81
+ it 'should be preconfigured for api host' do
82
+ @client.host.should == 'api.pusherapp.com'
83
+ end
84
+
85
+ it 'should be preconfigured for port 80' do
86
+ @client.port.should == 80
87
+ end
88
+
89
+ it 'should use standard logger if no other logger if defined' do
90
+ Pusher.logger.debug('foo')
91
+ Pusher.logger.should be_kind_of(Logger)
92
+ end
93
+ end
94
+
95
+ describe 'logging configuration' do
96
+ it "can be configured to use any logger" do
97
+ logger = mock("ALogger")
98
+ logger.should_receive(:debug).with('foo')
99
+ Pusher.logger = logger
100
+ Pusher.logger.debug('foo')
101
+ Pusher.logger = nil
102
+ end
103
+ end
104
+
105
+ describe "configuration using url" do
106
+ it "should be possible to configure everything by setting the url" do
107
+ @client.url = "test://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
108
+
109
+ @client.scheme.should == 'test'
110
+ @client.host.should == 'api.staging.pusherapp.com'
111
+ @client.port.should == 8080
112
+ @client.key.should == 'somekey'
113
+ @client.secret.should == 'somesecret'
114
+ @client.app_id.should == '87'
115
+ end
116
+
117
+ it "should override scheme and port when setting encrypted=true after url" do
118
+ @client.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
119
+ @client.encrypted = true
120
+
121
+ @client.scheme.should == 'https'
122
+ @client.port.should == 443
123
+ end
124
+ end
125
+
126
+ describe 'when configured' do
127
+ before :each do
128
+ @client.app_id = '20'
129
+ @client.key = '12345678900000001'
130
+ @client.secret = '12345678900000001'
131
+ end
132
+
133
+ describe '.[]' do
134
+ before do
135
+ @channel = @client['test_channel']
136
+ end
137
+
138
+ it 'should return a channel' do
139
+ @channel.should be_kind_of(Pusher::Channel)
140
+ end
141
+
142
+ it "should reuse the same channel objects" do
143
+ channel1, channel2 = @client['test_channel'], @client['test_channel']
144
+
145
+ channel1.object_id.should == channel2.object_id
146
+ end
147
+
148
+ %w{app_id key secret}.each do |config|
149
+ it "should raise exception if #{config} not configured" do
150
+ @client.send("#{config}=", nil)
151
+ lambda {
152
+ @client['test_channel']
153
+ }.should raise_error(Pusher::ConfigurationError)
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rack'
4
+ require 'stringio'
5
+
6
+ describe Pusher::WebHook do
7
+ before :each do
8
+ @hook_data = {
9
+ "time_ms" => 123456,
10
+ "events" => [
11
+ {"name" => 'foo'}
12
+ ]
13
+ }
14
+ end
15
+
16
+ describe "initialization" do
17
+ it "can be initialized with Rack::Request" do
18
+ request = Rack::Request.new({
19
+ 'HTTP_X_PUSHER_KEY' => '1234',
20
+ 'HTTP_X_PUSHER_SIGNATURE' => 'asdf',
21
+ 'CONTENT_TYPE' => 'application/json',
22
+ 'rack.input' => StringIO.new(MultiJson.encode(@hook_data))
23
+ })
24
+ wh = Pusher::WebHook.new(request)
25
+ wh.key.should == '1234'
26
+ wh.signature.should == 'asdf'
27
+ wh.data.should == @hook_data
28
+ end
29
+
30
+ it "can be initialized with a hash" do
31
+ request = {
32
+ key: '1234',
33
+ signature: 'asdf',
34
+ content_type: 'application/json',
35
+ body: MultiJson.encode(@hook_data),
36
+ }
37
+ wh = Pusher::WebHook.new(request)
38
+ wh.key.should == '1234'
39
+ wh.signature.should == 'asdf'
40
+ wh.data.should == @hook_data
41
+ end
42
+ end
43
+
44
+ describe "after initialization" do
45
+ before :each do
46
+ body = MultiJson.encode(@hook_data)
47
+ request = {
48
+ key: '1234',
49
+ signature: HMAC::SHA256.hexdigest('asdf', body),
50
+ content_type: 'application/json',
51
+ body: body
52
+ }
53
+
54
+ @client = Pusher::Client.new
55
+ @wh = Pusher::WebHook.new(request, @client)
56
+ end
57
+
58
+ it "should validate" do
59
+ @client.key = '1234'
60
+ @client.secret = 'asdf'
61
+ @wh.should be_valid
62
+ end
63
+
64
+ it "should not validate if key is wrong" do
65
+ @client.key = '12345'
66
+ @client.secret = 'asdf'
67
+ Pusher.logger.should_receive(:warn).with("Received webhook with unknown key: 1234")
68
+ @wh.should_not be_valid
69
+ end
70
+
71
+ it "should not validate if secret is wrong" do
72
+ @client.key = '1234'
73
+ @client.secret = 'asdfxxx'
74
+ Pusher.logger.should_receive(:warn).with("Received WebHook with invalid signature: got a18bd1374b3b198ec457fb11d636ee2024d8077fc542829443729988bd1e4aa4, expected bb81a112a46dee1e4154ee4f328621f32558192c7af12adfc0395082cfcd3c6c")
75
+ @wh.should_not be_valid
76
+ end
77
+
78
+ it "should validate with an extra token" do
79
+ @client.key = '12345'
80
+ @client.secret = 'xxx'
81
+ @wh.valid?({key: '1234', secret: 'asdf'}).should be_true
82
+ end
83
+
84
+ it "should validate with an array of extra tokens" do
85
+ @client.key = '123456'
86
+ @client.secret = 'xxx'
87
+ @wh.valid?([
88
+ {key: '12345', secret: 'wtf'},
89
+ {key: '1234', secret: 'asdf'}
90
+ ]).should be_true
91
+ end
92
+
93
+ it "should not validate if all keys are wrong with extra tokens" do
94
+ @client.key = '123456'
95
+ @client.secret = 'asdf'
96
+ Pusher.logger.should_receive(:warn).with("Received webhook with unknown key: 1234")
97
+ @wh.valid?({key: '12345', secret: 'asdf'}).should be_false
98
+ end
99
+
100
+ it "should not validate if secret is wrong with extra tokens" do
101
+ @client.key = '123456'
102
+ @client.secret = 'asdfxxx'
103
+ Pusher.logger.should_receive(:warn).with(/Received WebHook with invalid signature/)
104
+ @wh.valid?({key: '1234', secret: 'wtf'}).should be_false
105
+ end
106
+
107
+ it "should expose events" do
108
+ @wh.events.should == @hook_data["events"]
109
+ end
110
+
111
+ it "should expose time" do
112
+ @wh.time.should == Time.at(123.456)
113
+ end
114
+ end
115
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pusher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-05 00:00:00.000000000Z
12
+ date: 2012-01-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
16
- requirement: &70170954250900 !ruby/object:Gem::Requirement
16
+ requirement: &70115752741600 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '1.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70170954250900
24
+ version_requirements: *70115752741600
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: ruby-hmac
27
- requirement: &70170954250400 !ruby/object:Gem::Requirement
27
+ requirement: &70115752779060 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.4.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70170954250400
35
+ version_requirements: *70115752779060
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: signature
38
- requirement: &70170954249520 !ruby/object:Gem::Requirement
38
+ requirement: &70115752776600 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.1.2
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70170954249520
46
+ version_requirements: *70115752776600
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &70170954248040 !ruby/object:Gem::Requirement
49
+ requirement: &70115752774560 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '2.0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70170954248040
57
+ version_requirements: *70115752774560
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: webmock
60
- requirement: &70170954246460 !ruby/object:Gem::Requirement
60
+ requirement: &70115752772240 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70170954246460
68
+ version_requirements: *70115752772240
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: em-http-request
71
- requirement: &70170954244800 !ruby/object:Gem::Requirement
71
+ requirement: &70115752784600 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 1.0.0
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70170954244800
79
+ version_requirements: *70115752784600
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rake
82
- requirement: &70170954244120 !ruby/object:Gem::Requirement
82
+ requirement: &70115752779980 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,18 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70170954244120
90
+ version_requirements: *70115752779980
91
+ - !ruby/object:Gem::Dependency
92
+ name: rack
93
+ requirement: &70115752793240 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70115752793240
91
102
  description: Wrapper for pusher.com REST api
92
103
  email:
93
104
  - support@pusher.com
@@ -106,11 +117,14 @@ files:
106
117
  - examples/async_message.rb
107
118
  - lib/pusher.rb
108
119
  - lib/pusher/channel.rb
120
+ - lib/pusher/client.rb
109
121
  - lib/pusher/request.rb
122
+ - lib/pusher/webhook.rb
110
123
  - pusher.gemspec
111
124
  - spec/channel_spec.rb
112
- - spec/pusher_spec.rb
125
+ - spec/client_spec.rb
113
126
  - spec/spec_helper.rb
127
+ - spec/web_hook_spec.rb
114
128
  homepage: http://github.com/pusher/pusher-gem
115
129
  licenses: []
116
130
  post_install_message:
@@ -131,11 +145,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
145
  version: '0'
132
146
  requirements: []
133
147
  rubyforge_project:
134
- rubygems_version: 1.8.11
148
+ rubygems_version: 1.8.10
135
149
  signing_key:
136
150
  specification_version: 3
137
151
  summary: Pusher API client
138
152
  test_files:
139
153
  - spec/channel_spec.rb
140
- - spec/pusher_spec.rb
154
+ - spec/client_spec.rb
141
155
  - spec/spec_helper.rb
156
+ - spec/web_hook_spec.rb
data/spec/pusher_spec.rb DELETED
@@ -1,87 +0,0 @@
1
- require 'spec_helper'
2
-
3
- require 'em-http'
4
-
5
- describe Pusher do
6
- describe 'configuration' do
7
- it 'should be preconfigured for api host' do
8
- Pusher.host.should == 'api.pusherapp.com'
9
- end
10
-
11
- it 'should be preconfigured for port 80' do
12
- Pusher.port.should == 80
13
- end
14
-
15
- it 'should use standard logger if no other logger if defined' do
16
- Pusher.logger.debug('foo')
17
- Pusher.logger.should be_kind_of(Logger)
18
- end
19
-
20
- it "can be configured to use any logger" do
21
- logger = mock("ALogger")
22
- logger.should_receive(:debug).with('foo')
23
- Pusher.logger = logger
24
- Pusher.logger.debug('foo')
25
- Pusher.logger = nil
26
- end
27
- end
28
-
29
- describe "configuration using url" do
30
- after do
31
- Pusher.app_id = nil
32
- Pusher.key = nil
33
- Pusher.secret = nil
34
- Pusher.host = 'api.pusherapp.com'
35
- Pusher.port = 80
36
- end
37
-
38
- it "should be possible to configure everything by setting the url" do
39
- Pusher.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
40
-
41
- Pusher.host.should == 'api.staging.pusherapp.com'
42
- Pusher.port.should == 8080
43
- Pusher.key.should == 'somekey'
44
- Pusher.secret.should == 'somesecret'
45
- Pusher.app_id.should == '87'
46
- end
47
- end
48
-
49
- describe 'when configured' do
50
- before do
51
- Pusher.app_id = '20'
52
- Pusher.key = '12345678900000001'
53
- Pusher.secret = '12345678900000001'
54
- end
55
-
56
- after do
57
- Pusher.app_id = nil
58
- Pusher.key = nil
59
- Pusher.secret = nil
60
- end
61
-
62
- describe '.[]' do
63
- before do
64
- @channel = Pusher['test_channel']
65
- end
66
-
67
- it 'should return a channel' do
68
- @channel.should be_kind_of(Pusher::Channel)
69
- end
70
-
71
- it "should reuse the same channel objects" do
72
- channel1, channel2 = Pusher['test_channel'], Pusher['test_channel']
73
-
74
- channel1.object_id.should == channel2.object_id
75
- end
76
-
77
- %w{app_id key secret}.each do |config|
78
- it "should raise exception if #{config} not configured" do
79
- Pusher.send("#{config}=", nil)
80
- lambda {
81
- Pusher['test_channel']
82
- }.should raise_error(Pusher::ConfigurationError)
83
- end
84
- end
85
- end
86
- end
87
- end