higcm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gcm (0.0.1)
5
+ typhoeus (~> 0.3.3)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.3)
11
+ json (1.7.3)
12
+ mime-types (1.19)
13
+ rake (0.9.2.2)
14
+ rspec (2.11.0)
15
+ rspec-core (~> 2.11.0)
16
+ rspec-expectations (~> 2.11.0)
17
+ rspec-mocks (~> 2.11.0)
18
+ rspec-core (2.11.1)
19
+ rspec-expectations (2.11.1)
20
+ diff-lcs (~> 1.1.3)
21
+ rspec-mocks (2.11.1)
22
+ typhoeus (0.3.3)
23
+ mime-types
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ gcm!
30
+ json
31
+ rake
32
+ rspec (~> 2.6)
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ GCM Ruby wrapper [![Build Status](https://secure.travis-ci.org/hifrank/higcm.png?branch=master)](http://travis-ci.org/hifrank/higcm)
2
+ ===
3
+ This is Ruby libray for push message to device via [Google Cloud Messaging for Android](http://developer.android.com/guide/google/gcm/index.html)
4
+ # Features
5
+ ## parallel gcm messaging
6
+ use [Typhoeus](http://typhoeus.github.com/) as http client so it is able to send gcm messges in parallel way.
7
+
8
+ ## handler
9
+ parse gcm response with GCM::Handler according to [GCM Response format](http://developer.android.com/guide/google/gcm/gcm.html#response),
10
+ into serveral kinds of responses, say, success, fails, retry, unregister, you can only handle events you care about.
11
+
12
+ # Usage
13
+
14
+ ## send a message
15
+ <code>
16
+ sender = HiGCM::Sender.new(your_api_key)
17
+ registration_ids = [1, 2, 3]
18
+ opts = {
19
+ :collapse_key => "test"
20
+ :data => { :mesg => "hello GCM" }
21
+ }
22
+ response = sender.send(registration_ids, opts)
23
+ ...
24
+ </code>
25
+
26
+ ## send a message with handler
27
+ <code>
28
+ # prepare handler for retry and unregister event
29
+ handler = HiGCM::Handler.new
30
+ handler.do_retry do | retry_ids, opts, response |
31
+ retry_ids.each do | reg_id, retry_after |
32
+ # do retry things
33
+ end
34
+ end
35
+ #prepare for renew registration_ids
36
+ handler.do_renew_token do | renew_ids, response |
37
+ renew_ids.each do | reg_id, new_reg_id |
38
+ # do renew stuff
39
+ end
40
+ end
41
+
42
+ sender = HiGCM::Sender.new(your_api_key)
43
+ registration_ids = [1, 2, 3]
44
+ opts = {
45
+ :collapse_key => "test"
46
+ :data => { :mesg => "hello GCM" }
47
+ }
48
+ sender.send(registration_ids, opts)
49
+ </code>
50
+
51
+ ## send a muti-messages in parallel way
52
+ <code>
53
+ sender = HiGCM::Sender.new(your_api_key)
54
+
55
+ # queue your messages first
56
+ something.each do | registration_id, name |
57
+ # prepare handler for retry and unregister event
58
+ handler = HiGCM::Handler.new
59
+ handler.do_retry do | retry_ids, opts, response |
60
+ retry_ids.each do | reg_id, retry_after |
61
+ # do retry things
62
+ end
63
+ end
64
+ #prepare for renew registration_ids
65
+ handler.do_renew_token do | renew_ids, response |
66
+ renew_ids.each do | reg_id, new_reg_id |
67
+ # do renew stuff
68
+ end
69
+ end
70
+
71
+ opts = {
72
+ :collapse_key => "test"
73
+ :data => { :mesg => "hello #{name}" }
74
+ }
75
+ sender.send_async(registration_id, opts, handler)
76
+ end
77
+ # now fire
78
+ sender.send_async_run
79
+ </code>
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/higcm.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "higcm"
7
+ s.version = "0.0.1"
8
+ s.authors = ["hifrank"]
9
+ s.email = "frank_chen@htc.com"
10
+ s.homepage = "https://github.com/hifrank/higcm"
11
+ s.summary = "ruby wrapper for GCM google cloud messaging."
12
+ s.description = %q{ruby wrapper for GCM google cloud messaging.}
13
+ #s.extensions = [""]
14
+ s.files = (`git ls-files ext lib spec`.split("\n")) + [
15
+ 'README.md',
16
+ 'Gemfile',
17
+ 'Gemfile.lock',
18
+ 'Rakefile',
19
+ 'higcm.gemspec'
20
+ ]
21
+ s.platform = Gem::Platform::RUBY
22
+ s.require_path = 'lib'
23
+ s.rubyforge_project = '[none]'
24
+
25
+ s.add_runtime_dependency 'typhoeus', ['~> 0.3.3']
26
+ s.add_development_dependency 'rspec', ["~> 2.6"]
27
+ s.add_development_dependency 'json', [">= 0"]
28
+ s.add_development_dependency 'rake', [">= 0"]
29
+ end
30
+
@@ -0,0 +1,105 @@
1
+ require 'json'
2
+
3
+ module HiGCM
4
+
5
+ class Handler
6
+
7
+ attr_accessor :retry_conditions
8
+
9
+ def initialize
10
+ @retry_ids = {}
11
+ @fail_ids = {}
12
+ @success_ids = {}
13
+ @renew_ids = {}
14
+ @retry_conditions =[ 'InternalServerError', 'Timout', 'Unavailable' ]
15
+ @unregister_conditions = [ 'NotRegistered', 'InvalidRegistration' ]
16
+ end
17
+
18
+ def handle(registration_ids, opts, response)
19
+ begin
20
+ @code = response.code.to_i
21
+ @body = JSON.parse(response.body)
22
+ #Honor Retry-After
23
+ @retry_after = (response.headers['Retry-After'].nil? ? 0 : response.headers['Retry-After'])
24
+ rescue Exception => e
25
+ @code = 99
26
+ @error_message = "unexpected error, response: #{response.body}, exception: #{e.inspect}"
27
+ end
28
+ case @code
29
+ when 200
30
+ #200 Message was processed successfully. The response body will contain more details about the message status, but its format will depend whether the request was JSON or plain text. See Interpreting a success response for more details.
31
+ # handle success case, http://developer.android.com/guide/google/gcm/gcm.html#success
32
+ @body['results'].each_with_index do | rs, index |
33
+ reg_id = registration_ids[index]
34
+ #handle fail
35
+ if rs.key?('error')
36
+ @error_message = rs['error']
37
+ if @retry_conditions.include?(rs['error'])
38
+ @retry_ids[reg_id] = @retry_after
39
+ @error_message << ", retry after #{@retry_after}"
40
+ end
41
+ @fail_ids[reg_id] = @error_message
42
+ #handle success
43
+ elsif rs.key?('message_id')
44
+ @success_ids[reg_id] = rs['message_id']
45
+ if rs.key?('registration_id')
46
+ @renew_ids[reg_id] = rs['registration_id']
47
+ end
48
+ else
49
+ #should not jump here
50
+ end
51
+ end
52
+ @do_success.call(@success_ids, response) if @success_ids.count > 0 && @do_success
53
+ @do_fail.call(@fail_ids, response) if @fail_ids.count > 0 && @do_fail
54
+ @do_renew_token.call(@renew_ids, response) if @renew_ids.count > 0 && @do_renew_token
55
+ @do_retry.call(@retry_ids, opts, response) if @retry_ids.count > 0 && @do_retry
56
+ #TODO need to check what kinf of response will return
57
+ when 400, 401
58
+ #400 Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields (for instance, passing a string where a number was expected). The exact failure reason is described in the response and the problem should be addressed before the request can be retried.
59
+ #401 There was an error authenticating the sender account.
60
+ registration_ids.each do | reg_id |
61
+ if 400 == @code
62
+ error_message = 'request could not be parsed as JSON'
63
+ else
64
+ error_message = 'There was an error authenticating the sender account'
65
+ end
66
+ @fail_ids[reg_id] = error_message
67
+ end
68
+ @do_fail.call(@fail_ids, response) if @do_fail && @fail_ids.count > 0
69
+ #TODO need to check what kinf of response will return
70
+ when 500, 503
71
+ #500 There was an internal error in the GCM server while trying to process the request. trouble shooting http://developer.android.com/guide/google/gcm/gcm.html#internal_error
72
+ #503 Indicates that the server is temporarily unavailable (i.e., because of timeouts, etc ). Sender must retry later, honoring any Retry-After header included in the response. Application servers must implement exponential back-off. The GCM server took too long to process the request. Troubleshoot
73
+ registration_ids.each do | reg_id |
74
+ @retry_ids[reg_id] = @retry_after
75
+ end
76
+ @do_retry.call(@retry_ids, opts) if @do_retry && @retry_ids.count > 0
77
+ else
78
+ registration_ids.each do | reg_id |
79
+ @fail_ids[reg_id] = @error_message
80
+ end
81
+ @do_fail.call(@fail_ids, response) if @do_fail && @fail_ids.count > 0
82
+ end
83
+ end
84
+
85
+ # success_ids
86
+ def do_success(&block)
87
+ @do_success = block
88
+ end
89
+
90
+ # retry_ids, opts, retry_after
91
+ def do_retry(&block)
92
+ @do_retry = block
93
+ end
94
+
95
+ def do_fail(&block)
96
+ @do_fail = block
97
+ end
98
+
99
+ # renew_ids(hash)
100
+ def do_renew_token(&block)
101
+ @do_renew_token = block
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,73 @@
1
+ require 'typhoeus'
2
+
3
+ module HiGCM
4
+
5
+ class SenderError < StandardError; end
6
+
7
+ class Sender
8
+ attr_accessor :api_key, :api_status, :hydra, :requests
9
+
10
+ OPTIONAL_OPTIONS = {
11
+ :collapse_key => String,
12
+ :data => Hash,
13
+ :delay_while_idle => Fixnum,
14
+ :time_to_live => Fixnum
15
+ }
16
+
17
+ def initialize(api_key)
18
+ @api_key = api_key
19
+ raise SenderError.new("api_key is necessary for #{self.class}") if api_key.nil? || api_key.empty?
20
+ end
21
+
22
+ def send(registration_ids, opts={}, handler=nil)
23
+ request = send_async(registration_ids, opts, handler)
24
+ send_async_run
25
+ request.handled_response
26
+ end
27
+
28
+ #http://developer.android.com/guide/google/gcm/gcm.html#server
29
+ def send_async(registration_ids, opts={}, handler=nil)
30
+
31
+ headers = {
32
+ 'Content-Type' => 'application/json',
33
+ 'Authorization' => sprintf("key=%s", @api_key)
34
+ }
35
+
36
+ body = {
37
+ 'registration_ids' => registration_ids,
38
+ }
39
+
40
+ #fill up option
41
+ OPTIONAL_OPTIONS.each do | key, type |
42
+ if opts.key?(key)
43
+ raise SenderError.new("#{key} should be Type #{type}") unless opts[key].is_a?(type)
44
+ body[key] = opts[key]
45
+ end
46
+ end
47
+
48
+ request = Typhoeus::Request.new(
49
+ 'https://android.googleapis.com/gcm/send',
50
+ :headers => headers,
51
+ :method => :post,
52
+ :body => body.to_json,
53
+ :follow_location => true
54
+ )
55
+
56
+ @hydra ||= Typhoeus::Hydra.new
57
+
58
+ request.on_complete do | response |
59
+ handler.handle(registration_ids, opts, response)
60
+ end
61
+
62
+ @hydra.queue(request)
63
+
64
+ request
65
+ end
66
+
67
+ def send_async_run
68
+ # handle response according to http://developer.android.com/guide/google/gcm/gcm.html#response
69
+ @hydra.run
70
+ end
71
+
72
+ end
73
+ end
data/lib/higcm.rb ADDED
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+
3
+ require 'typhoeus'
4
+ require 'higcm/sender'
5
+ require 'higcm/handler'
6
+
7
+ module HiGCM
8
+ end
@@ -0,0 +1,13 @@
1
+ { "multicast_id": 216,
2
+ "success": 3,
3
+ "failure": 3,
4
+ "canonical_ids": 1,
5
+ "results": [
6
+ { "message_id": "1:0408" },
7
+ { "error": "Unavailable" },
8
+ { "error": "InvalidRegistration" },
9
+ { "message_id": "1:1516" },
10
+ { "message_id": "1:2342", "registration_id": "32" },
11
+ { "error": "NotRegistered"}
12
+ ]
13
+ }
@@ -0,0 +1,66 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe HiGCM::Handler do
4
+ it "#handle should handle various response in when response code is 500" do
5
+ end
6
+
7
+ it "#handle should handle various response when response code is 200" do
8
+ @raw_gcm_response = File.read("#{File.dirname(__FILE__)}/../../fixtures/gcm_response_200.json")
9
+ @retry_after = 10
10
+ @stub_gcm_response = Typhoeus::Response.new(
11
+ :code => 200,
12
+ :headers => {"Retry-After" => @retry_after},
13
+ :body => @raw_gcm_response,
14
+ :time => 0.1
15
+ )
16
+ @api_key = 'foo'
17
+ sender = HiGCM::Sender.new(@api_key)
18
+ sender.hydra = Typhoeus::Hydra.hydra
19
+ sender.hydra.stub(:post, 'https://android.googleapis.com/gcm/send').and_return(@stub_gcm_response)
20
+
21
+ _fails = 0
22
+ _success = 0
23
+ _renew = 0
24
+ _retry = 0
25
+
26
+ _updated_token = { 5 => "32"}
27
+
28
+ handler = HiGCM::Handler.new
29
+ handler.do_success do | succes_ids, response |
30
+ @success_ids = succes_ids
31
+ @success_response = response
32
+ end
33
+ handler.do_retry do | retry_ids, opts, response |
34
+ @retry_ids = retry_ids
35
+ @retry_opts = opts
36
+ @retry_response = response
37
+ end
38
+ handler.do_fail do | fail_ids, response |
39
+ @fail_ids = fail_ids
40
+ @fail_response = response
41
+ end
42
+ handler.do_renew_token do | renew_ids, response |
43
+ @renew_ids = renew_ids
44
+ @renew_response = response
45
+ end
46
+
47
+ registration_ids = [1, 2, 3, 4, 5, 6]
48
+ sender.send_async(registration_ids, {}, handler)
49
+ sender.send_async_run
50
+
51
+ @fail_ids.should == { 2 => "Unavailable, retry after #{@retry_after}", 3 => "InvalidRegistration", 6 => "NotRegistered" }
52
+ @fail_response.is_a?(Typhoeus::Response).should == true
53
+
54
+ @retry_ids.should == { 2 => 10 }
55
+ @retry_opts.should == {}
56
+ @retry_response.is_a?(Typhoeus::Response).should == true
57
+
58
+ @renew_ids.should == { 5 => "32" }
59
+ @renew_response.is_a?(Typhoeus::Response).should == true
60
+
61
+ @success_ids.should == { 1 => "1:0408", 4 => "1:1516", 5 => "1:2342" }
62
+ @success_response.is_a?(Typhoeus::Response).should == true
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,87 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe HiGCM::Sender do
4
+
5
+ before(:each) do
6
+ @api_key = 'foo'
7
+ @registration_ids = [1, 2, 3, 4, 5, 6]
8
+ @raw_gcm_response = File.read("#{File.dirname(__FILE__)}/../../fixtures/gcm_response_200.json")
9
+ @retry_after = 10
10
+ @stub_gcm_response = Typhoeus::Response.new(
11
+ :code => 200,
12
+ :headers => {"Retry-After" => @retry_after},
13
+ :body => @raw_gcm_response,
14
+ :time => 0.1
15
+ )
16
+ @sender = HiGCM::Sender.new(@api_key)
17
+ @sender.hydra = Typhoeus::Hydra.new
18
+ @sender.hydra.stub(:post, 'https://android.googleapis.com/gcm/send').and_return(@stub_gcm_response)
19
+ end
20
+
21
+ describe "#initialize" do
22
+ it "should raise exception when api_key does not given when init" do
23
+ expect { sender = HiGCM::Sender.new }.to raise_error
24
+ end
25
+
26
+ it "should raise exception when given api_key is nil or empty" do
27
+ expect { sender = HiGCM::Sender.new ""}.to raise_error
28
+ expect { sender = HiGCM::Sender.new nil}.to raise_error
29
+ end
30
+
31
+ it "should raise exception when given api_key is nil or empty" do
32
+ sender = HiGCM::Sender.new(@api_key)
33
+ sender.is_a?(HiGCM::Sender).should == true
34
+ sender.api_key.should == @api_key
35
+ end
36
+ end
37
+
38
+ describe "#send" do
39
+ it "should return Typhoeus::Request" do
40
+ response = @sender.send_async(@registration_ids, {})
41
+ response.class.should == Typhoeus::Request
42
+ end
43
+
44
+ it "should call handler.handle after request is completed" do
45
+ handler = double(HiGCM::Handler)
46
+ handler.should_receive(:handle).with(@registration_ids, {}, @stub_gcm_response)
47
+ @sender.send(@registration_ids, {}, handler)
48
+ end
49
+ end
50
+
51
+ describe "#send_async" do
52
+
53
+ it "should call handler.handle after request is completed" do
54
+ handler = double(HiGCM::Handler)
55
+ handler.should_receive(:handle).with(@registration_ids, {}, @stub_gcm_response)
56
+ @sender.send_async(@registration_ids, {}, handler)
57
+ @sender.send_async_run
58
+ end
59
+
60
+ it "should raise exception if opts[:collapse_key] is not String, empty string is acceptable" do
61
+ expect { @sender.send_async(@registration_ids, {:collapse_key => nil }, HiGCM::Handler.new) }.to raise_error(HiGCM::SenderError)
62
+ expect { @sender.send_async(@registration_ids, {:collapse_key => "" }, HiGCM::Handler.new) }.not_to raise_error(HiGCM::SenderError)
63
+ end
64
+
65
+ it "should raise exception if opts[:data] is not Hash, empty hash is acceptable" do
66
+ expect { @sender.send_async(@registration_ids, {:data => [] }, HiGCM::Handler.new) }.to raise_error(HiGCM::SenderError)
67
+ expect { @sender.send_async(@registration_ids, {:data => {} }, HiGCM::Handler.new) }.not_to raise_error(HiGCM::SenderError)
68
+ end
69
+
70
+ it "should raise exception if opts[:delay_while_idle] && opts[:time_to_live] is not Fixnum" do
71
+ expect { @sender.send_async(@registration_ids, {:delay_while_idle => [] }, HiGCM::Handler.new) }.to raise_error(HiGCM::SenderError)
72
+ expect { @sender.send_async(@registration_ids, {:delay_while_idle => 1 }, HiGCM::Handler.new) }.not_to raise_error(HiGCM::SenderError)
73
+ expect { @sender.send_async(@registration_ids, {:time_to_live => [] }, HiGCM::Handler.new) }.to raise_error(HiGCM::SenderError)
74
+ expect { @sender.send_async(@registration_ids, {:time_to_live => 1 }, HiGCM::Handler.new) }.not_to raise_error(HiGCM::SenderError)
75
+ end
76
+ end
77
+
78
+ describe "#send_async_run" do
79
+ it "should run Typhoeus::Hydra when send requests" do
80
+ hydra = double(Typhoeus::Hydra)
81
+ @sender.hydra = hydra
82
+ hydra.should_receive(:run).once
83
+ @sender.send_async_run
84
+ end
85
+ end
86
+
87
+ end
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ require 'json'
3
+ require "rspec"
4
+
5
+ # gem install redgreen for colored test output
6
+ begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
7
+
8
+ path = File.expand_path(File.dirname(__FILE__) + "/../lib/")
9
+
10
+ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
11
+
12
+ require 'higcm'
13
+
14
+ RSpec.configure do |config|
15
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: higcm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - hifrank
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-21 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: typhoeus
16
+ requirement: &2177156140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.3.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2177156140
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2177142980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.6'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2177142980
36
+ - !ruby/object:Gem::Dependency
37
+ name: json
38
+ requirement: &2177142280 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2177142280
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &2177141340 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2177141340
58
+ description: ruby wrapper for GCM google cloud messaging.
59
+ email: frank_chen@htc.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - lib/higcm.rb
65
+ - lib/higcm/handler.rb
66
+ - lib/higcm/sender.rb
67
+ - spec/fixtures/gcm_response_200.json
68
+ - spec/lib/higcm/handler_spec.rb
69
+ - spec/lib/higcm/sender_spec.rb
70
+ - spec/spec_helper.rb
71
+ - README.md
72
+ - Gemfile
73
+ - Gemfile.lock
74
+ - Rakefile
75
+ - higcm.gemspec
76
+ homepage: https://github.com/hifrank/higcm
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project: ! '[none]'
96
+ rubygems_version: 1.8.10
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: ruby wrapper for GCM google cloud messaging.
100
+ test_files: []