apn_client 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -15,12 +15,14 @@ This is a RubyGem that allows sending of Apple Push Notifications to iOS devices
15
15
  ```
16
16
  require 'apn_client'
17
17
 
18
- message1 = ApnClient::Message.new(1,
18
+ message1 = ApnClient::Message.new(
19
+ :message_id => 1,
19
20
  :device_token => "7b7b8de5888bb742ba744a2a5c8e52c6481d1deeecc283e830533b7c6bf1d099",
20
21
  :alert => "New version of the app is out. Get it now in the app store!",
21
22
  :badge => 2
22
23
  )
23
- message2 = ApnClient::Message.new(2,
24
+ message2 = ApnClient::Message.new(
25
+ :message_id => 2,
24
26
  :device_token => "6a5g4de5888bb742ba744a2a5c8e52c6481d1deeecc283e830533b7c6bf1d044",
25
27
  :alert => "New version of the app is out. Get it now in the app store!",
26
28
  :badge => 1
@@ -34,7 +36,7 @@ delivery = ApnClient::Delivery.new([message1, message2],
34
36
  },
35
37
  :consecutive_failure_limit => 10, # If more than 10 devices in a row fail, we abort the whole delivery
36
38
  :exception_limit => 3, # If a device raises an exception three times in a row we fail/skip the device and move on
37
- :connection => {
39
+ :connection_config => {
38
40
  :host => 'gateway.push.apple.com', # For sandbox, use: gateway.sandbox.push.apple.com
39
41
  :port => 2195,
40
42
  :certificate => IO.read("my_apn_certificate.pem"),
@@ -7,6 +7,7 @@ module ApnClient
7
7
  # Creates a new APN delivery
8
8
  #
9
9
  # @param [#next] messages should be Enumerator type object that responds to #next. If it's an Array #shift will be used instead.
10
+ # @param [Hash] connection_config configuration parameters for the APN connection (port, host etc.) (required)
10
11
  def initialize(messages, options = {})
11
12
  self.messages = messages
12
13
  initialize_options(options)
@@ -42,13 +43,13 @@ module ApnClient
42
43
  def initialize_options(options)
43
44
  NamedArgs.assert_valid!(options,
44
45
  :optional => [:callbacks, :consecutive_failure_limit, :exception_limit, :sleep_on_exception],
45
- :required => [:connection])
46
+ :required => [:connection_config])
46
47
  NamedArgs.assert_valid!(options[:callbacks], :optional => [:on_write, :on_error, :on_nil_select, :on_read_exception, :on_exception, :on_failure])
47
48
  self.callbacks = options[:callbacks]
48
49
  self.consecutive_failure_limit = options[:consecutive_failure_limit] || 10
49
50
  self.exception_limit = options[:exception_limit] || 3
50
51
  self.sleep_on_exception = options[:sleep_on_exception] || 1
51
- self.connection_config = options[:connection]
52
+ self.connection_config = options[:connection_config]
52
53
  end
53
54
 
54
55
  def current_message
@@ -1,24 +1,20 @@
1
1
  module ApnClient
2
2
  class Message
3
- attr_accessor :message_id, :device_token, :alert, :badge, :sound, :content_available, :custom_properties
3
+ attr_accessor :attributes
4
4
 
5
5
  # Creates an APN message to to be sent over SSL to the APN service.
6
6
  #
7
- # @param [Fixnum] message_id a unique (at least within a delivery) integer identifier for this message
8
- # @param [String] device_token A 64 byte long hex digest supplied from an app installed on a device to the server
7
+ # @param [Fixnum] message_id a unique (at least within a delivery) integer identifier for this message (required)
8
+ # @param [String] device_token A 64 byte long hex digest supplied from an app installed on a device to the server (required)
9
9
  # @param [String] alert A text message to display to the user. Should be tweet sized (payload may not exceed 256 bytes)
10
10
  # @param [Fixnum] badge the number to show on the badge on the app icon - number of new/unread items
11
11
  # @param [String] sound filename of a sound file in the app bundle to be played to the user
12
12
  # @param [Boolean] content_available set to true if the message should trigger download of new content
13
- def initialize(message_id, config = {})
14
- self.message_id = message_id
15
- self.device_token = device_token
16
- NamedArgs.assert_valid!(config,
17
- :optional => [:alert, :badge, :sound, :content_available],
18
- :required => [:device_token])
19
- config.keys.each do |key|
20
- send("#{key}=", config[key])
21
- end
13
+ def initialize(attributes = {})
14
+ self.attributes = NamedArgs.symbolize_keys!(attributes)
15
+ NamedArgs.assert_valid!(attributes,
16
+ :optional => self.class.optional_attributes,
17
+ :required => self.class.required_attributes)
22
18
  check_payload_size!
23
19
  end
24
20
 
@@ -51,6 +47,33 @@ module ApnClient
51
47
  (Time.now + 30*seconds_per_day).to_i
52
48
  end
53
49
 
50
+ # Delegate attribute reading and writing (#attribute_name and #attribute_name=)
51
+ # to the attributes hash.
52
+ def method_missing(method_name, *args)
53
+ method_match, attribute_name, equal_sign = method_name.to_s.match(/\A([^=]+)(=)?\Z/).to_a
54
+ if attribute_name && self.class.valid_attributes.include?(attribute_name.to_sym)
55
+ if equal_sign
56
+ attributes[attribute_name.to_sym] = args.first
57
+ else
58
+ attributes[attribute_name.to_sym]
59
+ end
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def self.optional_attributes
66
+ [:alert, :badge, :sound, :content_available]
67
+ end
68
+
69
+ def self.required_attributes
70
+ [:message_id, :device_token]
71
+ end
72
+
73
+ def self.valid_attributes
74
+ optional_attributes + required_attributes
75
+ end
76
+
54
77
  # The payload is a JSON formated hash with alert, sound, badge, content-available,
55
78
  # and any custom properties, example:
56
79
  # {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
@@ -75,6 +98,14 @@ module ApnClient
75
98
  payload.bytesize
76
99
  end
77
100
 
101
+ def to_hash
102
+ attributes
103
+ end
104
+
105
+ def to_json
106
+ to_hash.to_json
107
+ end
108
+
78
109
  private
79
110
 
80
111
  def check_payload_size!
@@ -21,5 +21,14 @@ module ApnClient
21
21
  raise "Missing required arguments: #{missing_keys.join(', ')}"
22
22
  end
23
23
  end
24
+
25
+ # Destructively convert all keys to symbols, as long as they respond
26
+ # to +to_sym+.
27
+ def self.symbolize_keys!(arguments)
28
+ arguments.keys.each do |key|
29
+ arguments[(key.to_sym rescue key) || key] = arguments.delete(key)
30
+ end
31
+ arguments
32
+ end
24
33
  end
25
34
  end
@@ -1,3 +1,3 @@
1
1
  module ApnClient
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -2,21 +2,30 @@ require 'spec_helper'
2
2
 
3
3
  describe ApnClient::Delivery do
4
4
  before(:each) do
5
- @message1 = ApnClient::Message.new(1,
5
+ @message1 = ApnClient::Message.new(
6
+ :message_id => 1,
6
7
  :device_token => "7b7b8de5888bb742ba744a2a5c8e52c6481d1deeecc283e830533b7c6bf1d099",
7
8
  :alert => "New version of the app is out. Get it now in the app store!",
8
9
  :badge => 2
9
10
  )
10
- @message2 = ApnClient::Message.new(2,
11
+ @message2 = ApnClient::Message.new(
12
+ :message_id => 2,
11
13
  :device_token => "6a5g4de5888bb742ba744a2a5c8e52c6481d1deeecc283e830533b7c6bf1d044",
12
14
  :alert => "New version of the app is out. Get it now in the app store!",
13
15
  :badge => 1
14
16
  )
17
+ @connection_config = {
18
+ :host => 'gateway.push.apple.com',
19
+ :port => 2195,
20
+ :certificate => "certificate",
21
+ :certificate_passphrase => ''
22
+ }
15
23
  end
16
24
 
17
25
  describe "#initialize" do
18
26
  it "initializes counts and other attributes" do
19
- delivery = create_delivery([@message1, @message2], :connection => {})
27
+ delivery = create_delivery([@message1, @message2], :connection_config => @connection_config)
28
+ delivery.connection_config.should == @connection_config
20
29
  end
21
30
  end
22
31
 
@@ -29,7 +38,7 @@ describe ApnClient::Delivery do
29
38
  :on_write => lambda { |d, m| written_messages << m },
30
39
  :on_nil_select => lambda { |d| nil_selects += 1 }
31
40
  }
32
- delivery = create_delivery(messages.dup, :callbacks => callbacks, :connection => {})
41
+ delivery = create_delivery(messages.dup, :callbacks => callbacks, :connection_config => @connection_config)
33
42
 
34
43
  connection = mock('connection')
35
44
  connection.expects(:write).with(@message1)
@@ -58,7 +67,7 @@ describe ApnClient::Delivery do
58
67
  :on_failure => lambda { |d, m| failures << m },
59
68
  :on_read_exception => lambda { |d, e| read_exceptions << e }
60
69
  }
61
- delivery = create_delivery(messages.dup, :callbacks => callbacks, :connection => {})
70
+ delivery = create_delivery(messages.dup, :callbacks => callbacks, :connection_config => @connection_config)
62
71
 
63
72
  connection = mock('connection')
64
73
  connection.expects(:write).with(@message1).times(3).raises(RuntimeError)
@@ -92,7 +101,7 @@ describe ApnClient::Delivery do
92
101
  :on_read_exception => lambda { |d, e| read_exceptions << e },
93
102
  :on_error => lambda { |d, message_id, error_code| errors << [message_id, error_code] }
94
103
  }
95
- delivery = create_delivery(messages.dup, :callbacks => callbacks, :connection => {})
104
+ delivery = create_delivery(messages.dup, :callbacks => callbacks, :connection_config => @connection_config)
96
105
 
97
106
  connection = mock('connection')
98
107
  connection.expects(:write).with(@message1)
data/spec/message_spec.rb CHANGED
@@ -9,24 +9,31 @@ describe ApnClient::Message do
9
9
  end
10
10
 
11
11
  describe "#initialize" do
12
+ it "cannot be created without a message_id" do
13
+ lambda {
14
+ ApnClient::Message.new()
15
+ }.should raise_error(/message_id/)
16
+ end
17
+
12
18
  it "cannot be created without a token" do
13
19
  lambda {
14
- ApnClient::Message.new(1)
20
+ ApnClient::Message.new(:message_id => 1)
15
21
  }.should raise_error(/device_token/)
16
22
  end
17
23
 
18
24
  it "can be created with a token and an alert" do
19
- message = create_message(1, :device_token => @device_token, :alert => @alert)
25
+ message = create_message(:message_id => 1, :device_token => @device_token, :alert => @alert)
20
26
  message.payload_hash.should == {'aps' => {'alert' => @alert}}
21
27
  end
22
28
 
23
29
  it "can be created with a token and an alert and a badge" do
24
- message = create_message(1, :device_token => @device_token, :alert => @alert, :badge => @badge)
30
+ message = create_message(:message_id => 1, :device_token => @device_token, :alert => @alert, :badge => @badge)
25
31
  message.payload_hash.should == {'aps' => {'alert' => @alert, 'badge' => @badge}}
26
32
  end
27
33
 
28
34
  it "can be created with a token and an alert and a badge and content-available" do
29
- message = create_message(1,
35
+ message = create_message(
36
+ :message_id => 1,
30
37
  :device_token => @device_token,
31
38
  :alert => @alert,
32
39
  :badge => @badge,
@@ -37,15 +44,50 @@ describe ApnClient::Message do
37
44
  it "raises an exception if payload_size exceeds 256 bytes" do
38
45
  lambda {
39
46
  too_long_alert = "A"*1000
40
- ApnClient::Message.new(1, :device_token => @device_token, :alert => too_long_alert)
47
+ ApnClient::Message.new(:message_id => 1, :device_token => @device_token, :alert => too_long_alert)
41
48
  }.should raise_error(/payload/i)
42
49
  end
43
50
  end
44
51
 
52
+ describe "attribute accessors" do
53
+ it "works with symbol keys" do
54
+ message = create_message(
55
+ :message_id => 1,
56
+ :device_token => @device_token,
57
+ :alert => @alert,
58
+ :badge => @badge,
59
+ :content_available => true)
60
+ message.message_id.should == 1
61
+ message.badge.should == @badge
62
+ message.message_id = 3
63
+ message.message_id.should == 3
64
+ end
65
+
66
+ it "works with string keys too" do
67
+ message = create_message(
68
+ 'message_id' => 1,
69
+ 'device_token' => @device_token,
70
+ 'alert' => @alert,
71
+ 'badge' => @badge,
72
+ 'content_available' => true)
73
+ message.message_id.should == 1
74
+ message.badge.should == @badge
75
+ message.message_id = 3
76
+ message.message_id.should == 3
77
+ message.attributes.should == {
78
+ :message_id => 3,
79
+ :device_token => @device_token,
80
+ :alert => @alert,
81
+ :badge => @badge,
82
+ :content_available => true
83
+ }
84
+ end
85
+ end
86
+
45
87
  describe "#==" do
46
88
  before(:each) do
47
- @message = create_message(3, :device_token => @device_token)
48
- @other_message = create_message(5, :device_token => @other_device_token)
89
+ @message = create_message(:message_id => 3, :device_token => @device_token)
90
+ @other_message = create_message(:message_id => 5, :device_token => @other_device_token)
49
91
  end
50
92
 
51
93
  it "returns false for nil" do
@@ -66,15 +108,45 @@ describe ApnClient::Message do
66
108
  end
67
109
  end
68
110
 
69
- describe "#payload_size" do
70
- it "returns number of bytes in the payload"
111
+ describe "#to_hash" do
112
+ it "returns a hash with the attributes of the message" do
113
+ attributes = {
114
+ :message_id => 1,
115
+ :device_token => @device_token,
116
+ :alert => @alert,
117
+ :badge => @badge,
118
+ :content_available => true
119
+ }
120
+ message = create_message(attributes)
121
+ message.to_hash.should == attributes
122
+ end
123
+ end
124
+
125
+ describe "#to_json" do
126
+ it "converts the attributes hash to JSON" do
127
+ attributes = {
128
+ :message_id => 1,
129
+ :device_token => @device_token,
130
+ :alert => @alert,
131
+ :badge => @badge,
132
+ :content_available => true
133
+ }
134
+ message = create_message(attributes)
135
+ message.to_hash.should == attributes
136
+ JSON.parse(message.to_json).should == {
137
+ 'message_id' => 1,
138
+ 'device_token' => @device_token,
139
+ 'alert' => @alert,
140
+ 'badge' => @badge,
141
+ 'content_available' => true
142
+ }
143
+ end
71
144
  end
72
145
 
73
- def create_message(message_id, config = {})
74
- message = ApnClient::Message.new(message_id, config)
75
- message.message_id.should == message_id
76
- [:device_token, :alert, :badge, :sound, :content_available].each do |attribute|
77
- message.send(attribute).should == config[attribute]
146
+ def create_message(attributes = {})
147
+ message = ApnClient::Message.new(attributes)
148
+ attributes.keys.each do |attribute|
149
+ message.send(attribute).should == attributes[attribute]
78
150
  end
79
151
  message.payload_size.should < 256
80
152
  message.to_s.should_not be_nil
@@ -89,4 +89,18 @@ describe ApnClient::NamedArgs do
89
89
  ApnClient::NamedArgs.assert_valid!(nil, :optional => [:bar])
90
90
  end
91
91
  end
92
+
93
+ describe ".symbolize_keys!" do
94
+ it "takes a hash and symbolizes its keys" do
95
+ attributes = {
96
+ 'foo' => 1,
97
+ :bar => 2
98
+ }
99
+ ApnClient::NamedArgs.symbolize_keys!(attributes)
100
+ attributes.should == {
101
+ :foo => 1,
102
+ :bar => 2
103
+ }
104
+ end
105
+ end
92
106
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apn_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
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: 2011-12-06 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &70265547611420 !ruby/object:Gem::Requirement
16
+ requirement: &70251934469880 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70265547611420
24
+ version_requirements: *70251934469880
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70265547611000 !ruby/object:Gem::Requirement
27
+ requirement: &70251934469460 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70265547611000
35
+ version_requirements: *70251934469460
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: mocha
38
- requirement: &70265547610580 !ruby/object:Gem::Requirement
38
+ requirement: &70251934469040 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70265547610580
46
+ version_requirements: *70251934469040
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yard
49
- requirement: &70265547610160 !ruby/object:Gem::Requirement
49
+ requirement: &70251934468620 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70265547610160
57
+ version_requirements: *70251934468620
58
58
  description: Uses the "enhanced format" Apple protocol and deals with errors and failures
59
59
  when broadcasting to many devices. Includes support for talking to the Apple Push
60
60
  Notification Feedback service for dealing with uninstalled apps.