c2dm_batch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg
6
+ tmp
7
+ config.yml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("../../lib/c2dm_batch", __FILE__)
@@ -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
@@ -0,0 +1,4 @@
1
+ test:
2
+ email: test@example.com
3
+ password: abc123
4
+ source: your_name
@@ -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,12 @@
1
+ require "rubygems"
2
+ require "yaml"
3
+ require "cgi"
4
+ gem 'typhoeus', '= 0.2.4'
5
+ require "typhoeus"
6
+
7
+ $:.unshift File.dirname(__FILE__)
8
+
9
+ module C2dmBatch
10
+ end
11
+
12
+ require 'c2dm_batch/core'
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe C2dmBatch do
4
+ end
@@ -0,0 +1,8 @@
1
+ require "pp"
2
+ require "bundler"
3
+
4
+ Bundler.require(:development)
5
+
6
+ $root = File.expand_path('../../', __FILE__)
7
+
8
+ require "#{$root}/lib/c2dm_batch"
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