grocer 0.0.13 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -3,5 +3,5 @@ rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
5
  - ruby-head
6
- - rbx-19mode
7
- # - jruby-19mode
6
+ - jruby-head
7
+ # - rbx-19mode # rubinius still incorrectly defaults to 'US-ASCII'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.1.0
4
+
5
+ * Enables socket keepalive option on APNS client sockets (Kyle Drake)
6
+ * Supports non-ASCII characters in notifications (Patrick Van Stee, Andy Lindeman)
7
+ * Certificate can be any object that responds to #read (Kyle Drake)
8
+
3
9
  ## 0.0.13
4
10
 
5
11
  * Fixes a bug where closing a Grocer.server could result in an
data/README.md CHANGED
@@ -13,8 +13,7 @@ cleanest, most extensible, and friendliest.
13
13
 
14
14
  ## Requirements
15
15
 
16
- * Ruby/MRI 1.9.x *or* Rubinius in 1.9 mode (JRuby in 1.9 is broken due to some
17
- problem with `json` not being in the stdlib?)
16
+ * Ruby/MRI 1.9.x or JRuby 1.7.x in 1.9 mode
18
17
 
19
18
  ## Installation
20
19
 
@@ -45,6 +44,9 @@ pusher = Grocer.pusher(
45
44
 
46
45
  #### Notes
47
46
 
47
+ * `certificate`: If you don't have the certificate stored in a file, you
48
+ can pass any object that responds to `read`.
49
+ Example: `certificate: StringIO.new(pem_string)`
48
50
  * `gateway`: Defaults to different values depending on the `RAILS_ENV` or
49
51
  `RACK_ENV` environment variables. If set to `production`, defaults to
50
52
  `gateway.push.apple.com`, if set to `test`, defaults to `localhost` (see
data/grocer.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |gem|
17
17
  extensible, and friendliest.
18
18
  DESC
19
19
  gem.homepage = 'https://github.com/highgroove/grocer'
20
+ gem.license = 'MIT'
20
21
 
21
22
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
23
  gem.files = `git ls-files`.split("\n")
@@ -5,7 +5,10 @@ module Grocer
5
5
  def deep_symbolize_keys
6
6
  result = {}
7
7
  each do |key, value|
8
- result[(key.to_sym rescue key)] = value.is_a?(Hash) ?
8
+ # Workaround for JRuby defining Fixnum#to_sym even in 1.9 mode
9
+ symbolized_key = key.is_a?(Fixnum) ? key : (key.to_sym rescue key)
10
+
11
+ result[symbolized_key] = value.is_a?(Hash) ?
9
12
  (value.extend DeepSymbolizeKeys).deep_symbolize_keys : value
10
13
  end
11
14
  result
@@ -18,7 +18,15 @@ module Grocer
18
18
  validate_payload
19
19
  payload = encoded_payload
20
20
 
21
- [1, identifier, expiry_epoch_time, device_token_length, sanitized_device_token, payload.length].pack('CNNnH64n') << payload
21
+ [
22
+ 1,
23
+ identifier,
24
+ expiry_epoch_time,
25
+ device_token_length,
26
+ sanitized_device_token,
27
+ payload.bytesize,
28
+ payload
29
+ ].pack('CNNnH64nA*')
22
30
  end
23
31
 
24
32
  private
@@ -1,6 +1,7 @@
1
1
  require 'socket'
2
2
  require 'openssl'
3
3
  require 'forwardable'
4
+ require 'stringio'
4
5
 
5
6
  module Grocer
6
7
  class SSLConnection
@@ -23,14 +24,22 @@ module Grocer
23
24
  context = OpenSSL::SSL::SSLContext.new
24
25
 
25
26
  if certificate
26
- cert_data = File.read(certificate)
27
+
28
+ if certificate.respond_to?(:read)
29
+ cert_data = certificate.read
30
+ certificate.rewind if certificate.respond_to?(:rewind)
31
+ else
32
+ cert_data = File.read(certificate)
33
+ end
34
+
27
35
  context.key = OpenSSL::PKey::RSA.new(cert_data, passphrase)
28
36
  context.cert = OpenSSL::X509::Certificate.new(cert_data)
29
37
  end
30
38
 
31
- @sock = TCPSocket.new(gateway, port)
32
- @ssl = OpenSSL::SSL::SSLSocket.new(@sock, context)
33
- @ssl.sync = true
39
+ @sock = TCPSocket.new(gateway, port)
40
+ @sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true
41
+ @ssl = OpenSSL::SSL::SSLSocket.new(@sock, context)
42
+ @ssl.sync = true
34
43
  @ssl.connect
35
44
  end
36
45
 
@@ -1,3 +1,3 @@
1
1
  module Grocer
2
- VERSION = '0.0.13'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,3 +1,5 @@
1
+ # encoding: UTF-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'grocer/notification'
3
5
 
@@ -8,47 +10,47 @@ describe Grocer::Notification do
8
10
  let(:payload_dictionary_from_bytes) { JSON.parse(payload_from_bytes, symbolize_names: true) }
9
11
  let(:payload_options) { { alert: 'hi', badge: 2, sound: 'siren.aiff' } }
10
12
 
11
- subject { notification.to_bytes }
13
+ subject(:bytes) { notification.to_bytes }
12
14
 
13
15
  it 'sets the command byte to 1' do
14
- subject[0].should == "\x01"
16
+ bytes[0].should == "\x01"
15
17
  end
16
18
 
17
19
  it 'defaults the identifer to 0' do
18
- subject[1...5].should == "\x00\x00\x00\x00"
20
+ bytes[1...5].should == "\x00\x00\x00\x00"
19
21
  end
20
22
 
21
23
  it 'allows the identifier to be set' do
22
24
  notification.identifier = 1234
23
- subject[1...5].should == [1234].pack('N')
25
+ bytes[1...5].should == [1234].pack('N')
24
26
  end
25
27
 
26
28
  it 'defaults expiry to zero' do
27
- subject[5...9].should == "\x00\x00\x00\x00"
29
+ bytes[5...9].should == "\x00\x00\x00\x00"
28
30
  end
29
31
 
30
32
  it 'allows the expiry to be set' do
31
33
  expiry = notification.expiry = Time.utc(2013, 3, 24, 12, 34, 56)
32
- subject[5...9].should == [expiry.to_i].pack('N')
34
+ bytes[5...9].should == [expiry.to_i].pack('N')
33
35
  end
34
36
 
35
37
  it 'encodes the device token length as 32' do
36
- subject[9...11].should == "\x00\x20"
38
+ bytes[9...11].should == "\x00\x20"
37
39
  end
38
40
 
39
41
  it 'encodes the device token as a 256-bit integer' do
40
42
  token = notification.device_token = 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
41
- subject[11...43].should == ['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*')
43
+ bytes[11...43].should == ['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*')
42
44
  end
43
45
 
44
46
  it 'as a convenience, flattens the device token to remove spaces' do
45
47
  token = notification.device_token = 'fe15 a27d 5df3c3 4778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
46
- subject[11...43].should == ['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*')
48
+ bytes[11...43].should == ['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*')
47
49
  end
48
50
 
49
51
  it 'encodes the payload length' do
50
52
  notification.alert = 'Hello World!'
51
- subject[43...45].should == [payload_from_bytes.length].pack('n')
53
+ bytes[43...45].should == [payload_from_bytes.bytesize].pack('n')
52
54
  end
53
55
 
54
56
  it 'encodes alert as part of the payload' do
@@ -71,6 +73,16 @@ describe Grocer::Notification do
71
73
  payload_dictionary_from_bytes[:foo].should == 'bar'
72
74
  end
73
75
 
76
+ it 'encodes UTF-8 characters' do
77
+ notification.alert = '私'
78
+ payload_dictionary_from_bytes[:aps][:alert].force_encoding("UTF-8").should == '私'
79
+ end
80
+
81
+ it 'encodes the payload length correctly for multibyte UTF-8 strings' do
82
+ notification.alert = '私'
83
+ bytes[43...45].should == [payload_from_bytes.bytesize].pack('n')
84
+ end
85
+
74
86
  context 'invalid payload' do
75
87
  let(:payload_options) { Hash.new }
76
88
 
@@ -24,6 +24,21 @@ describe Grocer::SSLConnection do
24
24
  }
25
25
  }
