apn_client 0.0.3 → 0.0.4
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/README.md +5 -3
- data/lib/apn_client/delivery.rb +3 -2
- data/lib/apn_client/message.rb +43 -12
- data/lib/apn_client/named_args.rb +9 -0
- data/lib/apn_client/version.rb +1 -1
- data/spec/delivery_spec.rb +15 -6
- data/spec/message_spec.rb +86 -14
- data/spec/named_args_spec.rb +14 -0
- metadata +10 -10
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(
|
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(
|
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
|
-
:
|
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"),
|
data/lib/apn_client/delivery.rb
CHANGED
@@ -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 => [:
|
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[:
|
52
|
+
self.connection_config = options[:connection_config]
|
52
53
|
end
|
53
54
|
|
54
55
|
def current_message
|
data/lib/apn_client/message.rb
CHANGED
@@ -1,24 +1,20 @@
|
|
1
1
|
module ApnClient
|
2
2
|
class Message
|
3
|
-
attr_accessor :
|
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(
|
14
|
-
self.
|
15
|
-
|
16
|
-
|
17
|
-
:
|
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
|
data/lib/apn_client/version.rb
CHANGED
data/spec/delivery_spec.rb
CHANGED
@@ -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(
|
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(
|
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], :
|
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, :
|
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, :
|
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, :
|
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(
|
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 "#
|
70
|
-
it "returns
|
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(
|
74
|
-
message = ApnClient::Message.new(
|
75
|
-
|
76
|
-
|
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
|
data/spec/named_args_spec.rb
CHANGED
@@ -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.
|
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-
|
12
|
+
date: 2011-12-06 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
16
|
-
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: *
|
24
|
+
version_requirements: *70251934469880
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
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: *
|
35
|
+
version_requirements: *70251934469460
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mocha
|
38
|
-
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: *
|
46
|
+
version_requirements: *70251934469040
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: yard
|
49
|
-
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: *
|
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.
|