ass 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/LICENSE.txt +20 -0
  2. data/README.md +69 -0
  3. data/VERSION +1 -0
  4. data/ass.yml +11 -0
  5. data/bin/ass +14 -0
  6. data/cron +36 -0
  7. data/lib/ass.rb +203 -0
  8. metadata +331 -0
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Eiffel Qiu
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ ass
2
+ =======
3
+ Apple Service Server written with Sinatra and Sequel(Sqlite3) with apple push notification and in app purchase support.
4
+
5
+ Feature:
6
+ =======
7
+
8
+ 1. rubygem, simple to install.
9
+ 2. provide pem file is the only requirement.
10
+ 3. no need to setup database, using sqlite3.
11
+ 4. provide default config file(ass.yml) with default value.
12
+
13
+ Requirement
14
+ =======
15
+
16
+ Sqlite3
17
+ -------
18
+
19
+ CentOS/Redhat:
20
+
21
+ $ yum install sqlite3
22
+
23
+ Debian/Ubuntu:
24
+
25
+ $ apt-get install sqlite3
26
+
27
+ FreeBSD:
28
+
29
+ $ cd /usr/ports/databases/sqlite34
30
+ $ sudo make install clean
31
+
32
+ Mac:
33
+
34
+ $ brew install sqlite3 # Homebrew
35
+ $ port install sqlite3 # Macport
36
+
37
+ Installation
38
+ =======
39
+
40
+ $ sudo gem install ass
41
+
42
+ Usage
43
+ =======
44
+ 1. under the current directory, provide single pem file combined with certificate and key, HOWTO ([Check this link](http://www.raywenderlich.com/3443/apple-push-notification-services-tutorial-part-12))
45
+
46
+ 2. edit ass.yml. (when you run ass first time, it will generate this file) ([Check this link](https://raw.github.com/eiffelqiu/ass/master/ass.yml))
47
+
48
+ 3. provide a cron script under current directory, default named "cron" according to ass.yml
49
+
50
+ 4. start ass server, default port is 4567(sinatra's default port)
51
+
52
+ ![ass usage](https://raw.github.com/eiffelqiu/ass/master/doc/capture1.png)
53
+
54
+ Contributing to ass
55
+ =======
56
+
57
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
58
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
59
+ * Fork the project.
60
+ * Start a feature/bugfix branch.
61
+ * Commit and push until you are happy with your contribution.
62
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
63
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
64
+
65
+ Copyright
66
+ =======
67
+
68
+ Copyright (c) 2012 Eiffel Qiu. See LICENSE.txt for further details.
69
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/ass.yml ADDED
@@ -0,0 +1,11 @@
1
+ port: 4567
2
+ mode: development
3
+ cron: cron
4
+ timer: 0 # minute
5
+ development:
6
+ - certificate : ck_dev.pem
7
+ production:
8
+ - certificate : ck_prod.pem
9
+ apps:
10
+ - app1
11
+ - app2
data/bin/ass ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'rubygems' unless defined?(Gem)
5
+ require 'sinatra/base'
6
+ require 'thread'
7
+
8
+ class Object
9
+ alias sh system
10
+ end
11
+
12
+ require 'ass'
13
+
14
+ App.run!
data/cron ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'yaml'
5
+ require 'cgi'
6
+ require 'digest/sha2'
7
+
8
+ ###########################################################################
9
+ ####
10
+ #### This script is only for demo purpose, you can write this script
11
+ #### with any language you like(python, perl, shell etc)
12
+ ####
13
+ ###########################################################################
14
+
15
+ ############################################################
16
+ ## Configuration Setup
17
+ ############################################################
18
+ env = ENV['SINATRA_ENV'] || "development"
19
+ config = YAML.load_file("#{Dir.pwd}/ass.yml")
20
+ $port = config['port'] || 4567
21
+ $apps = config['apps'] || []
22
+
23
+ ############################################################
24
+ ## Using curl command to send push notification message
25
+ ############################################################
26
+
27
+ @message = CGI::escape("sample push notification message")
28
+
29
+ #@pid = Digest::SHA2.hexdigest("#{Time.now.to_i}")
30
+ @pid = "#{Time.now.to_i}"
31
+
32
+ $apps.each { |app|
33
+ sleep 1
34
+ puts "curl http://localhost:#{$port}/v1/apps/#{app}/push/#{@message}/#{@pid}"
35
+ system "curl http://localhost:#{$port}/v1/apps/#{app}/push/#{@message}/#{@pid}"
36
+ }
data/lib/ass.rb ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'rubygems'
5
+ require 'sinatra'
6
+ require 'sequel'
7
+ require 'socket'
8
+ require 'openssl'
9
+ require 'cgi'
10
+ require 'rufus/scheduler'
11
+ require 'eventmachine'
12
+ require 'sinatra/base'
13
+ require 'yaml'
14
+
15
+ ############################################################
16
+ ## Initilization Setup
17
+ ############################################################
18
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
19
+ root_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
20
+ unless $LOAD_PATH.include?(lib_dir)
21
+ $LOAD_PATH << lib_dir
22
+ end
23
+
24
+ unless File.exist?("#{Dir.pwd}/ass.yml") then
25
+ puts 'create config file: ass.yml'
26
+ system "cp #{root_dir}/ass.yml #{Dir.pwd}/ass.yml"
27
+ end
28
+
29
+ unless File.exist?("#{Dir.pwd}/cron") then
30
+ puts "create a demo 'cron' script"
31
+ system "cp #{root_dir}/cron #{Dir.pwd}/cron"
32
+ end
33
+
34
+ ############################################################
35
+ ## Configuration Setup
36
+ ############################################################
37
+ env = ENV['SINATRA_ENV'] || "development"
38
+ config = YAML.load_file("#{Dir.pwd}/ass.yml")
39
+ $timer = config['timer'] || 1
40
+ $cron = config['cron'] || 'cron'
41
+ $port = config['port'] || 4567
42
+ $mode = config['mode'] || env
43
+ $certificate = config["#{$mode}"][0]['certificate'] || 'ck.pem'
44
+ ROOTDIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
45
+ VERSION = File.open("#{ROOTDIR}/VERSION", "rb").read
46
+ $apps = config['apps'] || []
47
+
48
+ ############################################################
49
+ ## Certificate Key Setup
50
+ ############################################################
51
+
52
+ unless File.exist?("#{Dir.pwd}/#{$certificate}") then
53
+ puts "1: please provide certificate key pem file under current directory"
54
+ puts "2: edit your ass.yml under current directory"
55
+ puts "3: run spns"
56
+ puts "4: iOS Client: in AppDelegate file, didRegisterForRemoteNotificationsWithDeviceToken method should access url below:"
57
+ $apps.each { |app|
58
+ puts "'#{app}'s registration url: http://serverIP:#{$port}/v1/apps/#{app}/DeviceToken"
59
+ }
60
+ puts "5: Server: cron should access 'curl http://localhost:#{$port}/v1/app/push/#{messages}/#{pid}' to send push message"
61
+ exit
62
+ else
63
+ puts "*"*80
64
+ puts "Simple Push Notification Server is Running (#{VERSION}) ..."
65
+ puts "Mode: #{$mode}"
66
+ puts "Port: #{$port}"
67
+ puts "Certificate File: '#{Dir.pwd}/#{$certificate}'"
68
+ puts "Cron Job: '#{Dir.pwd}/#{$cron}' script is running every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'} " unless "#{$timer}".squeeze.strip == "0"
69
+ puts "*"*80
70
+ end
71
+
72
+ $cert = File.read("#{Dir.pwd}/#{$certificate}")
73
+ $openSSLContext = OpenSSL::SSL::SSLContext.new
74
+ $openSSLContext.cert = OpenSSL::X509::Certificate.new($cert)
75
+ $openSSLContext.key = OpenSSL::PKey::RSA.new($cert)
76
+
77
+ ############################################################
78
+ ## Sequel Database Setup
79
+ ############################################################
80
+
81
+ DB = nil;
82
+
83
+ unless File.exist?("#{Dir.pwd}/push.db") then
84
+ DB = Sequel.connect("sqlite://#{Dir.pwd}/push.db")
85
+
86
+ DB.create_table :tokens do
87
+ primary_key :id
88
+ String :app, :unique => true, :null => false
89
+ String :token, :unique => true, :null => false, :size => 100
90
+ index [:app, :token]
91
+ end
92
+
93
+ DB.create_table :pushes do
94
+ primary_key :id
95
+ String :pid, :unique => true, :null => false, :size => 100
96
+ index :pid
97
+ end
98
+ else
99
+ DB = Sequel.connect("sqlite://#{Dir.pwd}/push.db")
100
+ end
101
+
102
+ Token = DB[:tokens]
103
+ Push = DB[:pushes]
104
+
105
+ ############################################################
106
+ ## Timer Job Setup
107
+ ############################################################
108
+ scheduler = Rufus::Scheduler.start_new
109
+
110
+ unless "#{$timer}".squeeze.strip == "0"
111
+ scheduler.every "#{$timer}m" do
112
+ puts "running job: '#{Dir.pwd}/#{$cron}' every #{$timer} #{($timer == 1) ? 'minute' : 'minutes'}"
113
+ system "./#{$cron}"
114
+ end
115
+ else
116
+ puts
117
+ puts "*"*80
118
+ puts "How to register notification?"
119
+ puts
120
+ puts "iOS Client: in AppDelegate file, didRegisterForRemoteNotificationsWithDeviceToken method should access url below:"
121
+ $apps.each { |app|
122
+ puts "'#{app}'s registration url: http://serverIP:#{$port}/v1/apps/#{app}/DeviceToken"
123
+ }
124
+ puts
125
+ puts "How to send push notification?"
126
+ puts
127
+ $apps.each { |app|
128
+ puts "curl http://localhost:#{$port}/v1/apps/#{app}/push/{message}/{pid}"
129
+ }
130
+ puts
131
+ puts "Note:"
132
+ puts "message: notification message you want to send, remember the message should be html escaped"
133
+ puts "pid: unique id that you mark the message, for example current timestamp"
134
+ puts
135
+ puts "*"*80
136
+ puts
137
+ end
138
+
139
+ ############################################################
140
+ ## Simple Push Notification Server based on Sinatra
141
+ ############################################################
142
+
143
+ class App < Sinatra::Base
144
+
145
+ set :port, "#{$port}".to_i
146
+
147
+ get '/' do
148
+ puts "Simple Push Notification Server"
149
+ end
150
+
151
+ $apps.each { |app|
152
+ get "/v1/apps/#{app}/:token" do
153
+ puts "[#{params[:token]}] was added to '#{app}'"
154
+ o = Token.first(:token => params[:token])
155
+ unless o
156
+ Token.insert(
157
+ :app => app,
158
+ :token => params[:token]
159
+ )
160
+ end
161
+ end
162
+
163
+ get "/v1/apps/#{app}/push/:message/:pid" do
164
+ message = CGI::unescape(params[:message])
165
+ pid = params[:pid]
166
+
167
+ puts "'#{message}' was sent to (#{app}) with pid: [#{pid}]"
168
+
169
+ @push = Token.where(:app => app)
170
+ @exist = Push.first(:pid => pid)
171
+
172
+ unless @exist
173
+ # Connect to port 2195 on the server.
174
+ sock = nil
175
+ if $mode == 'production' then
176
+ sock = TCPSocket.new('gateway.push.apple.com', 2195)
177
+ else
178
+ sock = TCPSocket.new('gateway.sandbox.push.apple.com', 2195)
179
+ end
180
+ # do our SSL handshaking
181
+ sslSocket = OpenSSL::SSL::SSLSocket.new(sock, $openSSLContext)
182
+ sslSocket.connect
183
+ #Push.create( :pid => pid )
184
+ Push.insert(:pid => pid)
185
+ # write our packet to the stream
186
+ @push.each do |o|
187
+ tokenText = o[:token]
188
+ # pack the token to convert the ascii representation back to binary
189
+ tokenData = [tokenText].pack('H*')
190
+ # construct the payload
191
+ payload = "{\"aps\":{\"alert\":\"#{message}\", \"badge\":1}}"
192
+ # construct the packet
193
+ packet = [0, 0, 32, tokenData, 0, payload.length, payload].pack("ccca*cca*")
194
+ # read our certificate and set up our SSL context
195
+ sslSocket.write(packet)
196
+ end
197
+ # cleanup
198
+ sslSocket.close
199
+ sock.close
200
+ end
201
+ end
202
+ }
203
+ end
metadata ADDED
@@ -0,0 +1,331 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ass
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Eiffel Qiu
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sqlite3
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sinatra
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sequel
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rufus-scheduler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: feedzirra
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: eventmachine
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: activesupport
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: 3.2.8
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 3.2.8
126
+ - !ruby/object:Gem::Dependency
127
+ name: shoulda
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rdoc
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: '3.12'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: '3.12'
158
+ - !ruby/object:Gem::Dependency
159
+ name: bundler
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ~>
164
+ - !ruby/object:Gem::Version
165
+ version: 1.2.0
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: 1.2.0
174
+ - !ruby/object:Gem::Dependency
175
+ name: jeweler
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ~>
180
+ - !ruby/object:Gem::Version
181
+ version: 1.8.4
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ~>
188
+ - !ruby/object:Gem::Version
189
+ version: 1.8.4
190
+ - !ruby/object:Gem::Dependency
191
+ name: sinatra
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :runtime
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ - !ruby/object:Gem::Dependency
207
+ name: sequel
208
+ requirement: !ruby/object:Gem::Requirement
209
+ none: false
210
+ requirements:
211
+ - - ! '>='
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ type: :runtime
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ none: false
218
+ requirements:
219
+ - - ! '>='
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ - !ruby/object:Gem::Dependency
223
+ name: rufus-scheduler
224
+ requirement: !ruby/object:Gem::Requirement
225
+ none: false
226
+ requirements:
227
+ - - ! '>='
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ none: false
234
+ requirements:
235
+ - - ! '>='
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ - !ruby/object:Gem::Dependency
239
+ name: feedzirra
240
+ requirement: !ruby/object:Gem::Requirement
241
+ none: false
242
+ requirements:
243
+ - - ! '>='
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ type: :runtime
247
+ prerelease: false
248
+ version_requirements: !ruby/object:Gem::Requirement
249
+ none: false
250
+ requirements:
251
+ - - ! '>='
252
+ - !ruby/object:Gem::Version
253
+ version: '0'
254
+ - !ruby/object:Gem::Dependency
255
+ name: eventmachine
256
+ requirement: !ruby/object:Gem::Requirement
257
+ none: false
258
+ requirements:
259
+ - - ! '>='
260
+ - !ruby/object:Gem::Version
261
+ version: '0'
262
+ type: :runtime
263
+ prerelease: false
264
+ version_requirements: !ruby/object:Gem::Requirement
265
+ none: false
266
+ requirements:
267
+ - - ! '>='
268
+ - !ruby/object:Gem::Version
269
+ version: '0'
270
+ - !ruby/object:Gem::Dependency
271
+ name: activesupport
272
+ requirement: !ruby/object:Gem::Requirement
273
+ none: false
274
+ requirements:
275
+ - - ! '>='
276
+ - !ruby/object:Gem::Version
277
+ version: 3.2.8
278
+ type: :runtime
279
+ prerelease: false
280
+ version_requirements: !ruby/object:Gem::Requirement
281
+ none: false
282
+ requirements:
283
+ - - ! '>='
284
+ - !ruby/object:Gem::Version
285
+ version: 3.2.8
286
+ description: Simple Push Notification Server written with Sinatra and Sequel(Sqlite3)
287
+ email: eiffelqiu@gmail.com
288
+ executables:
289
+ - ass
290
+ extensions: []
291
+ extra_rdoc_files:
292
+ - LICENSE.txt
293
+ - README.md
294
+ files:
295
+ - LICENSE.txt
296
+ - README.md
297
+ - VERSION
298
+ - ass.yml
299
+ - cron
300
+ - lib/ass.rb
301
+ - !binary |-
302
+ YmluL2Fzcw==
303
+ homepage: http://github.com/eiffelqiu/ass
304
+ licenses:
305
+ - MIT
306
+ post_install_message:
307
+ rdoc_options: []
308
+ require_paths:
309
+ - lib
310
+ required_ruby_version: !ruby/object:Gem::Requirement
311
+ none: false
312
+ requirements:
313
+ - - ! '>='
314
+ - !ruby/object:Gem::Version
315
+ version: '0'
316
+ segments:
317
+ - 0
318
+ hash: 2113130320797481794
319
+ required_rubygems_version: !ruby/object:Gem::Requirement
320
+ none: false
321
+ requirements:
322
+ - - ! '>='
323
+ - !ruby/object:Gem::Version
324
+ version: '0'
325
+ requirements: []
326
+ rubyforge_project: ass
327
+ rubygems_version: 1.8.21
328
+ signing_key:
329
+ specification_version: 3
330
+ summary: Simple Push Notification Server
331
+ test_files: []