26
26
 
27
+ describe 'configuration with pre-read certificate' do
28
+ before do
29
+ stub_certificate
30
+ end
31
+
32
+ subject {
33
+ string_io = File.read(connection_options[:certificate])
34
+ described_class.new(connection_options.merge(certificate: string_io))
35
+ }
36
+
37
+ it 'is initialized with a certificate IO' do
38
+ subject.certificate.should == File.read(connection_options[:certificate])
39
+ end
40
+ end
41
+
27
42
  subject { described_class.new(connection_options) }
28
43
 
29
44
  describe 'configuration' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grocer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-06-20 00:00:00.000000000 Z
14
+ date: 2012-08-11 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rspec
@@ -155,7 +155,8 @@ files:
155
155
  - spec/grocer_spec.rb
156
156
  - spec/spec_helper.rb
157
157
  homepage: https://github.com/highgroove/grocer
158
- licenses: []
158
+ licenses:
159
+ - MIT
159
160
  post_install_message:
160
161
  rdoc_options: []
161
162
  require_paths:
@@ -168,7 +169,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
169
  version: '0'
169
170
  segments:
170
171
  - 0
171
- hash: -2773558114449338672
172
+ hash: 1241833556512094426
172
173
  required_rubygems_version: !ruby/object:Gem::Requirement
173
174
  none: false
174
175
  requirements:
@@ -177,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
178
  version: '0'
178
179
  segments:
179
180
  - 0
180
- hash: -2773558114449338672
181
+ hash: 1241833556512094426
181
182
  requirements: []
182
183
  rubyforge_project:
183
184
  rubygems_version: 1.8.24