sanjose 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sanjose.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Min Kim
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Sanjose
2
+ **Google Cloud Message.**
3
+
4
+ Sanjose is a simple gem for sending Google Cloud Message.
5
+
6
+ > This project is inspired by [houston](https://github.com/mattt/houston) and named as following the series of world-class command-line utilities.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'sanjose'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install sanjose
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ GCM = Sanjose::Client.new
26
+ GCM.gcm_api_key = "AIzaSyApdin5fMuTSGtW-OTC9gJrm2qz1hJPUzk"
27
+
28
+ # An example of the registration id sent back when a device registers for notifications
29
+ registration_id = "APA91bH35MqYza3xfc2hfag6Rr8VQPSOmi2nrUOPABlFwowfVMZNHaBGBpx-zQ7nuv9qzCEosepUMPKyOrVn0UncZMa__E2sWuM2Q53fjJ5loqIY1QKCza3MkxAu1rvyhhzJP3meEqpmv-kjBuRTeWe_ysRUICupE-awrK1eiStmmm2Y_VBBSs4"
30
+
31
+ # Create a notification that alerts a message to the user.
32
+ notification = Sanjose::Notification.new(devices: [registration_id])
33
+ notification.collapse_key = "Hello, World!"
34
+ notification.data = {foo: "bar"}
35
+
36
+ # And... sent! That's all it takes.
37
+ GCM.push(notification)
38
+ ```
39
+
40
+ ## Contact
41
+
42
+ Min Kim
43
+
44
+ - http://github.com/minsikzzang
45
+ - http://twitter.com/minsikzzang
46
+ - minsikzzang@gmail.com
47
+
48
+ ## License
49
+
50
+ Sanjose is available under the MIT license. See the LICENSE file for more info.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("sanjose.gemspec"))
5
+
6
+ task :build => "#{gemspec.full_name}.gem"
7
+
8
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["sanjose.gemspec"] do
9
+ system "gem build houston.gemspec"
10
+ system "gem install sanjose-#{Houston::VERSION}.gem"
11
+ end
data/bin/gcm ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'commander/import'
4
+
5
+ require 'sanjose'
6
+
7
+ HighLine.track_eof = false # Fix for built-in Ruby
8
+ Signal.trap("INT") {} # Suppress backtrace when exiting command
9
+
10
+ program :version, Sanjose::VERSION
11
+ program :description, 'A command-line interface for sending push notifications for Android'
12
+
13
+ program :help, 'Author', 'Min Kim <minsikzzang@gmail.com>'
14
+ program :help, 'Website', 'https://github.com/minsikzzang'
15
+ program :help_formatter, :compact
16
+
17
+ default_command :help
18
+
19
+ command :push do |c|
20
+ c.syntax = 'gcm push DEVICE [...]'
21
+ c.summary = 'Sends an Google Cloud Message to specified devices'
22
+ c.description = ''
23
+
24
+ c.example 'description', 'gcm push <registration_id> -d "{\"foo\":\"bar\"}"" -k "AIzaSyApdin5fMuTSGtW-OTC9gJrm2qz1hJPUzk" -c "Hello, World!"'
25
+ c.option '-k', '--key KEY', 'Google Cloud Message api key'
26
+ c.option '-c', '--collapse_key CK', 'An arbitrary string (such as "Updates Available") that is used to collapse a group of like messages when the device is offline'
27
+ c.option '-r', '--dry_run DRY_RUN', 'It allows developers to test their request without actually sending a message'
28
+ c.option '-t', '--time_to_live TTL', 'How long (in seconds) the message should be kept on GCM storage if the device is offline.'
29
+ c.option '-p', '--restricted_package_name RPN', 'A string containing the package name of your application.'
30
+ c.option '-d', '--data DATA', 'A JSON object whose fields represents the key-value pairs of the message\'s payload data'
31
+
32
+ c.action do |args, options|
33
+ say_error "One or more device registration_id required" and abort if args.empty?
34
+
35
+ @key = options.key
36
+ say_error "Google Cloud api key is required" and abort unless @key
37
+
38
+ @data = options.data
39
+ @time_to_live = options.time_to_live
40
+ @collapse_key = options.collapse_key
41
+ @restricted_package_name = options.restricted_package_name
42
+ @dry_run = options.dry_run
43
+
44
+ client = Sanjose::Client.new
45
+ client.gcm_api_key = @key
46
+ a = "APA91bH35MqYza3xfc2hfag6Rr8VQPSOmi2nrUOPABlFwowfVMZNHaBGBpx-zQ7nuv9qzCEosepUMPKyOrVn0UncZMa__E2sWuM2Q53fjJ5loqIY1QKCza3MkxAu1rvyhhzJP3meEqpmv-kjBuRTeWe_ysRUICupE-awrK1eiStmmm2Y_VBBSs4"
47
+ b = "APA91bHAX-Owu0Ulm5XubHs7BYy6a4O87Gk8-2JOW3eJ2TauVzWsB0d4yCxjzxMXl40elIIwG3E0Lpvd1_5xGldZm64UO4fqC0RNpvGWKUSr7hMNhavRS8w2NS2XUwYsluysVaXZU0rvmq0zSCH6JHGZiqd0qlUsmg"
48
+
49
+ notification = Sanjose::Notification.new
50
+ args.each do |device|
51
+ notification.add_device(device)
52
+ end
53
+
54
+ notification.data = JSON.parse(@data)
55
+ notification.collapse_key = @collapse_key
56
+ notification.dry_run = @dry_run
57
+ notification.restricted_package_name = @restricted_package_name
58
+ notification.time_to_live = @time_to_live
59
+
60
+ begin
61
+ results = client.push(notification)
62
+ puts results.inspect
63
+ rescue => e
64
+ say_error "Exception sending notification: #{e}" and abort
65
+ end
66
+
67
+ say_ok "Push notifications successfully sent"
68
+ end
69
+ end
@@ -0,0 +1,154 @@
1
+ module Sanjose
2
+ GCM_GATEWAY_URI = "https://android.googleapis.com/gcm/send"
3
+ MULTICAST_SIZE = 1000
4
+ TOKEN_CANONICAL_REG_ID = 'registration_id'
5
+ JSON_SUCCESS = 'success'
6
+ JSON_FAILURE ='failure'
7
+ JSON_CANONICAL_IDS = 'canonical_ids'
8
+ JSON_MULTICAST_ID = 'multicast_id'
9
+ JSON_RESULTS = 'results'
10
+ JSON_MESSAGE_ID = 'message_id'
11
+ JSON_ERROR = 'error'
12
+ ERROR_UNAVAILABLE = "Unavailable";
13
+
14
+ # Initial delay before first retry
15
+ BACKOFF_INITIAL_DELAY = 1000
16
+
17
+ # Maximum delay before a retry.
18
+ MAX_BACKOFF_DELAY = 1024000
19
+
20
+ class Client
21
+ attr_accessor :gcm_gateway_uri, :gcm_api_key
22
+
23
+ def initialize
24
+ @gcm_gateway_uri = GCM_GATEWAY_URI
25
+ @gcm_api_key = ENV['GCM_API_KEY']
26
+ end
27
+
28
+ def push(notification, retries = 5)
29
+ return if not notification or notification.sent?
30
+
31
+ attempt = 0
32
+ try_again = true
33
+ muticast_ids = []
34
+ results = {}
35
+ unsent_reg_ids = notification.devices
36
+ backoff = BACKOFF_INITIAL_DELAY
37
+
38
+ while try_again do
39
+ attempt += 1
40
+ multicast_result = push_no_retry(notification,
41
+ connection_options_for_endpoint(:gateway), unsent_reg_ids)
42
+
43
+ if multicast_result
44
+ muticast_ids << multicast_result.multicast_id
45
+ unsent_reg_ids = update_status(unsent_reg_ids, results, multicast_result)
46
+ end
47
+
48
+ try_again = !unsent_reg_ids.empty? and attempt <= retries
49
+
50
+ if try_again
51
+ sleep_time = backoff / 2 + rand(backoff)
52
+ sleep(sleep_time / 1000)
53
+ if 2 * backoff < MAX_BACKOFF_DELAY
54
+ backoff *= 2
55
+ end
56
+ end
57
+ end
58
+
59
+ # calculate summary
60
+ success = 0
61
+ failure = 0
62
+ canonical_ids = 0
63
+
64
+ results.values.each do |result|
65
+ if result.message_id
66
+ success += 1
67
+ if result.canonical_reg_id
68
+ canonical_ids += 1
69
+ end
70
+ else
71
+ failure += 1
72
+ end
73
+ end
74
+
75
+ # build a new object with the overall result
76
+ multicast_id = muticast_ids.pop(0)
77
+ builder = MulticastResult.new(
78
+ :success => success,
79
+ :failure => failure,
80
+ :canonical_ids => canonical_ids,
81
+ :multicast_id => multicast_id)
82
+ builder.retry_multicast_ids = muticast_ids
83
+
84
+ # add results, in the same order as the input
85
+ notification.devices.each do |device|
86
+ builder.add_result(results[device])
87
+ end
88
+
89
+ builder
90
+ end
91
+
92
+ private
93
+ def update_status(unsent_reg_ids, all_results, multicast_result)
94
+ results = multicast_result.results
95
+ if results.length != unsent_reg_ids.length
96
+ # should never heppan, unless there is a flaw in the algorithm
97
+ raise RuntimeError
98
+ "internal error: sizes do not match. current_results: #{results}; unsent_reg_ids: #{unsent_reg_ids}"
99
+ end
100
+
101
+ new_unsent_reg_ids = []
102
+ i = 0
103
+ unsent_reg_ids.each do |reg_id|
104
+ result = results.at(i)
105
+ all_results[reg_id] = result
106
+ i += 1
107
+ if result.error and result.error == ERROR_UNAVAILABLE
108
+ new_unsent_reg_ids << reg_id
109
+ end
110
+ end
111
+
112
+ new_unsent_reg_ids
113
+ end
114
+
115
+ def push_no_retry(notification, uri, reg_ids)
116
+ multicast_result = nil
117
+ messages = notification.clone
118
+ messages.devices = reg_ids
119
+
120
+ Connection.open(uri) do |connection|
121
+ message = messages.message
122
+
123
+ # Need to put gcm api keyand content length in header
124
+ headers = {"Authorization" => "key=#{@gcm_api_key}",
125
+ "Content-type" => "application/json"}
126
+ response = connection.post(uri.path, message, headers)
127
+ return nil if response.code != "200"
128
+
129
+ json = JSON.parse(response.body)
130
+ multicast_result = MulticastResult.new(
131
+ :success => json[JSON_SUCCESS],
132
+ :failure => json[JSON_FAILURE],
133
+ :canonical_ids => json[JSON_CANONICAL_IDS],
134
+ :multicast_id => json[JSON_MULTICAST_ID])
135
+ results = json[JSON_RESULTS]
136
+ results.each do |result|
137
+ multicast_result.add_result(
138
+ Result.new(:message_id => result[JSON_MESSAGE_ID],
139
+ :canonical_reg_id => result[TOKEN_CANONICAL_REG_ID],
140
+ :error => result[JSON_ERROR]))
141
+ end
142
+ end
143
+ multicast_result
144
+ end
145
+
146
+ def connection_options_for_endpoint(endpoint = :gateway)
147
+ uri = case endpoint
148
+ when :gateway then URI(@gcm_gateway_uri)
149
+ else
150
+ raise ArgumentError
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,26 @@
1
+ require 'net/http'
2
+
3
+ module Sanjose
4
+ class Connection
5
+ class << self
6
+ def open(uri)
7
+ return if uri == nil
8
+
9
+ connection = connection_for_uri(uri)
10
+ connection.start
11
+
12
+ yield connection
13
+
14
+ connection.finish
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def self.connection_for_uri(uri)
21
+ connection = Net::HTTP.new(uri.host, uri.port)
22
+ connection.use_ssl = uri.scheme == 'https'
23
+ connection
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,104 @@
1
+ require 'json'
2
+
3
+ module Sanjose
4
+ class Notification
5
+ attr_accessor :data, :collapse_key, :delay_when_idle, :time_to_live,
6
+ :dry_run, :restricted_package_name, :devices
7
+ attr_reader :sent_at
8
+
9
+ def initialize(options = {})
10
+ # Required field
11
+ # A string array with the list of devices (registration IDs) receiving
12
+ # the message. It must contain at least 1 and at most 1000 registration IDs.
13
+ if !options[:devices]
14
+ @devices = []
15
+ else
16
+ @devices = options[:devices]
17
+ end
18
+
19
+ # Optional fields
20
+ # An arbitraru string (such as "Updates Available") that is used to
21
+ # collapse a group of like messages when the device is offline, so
22
+ # that only the last message gets sent to the client.
23
+ @collapse_key = options[:collapse_key]
24
+
25
+ # If included, indicates that the message should not be sent immediately
26
+ # if the device is idle.
27
+ @delay_when_idle = options[:delay_when_idle]
28
+
29
+ # How long (in seconds) the message should be kept on GCM storage if the
30
+ # device is offline.
31
+ @time_to_live = options[:time_to_live]
32
+
33
+ # If included, allows developers to test their request without actually
34
+ # sending a message. Optional. The default value is false, and must be a
35
+ # JSON boolean.
36
+ @dry_run = options[:dry_run]
37
+
38
+ # A string containing the package name of your application. When set,
39
+ # messages will only be sent to registration IDs that match the package
40
+ # name.
41
+ @restricted_package_name = options[:restricted_package_name]
42
+
43
+ # A JSON object whose fields represents the key-value pairs of the message's
44
+ # payload data. There is no limit on the number of key/value pairs, though
45
+ # there is a limit on the total size of the message (4kb).
46
+ @data = options[:data]
47
+ end
48
+
49
+ def add_device(device)
50
+ @devices << device
51
+ end
52
+
53
+ def device_size
54
+ @devices.length
55
+ end
56
+
57
+ def message
58
+ # plan-text format
59
+ # 'registration_id=APA91bH35MqYza3xfc2hfag6Rr8VQPSOmi2nrUOPABlFwowfVMZNHaBGBpx-zQ7nuv9qzCEosepUMPKyOrVn0UncZMa__E2sWuM2Q53fjJ5loqIY1QKCza3MkxAu1rvyhhzJP3meEqpmv-kjBuRTeWe_ysRUICupE-awrK1eiStmmm2Y_VBBSs4'
60
+ # json format
61
+ # '{"registration_ids":["APA91bH35MqYza3xfc2hfag6Rr8VQPSOmi2nrUOPABlFwowfVMZNHaBGBpx-zQ7nuv9qzCEosepUMPKyOrVn0UncZMa__E2sWuM2Q53fjJ5loqIY1QKCza3MkxAu1rvyhhzJP3meEqpmv-kjBuRTeWe_ysRUICupE-awrK1eiStmmm2Y_VBBSs4","APA91bHAX-Owu0Ulm5XubHs7BYy6a4O87Gk8-2JOW3eJ2TauVzWsB0d4yCxjzxMXl40elIIwG3E0Lpvd1_5xGldZm64UO4fqC0RNpvGWKUSr7hMNhavRS8w2NS2XUwYsluysVaXZU0rvmq0zSCH6JHGZiqd0qlUsmg"]}'
62
+
63
+ json = {}
64
+ if @devices.empty?
65
+ raise ArgumentError 'devices cannot be empty'
66
+ else
67
+ json['registration_ids'] = @devices
68
+ end
69
+
70
+ if @collapse_key
71
+ json['collapse_key'] = @collapse_key
72
+ end
73
+ if @delay_when_idle
74
+ json['delay_when_idle'] = @delay_when_idle
75
+ end
76
+
77
+ if @time_to_live
78
+ json['time_to_live'] = @time_to_live
79
+ end
80
+
81
+ if @data
82
+ json['data'] = @data
83
+ end
84
+
85
+ if @restricted_package_name
86
+ json['restricted_package_name'] = @restricted_package_name
87
+ end
88
+
89
+ if @dry_run
90
+ json['dry_run'] = @dry_run
91
+ end
92
+
93
+ json.to_json
94
+ end
95
+
96
+ def mark_as_sent!
97
+ @sent_at = Time.now
98
+ end
99
+
100
+ def sent?
101
+ !!@sent_at
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module Sanjose
2
+ VERSION = "0.0.1"
3
+ end
data/lib/sanjose.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "sanjose/version"
2
+ require 'sanjose/client'
3
+ require 'sanjose/connection'
4
+ require 'sanjose/notification'
5
+ require 'sanjose/multicast_result'
6
+ require 'sanjose/result'
7
+
8
+
data/sanjose.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sanjose'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "sanjose"
8
+ s.version = Sanjose::VERSION
9
+ s.authors = ["Min Kim"]
10
+ s.email = ["minsikzzang@gmail.com", "minsik.kim@livestation.com"]
11
+ s.description = "Send Google Cloud Message"
12
+ s.summary = "Sanjose is a simple gem for sending Google Cloud Message"
13
+ s.homepage = "http://github.com/minsikzzang/sanjose"
14
+
15
+ s.add_dependency "commander", "~> 4.1.2"
16
+ s.add_dependency "json", "~> 1.7.3"
17
+
18
+ s.add_development_dependency "rspec", "~> 0.6.1"
19
+ s.add_development_dependency "rake", "~> 0.9.2"
20
+
21
+ s.files = `git ls-files`.split($/)
22
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
24
+ s.require_paths = ["lib"]
25
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sanjose
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Min Kim
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: commander
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 4.1.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 4.1.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.7.3
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.7.3
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.6.1
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.9.2
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.9.2
78
+ description: Send Google Cloud Message
79
+ email:
80
+ - minsikzzang@gmail.com
81
+ - minsik.kim@livestation.com
82
+ executables:
83
+ - gcm
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - Gemfile
89
+ - LICENSE.txt
90
+ - README.md
91
+ - Rakefile
92
+ - bin/gcm
93
+ - lib/sanjose.rb
94
+ - lib/sanjose/client.rb
95
+ - lib/sanjose/connection.rb
96
+ - lib/sanjose/notification.rb
97
+ - lib/sanjose/version.rb
98
+ - sanjose.gemspec
99
+ homepage: http://github.com/minsikzzang/sanjose
100
+ licenses: []
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 1.8.24
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Sanjose is a simple gem for sending Google Cloud Message
123
+ test_files: []