pusher 0.8.5 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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