c2dm_batch 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/LICENSE +18 -0
- data/README.md +66 -0
- data/Rakefile +1 -0
- data/bin/c2dm_batch +3 -0
- data/c2dm_batch.gemspec +24 -0
- data/config.example.yml +4 -0
- data/lib/c2dm_batch/core.rb +120 -0
- data/lib/c2dm_batch.rb +12 -0
- data/spec/c2dm_batch/core_spec.rb +79 -0
- data/spec/c2dm_batch_spec.rb +4 -0
- data/spec/spec_helper.rb +8 -0
- metadata +112 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2010
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
C2dmBatch
|
2
|
+
===========
|
3
|
+
|
4
|
+
A library to send c2dm notifications in parallel.
|
5
|
+
|
6
|
+
This code is based on https://github.com/amro/c2dm
|
7
|
+
|
8
|
+
Requirements
|
9
|
+
------------
|
10
|
+
|
11
|
+
<pre>
|
12
|
+
gem install typhoeus
|
13
|
+
</pre>
|
14
|
+
|
15
|
+
Send a single notification
|
16
|
+
--------------------------
|
17
|
+
<pre>
|
18
|
+
C2dmBatch.email = 'your_c2dm_sender@gmail.com'
|
19
|
+
C2dmBatch.password = 'password'
|
20
|
+
C2dmBatch.source = 'your app name'
|
21
|
+
notification = {
|
22
|
+
:registration_id => "your_reg_id",
|
23
|
+
:data => {
|
24
|
+
:test => "test"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
C2dmBatch.send_notification(notification)
|
28
|
+
</pre>
|
29
|
+
|
30
|
+
Send notifications in batch
|
31
|
+
-----------------------------
|
32
|
+
|
33
|
+
<pre>
|
34
|
+
C2dmBatch.email = 'your_c2dm_sender@gmail.com'
|
35
|
+
C2dmBatch.password = 'password'
|
36
|
+
C2dmBatch.source = 'your app name'
|
37
|
+
notification = [
|
38
|
+
{
|
39
|
+
:registration_id => "your_reg_id",
|
40
|
+
:data => {
|
41
|
+
:test => "foo"
|
42
|
+
}
|
43
|
+
},
|
44
|
+
{
|
45
|
+
:registration_id => "your_reg_id2",
|
46
|
+
:data => {
|
47
|
+
:test => "bar"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
]
|
51
|
+
errors = C2dmBatch.send_batch_notification(notification)
|
52
|
+
</pre>
|
53
|
+
|
54
|
+
Using Typhoeus, the send_batch_notification will parallelize the request in up to 200 parallel requests. Once a request finishes, a new request will automatically get send out. The return value is an array of hashes. The hash is of the form { :registration_id => 'reg_id', :error => 'error_code' }
|
55
|
+
|
56
|
+
Possible Error Codes
|
57
|
+
--------------------
|
58
|
+
The error code and description are taken from the official c2dm docuementation at: http://code.google.com/android/c2dm/
|
59
|
+
|
60
|
+
* QuotaExceeded — Too many messages sent by the sender. Retry after a while.
|
61
|
+
* DeviceQuotaExceeded — Too many messages sent by the sender to a specific device. Retry after a while.
|
62
|
+
* InvalidRegistration — Missing or bad registration_id. Sender should stop sending messages to this device.
|
63
|
+
* NotRegistered — The registration_id is no longer valid, for example user has uninstalled the application or turned off notifications. Sender should stop sending messages to this device.
|
64
|
+
* MessageTooBig — The payload of the message is too big, see the limitations. Reduce the size of the message.
|
65
|
+
* MissingCollapseKey — Collapse key is required. Include collapse key in the request.
|
66
|
+
* 503 - Must retry later. c2dm batch aborts all in-flight requests and returns all unsent requests with a 503 error code. Resending with honoring the Retry-After and exponentail backoff are not implemented.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/c2dm_batch
ADDED
data/c2dm_batch.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
root = File.expand_path('../', __FILE__)
|
3
|
+
lib = "#{root}/lib"
|
4
|
+
|
5
|
+
$:.unshift lib unless $:.include?(lib)
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "c2dm_batch"
|
9
|
+
s.version = '0.1.0'
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ["Alex Rockwell"]
|
12
|
+
s.email = ["arockwell@gmail.com"]
|
13
|
+
s.homepage = "http://alexrockwell.org"
|
14
|
+
s.summary = %q{Gem to send android c2dm notifications in batch}
|
15
|
+
s.description = %q{Gem to send android c2dm notifications in batch}
|
16
|
+
|
17
|
+
s.executables = `cd #{root} && git ls-files bin/*`.split("\n").collect { |f| File.basename(f) }
|
18
|
+
s.files = `cd #{root} && git ls-files`.split("\n")
|
19
|
+
s.require_paths = %w(lib)
|
20
|
+
s.test_files = `cd #{root} && git ls-files -- {features,test,spec}/*`.split("\n")
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec", "~> 1.0"
|
23
|
+
s.add_dependency "typhoeus", "= 0.2.4"
|
24
|
+
end
|
data/config.example.yml
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module C2dmBatch
|
2
|
+
@auth_url = 'https://www.google.com/accounts/ClientLogin'
|
3
|
+
@send_url = 'https://android.apis.google.com/c2dm/send'
|
4
|
+
|
5
|
+
@email = nil
|
6
|
+
@password = nil
|
7
|
+
@source = nil
|
8
|
+
|
9
|
+
@hydra = Typhoeus::Hydra.new
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :auth_url, :send_url, :email, :password, :source
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.authenticate!
|
16
|
+
request = Typhoeus::Request.new(@auth_url)
|
17
|
+
auth_options = {
|
18
|
+
'accountType' => 'HOSTED_OR_GOOGLE',
|
19
|
+
'service' => 'ac2dm',
|
20
|
+
'Email' => @email,
|
21
|
+
'Passwd' => @password,
|
22
|
+
'source' => @source,
|
23
|
+
}
|
24
|
+
request.body = build_post_body(auth_options)
|
25
|
+
|
26
|
+
headers = {
|
27
|
+
'Content-length' => request.body.length.to_s
|
28
|
+
}
|
29
|
+
request.headers = headers
|
30
|
+
|
31
|
+
@hydra.queue(request)
|
32
|
+
@hydra.run
|
33
|
+
response = request.response
|
34
|
+
|
35
|
+
auth_token = ""
|
36
|
+
if response.success?
|
37
|
+
response.body.split("\n").each do |line|
|
38
|
+
if line =~ /^Auth=/
|
39
|
+
auth_token = line.gsub('Auth=', '')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@auth_token = auth_token
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.send_notification(notification)
|
47
|
+
authenticate!
|
48
|
+
request = create_notification_request(notification)
|
49
|
+
|
50
|
+
@hydra.queue(request)
|
51
|
+
@hydra.run
|
52
|
+
response = request.response
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.send_batch_notifications(notifications)
|
56
|
+
authenticate!
|
57
|
+
requests = []
|
58
|
+
errors = []
|
59
|
+
notifications.each do |notification|
|
60
|
+
request = create_notification_request(notification)
|
61
|
+
requests << request
|
62
|
+
request.method = :post
|
63
|
+
request.on_complete do |response|
|
64
|
+
if response.success?
|
65
|
+
if response.body =~ /Error=(\w+)/
|
66
|
+
errors << { :registration_id => notification[:registration_id], :error => $1 }
|
67
|
+
else
|
68
|
+
requests.delete(request)
|
69
|
+
end
|
70
|
+
elsif response.code == 503
|
71
|
+
@hydra.abort
|
72
|
+
raise RuntimeError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@hydra.queue(request)
|
76
|
+
end
|
77
|
+
begin
|
78
|
+
@hydra.run
|
79
|
+
rescue RuntimeError
|
80
|
+
requests.each do |failed_request|
|
81
|
+
errors << {
|
82
|
+
:registration_id => failed_request.body.match(/registration_id=(\w+)/)[1],
|
83
|
+
:error => 503
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
errors
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def self.build_post_body(options={})
|
92
|
+
post_body = []
|
93
|
+
|
94
|
+
# data attributes need a key in the form of "data.key"...
|
95
|
+
data_attributes = options.delete(:data)
|
96
|
+
data_attributes.each_pair do |k,v|
|
97
|
+
post_body << "data.#{k}=#{CGI::escape(v.to_s)}"
|
98
|
+
end if data_attributes
|
99
|
+
|
100
|
+
options.each_pair do |k,v|
|
101
|
+
post_body << "#{k}=#{CGI::escape(v.to_s)}"
|
102
|
+
end
|
103
|
+
|
104
|
+
post_body.join('&')
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.create_notification_request(notification)
|
108
|
+
request = Typhoeus::Request.new(@send_url)
|
109
|
+
notification[:collapse_key] = 'collapse'
|
110
|
+
request.body = build_post_body(notification)
|
111
|
+
|
112
|
+
headers = {
|
113
|
+
'Authorization' => "GoogleLogin auth=#{@auth_token}",
|
114
|
+
'Content-type' => 'application/x-www-form-urlencoded',
|
115
|
+
'Content-length' => request.body.length.to_s
|
116
|
+
}
|
117
|
+
request.headers = headers
|
118
|
+
request
|
119
|
+
end
|
120
|
+
end
|
data/lib/c2dm_batch.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe C2dmBatch do
|
4
|
+
before do
|
5
|
+
@config = YAML::load(File.open("config.yml"))
|
6
|
+
C2dmBatch.email = @config['server']['username']
|
7
|
+
C2dmBatch.password = @config['server']['password']
|
8
|
+
C2dmBatch.source = @config['server']['source']
|
9
|
+
@reg_id = @config['client']['registration_id']
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should give an auth token" do
|
13
|
+
auth_code = C2dmBatch.authenticate!
|
14
|
+
auth_code.should_not eql("")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should send a notifcation" do
|
18
|
+
C2dmBatch.send_notification(create_notification(@reg_id, "boston-college-football"))
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should send notifications in batches" do
|
22
|
+
teams = [ "buffalo-bills", "miami-dolphins", "new-york-jets"]
|
23
|
+
notifications = []
|
24
|
+
teams.each do |team|
|
25
|
+
notifications << create_notification(@reg_id, team)
|
26
|
+
end
|
27
|
+
errors = C2dmBatch.send_batch_notifications(notifications)
|
28
|
+
errors.size.should == 0
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return InvalidRegistration and registration_id" do
|
32
|
+
teams = [ "buffalo-bills", "miami-dolphins"]
|
33
|
+
notifications = []
|
34
|
+
bad_reg_id = "bad_reg_id"
|
35
|
+
notifications << create_notification(bad_reg_id, teams[0])
|
36
|
+
notifications << create_notification(@reg_id, teams[1])
|
37
|
+
errors = C2dmBatch.send_batch_notifications(notifications)
|
38
|
+
errors.size.should == 1
|
39
|
+
errors[0][:registration_id].should == bad_reg_id
|
40
|
+
errors[0][:error].should == "InvalidRegistration"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should return MessageToBig status code" do
|
44
|
+
notifications = []
|
45
|
+
notifications << create_notification(@reg_id, "1" * 1025)
|
46
|
+
errors = C2dmBatch.send_batch_notifications(notifications)
|
47
|
+
errors[0][:registration_id].should == @reg_id
|
48
|
+
errors[0][:error].should == "MessageTooBig"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should abort on 503 and return remaining requests" do
|
52
|
+
hydra = Typhoeus::Hydra.new
|
53
|
+
response = Typhoeus::Response.new(:code => 503, :headers => "", :body => "registration=123")
|
54
|
+
hydra.stub(:post, 'https://android.apis.google.com/c2dm/send').and_return(response)
|
55
|
+
C2dmBatch.instance_variable_set("@hydra", hydra)
|
56
|
+
teams = [ "buffalo-bills", "miami-dolphins", "new-york-jets"]
|
57
|
+
notifications = []
|
58
|
+
teams.each do |team|
|
59
|
+
notifications << create_notification(@reg_id, team)
|
60
|
+
end
|
61
|
+
errors = C2dmBatch.send_batch_notifications(notifications)
|
62
|
+
errors.size.should == 3
|
63
|
+
errors[0][:error].should == 503
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def create_notification(reg_id, team)
|
68
|
+
{
|
69
|
+
:registration_id => reg_id,
|
70
|
+
:collapse_key => "#{1 + rand(100000)}",
|
71
|
+
:data => {
|
72
|
+
:alert => team,
|
73
|
+
:url => "/articles/816975-nfl-5-players-who-wont-replicate-last-years-success",
|
74
|
+
:tag => team
|
75
|
+
}
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: c2dm_batch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Alex Rockwell
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-10 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
version: "1.0"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: typhoeus
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - "="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 31
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 2
|
48
|
+
- 4
|
49
|
+
version: 0.2.4
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
description: Gem to send android c2dm notifications in batch
|
53
|
+
email:
|
54
|
+
- arockwell@gmail.com
|
55
|
+
executables:
|
56
|
+
- c2dm_batch
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- bin/c2dm_batch
|
68
|
+
- c2dm_batch.gemspec
|
69
|
+
- config.example.yml
|
70
|
+
- lib/c2dm_batch.rb
|
71
|
+
- lib/c2dm_batch/core.rb
|
72
|
+
- spec/c2dm_batch/core_spec.rb
|
73
|
+
- spec/c2dm_batch_spec.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
has_rdoc: true
|
76
|
+
homepage: http://alexrockwell.org
|
77
|
+
licenses: []
|
78
|
+
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.4.2
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Gem to send android c2dm notifications in batch
|
109
|
+
test_files:
|
110
|
+
- spec/c2dm_batch/core_spec.rb
|
111
|
+
- spec/c2dm_batch_spec.rb
|
112
|
+
- spec/spec_helper.rb
|