unipush 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. data/lib/unipush.rb +211 -0
  2. metadata +45 -0
data/lib/unipush.rb ADDED
@@ -0,0 +1,211 @@
1
+ class Unipush
2
+ attr_accessor :ios_cert_path
3
+
4
+
5
+ def initialize(mode='production')
6
+ if mode == 'production'
7
+ @ios_push_url = 'gateway.push.apple.com'
8
+ @ios_feedback_url = 'feedback.push.apple.com'
9
+ else
10
+ @ios_push_url = 'sandbox.gateway.push.apple.com'
11
+ @ios_feedback_url = 'sandbox.feedback.push.apple.com'
12
+ end
13
+ @ios_push_port = '2195'
14
+ @ios_feedback_port = '2196'
15
+ @ios_cert_path = ''
16
+
17
+
18
+ @android_auth_url = 'https://www.google.com/accounts/ClientLogin'
19
+ @android_push_url = 'https://android.apis.google.com/c2dm/send'
20
+
21
+ @last_error = []
22
+ end
23
+
24
+ def get_last_error
25
+ @last_error.empty? ? false : @last_error
26
+ end
27
+
28
+ def get_unsent_messages
29
+ @unsent_messages.nil? ? false : @unsent_messages
30
+ end
31
+
32
+ #message={:text=>"", :badge=>0, :newsstand=>true, :track=>true, :add=>{:param1=>1, :param2=>2}}
33
+ def prepare_ios_message(token, message)
34
+ json = ""
35
+ if message[:newsstand]
36
+ json = '{"aps":{"content-available":1}}'
37
+ token = [token].pack('H*')
38
+ elsif message[:text] && message[:text] != ''
39
+ badge = message[:badge].nil? ? "0" : message[:badge].to_s
40
+ sound = message[:sound].nil? ? "default" : message[:sound].to_s
41
+ add_str = ""
42
+
43
+ unless message[:add].nil?
44
+ add_fields = []
45
+ message[:add].each do |k, m|
46
+ add_fields.push('"'+k.to_s+'":"'+m.gsub(/['"\\\x0]/,'\\\\\0')+'"')
47
+ end
48
+ add_str = ","+add_fields.join(",")
49
+ end
50
+ json = '{"aps":{"alert":"'+message[:text].gsub(/['"\\\x0]/,'\\\\\0')+'","badge":"'+badge+'","sound":"'+sound+'"}'+add_str+'}'
51
+ token = [token].pack('H*')
52
+ end
53
+
54
+ if message[:track]
55
+ tt = Time.now + 1.day
56
+ mes = [1, Random.rand, tt.to_i, 0, 32, token, 0, json.bytesize, json].pack("cNNcca*cca*")
57
+ else
58
+ mes = [0, 0, 32, token, 0, json.bytesize, json].pack("ccca*cca*")
59
+ end
60
+
61
+ mes.nil? ? false : mes
62
+ end
63
+
64
+ # messages = [message2, message2, ...]
65
+ # message=[token, message={:text=>"", :badge=>0, :newsstand=>true, :track=>true, :add=>{:param1=>1, :param2=>2}}]
66
+ def send_ios_messages(messages)
67
+ cert_path = @ios_cert_path
68
+ if FileTest.exist?(cert_path)
69
+ begin
70
+ certificate = File.read(cert_path)
71
+ context = OpenSSL::SSL::SSLContext.new
72
+ context.key = OpenSSL::PKey::RSA.new(certificate)
73
+ context.cert = OpenSSL::X509::Certificate.new(certificate)
74
+ socket = TCPSocket.new(@ios_push_url, @ios_push_port)
75
+ ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
76
+ ssl.sync = true
77
+ ssl.connect
78
+ messages.each_with_index do |m, k|
79
+ mes = prepare_ios_message(m[0], m[1])
80
+ if mes
81
+ ssl.write(mes)
82
+ if ssl.pending >0 && m[1][:track]
83
+ tmp = ssl.readline
84
+ reply = tmp.unpack("CCN")
85
+ @unsent_messages.push(k)
86
+ @last_error.push("Could not send message #{k} width error: "+reply.join(", "))
87
+ end
88
+ else
89
+ @unsent_messages = [] if @unsent_messages.nil?
90
+ @unsent_messages.push(k)
91
+ end
92
+ end
93
+ ssl.close
94
+ socket.close
95
+ rescue
96
+ @last_error.push("Could not send messages. Exception: #{$!.inspect}")
97
+ false
98
+ else
99
+ true
100
+ end
101
+ else
102
+ @last_error.push("Certificate file does not exist")
103
+ false
104
+ end
105
+ end
106
+
107
+ def get_unregistered_tokens
108
+ cert_path = @ios_cert_path
109
+ if FileTest.exist?(cert_path)
110
+ begin
111
+ certificate = File.read(cert_path)
112
+ context = OpenSSL::SSL::SSLContext.new
113
+ context.key = OpenSSL::PKey::RSA.new(certificate)
114
+ context.cert = OpenSSL::X509::Certificate.new(certificate)
115
+ # получим удаленные токены
116
+ sock = TCPSocket.new("feedback.push.apple.com", 2196)
117
+ ssl = OpenSSL::SSL::SSLSocket.new(sock,context)
118
+ ssl.connect
119
+ apns_feedback = []
120
+ while line = ssl.read(38)
121
+ line.strip!
122
+ f = line.unpack("NnH*")
123
+ apns_feedback << [Time.at(f[0]), f[2]]
124
+ end
125
+ ssl.close
126
+ sock.close
127
+ # и вернем их для удаления
128
+ ret = []
129
+ unless apns_feedback.empty?
130
+ apns_feedback.each do |ff|
131
+ ret.push(ff[1])
132
+ end
133
+ end
134
+ ret
135
+ rescue
136
+ @last_error.push("Could not get tokens. Exception: #{$!.inspect}")
137
+ false
138
+ end
139
+ else
140
+ @last_error.push("Certificate file does not exist")
141
+ false
142
+ end
143
+ end
144
+
145
+
146
+ def self.send_android_messages(app_code, messages)
147
+ # Данные для доступа к точке отправки
148
+ email = 'finansmag.app@gmail.com'
149
+ password = 'developer123'
150
+ data = "accountType=HOSTED_OR_GOOGLE&Email=#{email}&Passwd=#{password}&service=ac2dm"
151
+ headers = { "Content-type" => "application/x-www-form-urlencoded",
152
+ "Content-length" => "#{data.length}"}
153
+
154
+
155
+ # Получаем токен
156
+ uri = URI.parse(AUTH_URL)
157
+ http = Net::HTTP.new(uri.host, uri.port)
158
+ http.use_ssl = true
159
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
160
+ response = http.post(uri.path, data, headers)
161
+ @auth_token = response.body.split("\n")[2].gsub("Auth=", "")
162
+
163
+ unless @auth_token.empty?
164
+ err_mes = []
165
+ messages.each do |m|
166
+ data = ['registration_id'=> m[2],
167
+ 'collapse_key' => 'c2dm',
168
+ 'data.message' => m[1]
169
+ ]
170
+ data = data.map{|k, v| "&#{k}=#{URI.escape(v.to_s)}"}.reduce{|k, v| k + v}
171
+ headers = { "Authorization" => "GoogleLogin auth=#{@auth_token}",
172
+ "Content-type" => "application/x-www-form-urlencoded",
173
+ "Content-length" => "#{data.length}" }
174
+ uri = URI.parse(PUSH_URL)
175
+ http = Net::HTTP.new(uri.host, uri.port)
176
+ http.use_ssl = true
177
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
178
+
179
+ result = http.post(uri.path, data, headers)
180
+
181
+ if result.code == 200
182
+ if result.body.split("\n")[1][0, 5].downcase == "error"
183
+ error = result.body.split("\n")[1].gsub("Error=", "")
184
+ case error
185
+ when 'NotRegistered' #Пользователь удалил приложение
186
+ Token.where(:token => m[2]).destroy
187
+ PushMessage.find(m[0]).update_attribute(:error, (t 'errors.android.app_deleted'))
188
+ when 'MessageTooBig' # слишком длинный текст сообщения
189
+ PushMessage.find(m[0]).update_attribute(:error, (t 'errors.android.messagge_too_large'))
190
+ when 'QuotaExceeded', 'DeviceQuotaExceeded' # слишком много сообщений, надо подождать
191
+ sleep 10
192
+ redo #Попробуем еще раз отправить
193
+ else
194
+ PushMessage.find(m[0]).update_attribute(:error, (t 'errors.android.common_error' + error))
195
+ end
196
+ err_mes.push(m[0])
197
+ end
198
+ else
199
+ PushMessage.find(m[0]).update_attribute(:error, 'Error. Could not connect to c2dm server')
200
+ return ['error', "Error. Could not connect to c2dm server"]
201
+ end
202
+ end
203
+ if err_mes.size > 0
204
+ return ['error', "Error sending #{err_mes.size} messages"]
205
+ end
206
+ else
207
+ return ['error', "Authorization error. Could not receive token"]
208
+ end
209
+ end
210
+
211
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unipush
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alexey Kirov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A simple push notification sender for iOS and Android
15
+ email: alexey.s.kirov@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/unipush.rb
21
+ homepage: http://rubygems.org/gems/unipush
22
+ licenses: []
23
+ post_install_message:
24
+ rdoc_options: []
25
+ require_paths:
26
+ - lib
27
+ required_ruby_version: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 1.8.11
42
+ signing_key:
43
+ specification_version: 3
44
+ summary: Unipush
45
+ test_files: []