From e64f8d73404f726d8bc9a15b5b1f11da424cb09f Mon Sep 17 00:00:00 2001 From: Mengxi Liao Date: Wed, 19 Jul 2017 10:30:32 -0700 Subject: [PATCH 1/4] add support to multiple apps --- Gemfile.lock | 29 ++++++++++----- apns.gemspec | 3 +- lib/apns/core.rb | 67 ++++++++++++++++++++++------------ spec/apns/core_spec.rb | 46 +++++++++++++++++++++++ spec/apns/notification_spec.rb | 4 +- spec/spec_helper.rb | 1 + 6 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 spec/apns/core_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 6752759..d939bc4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,19 +6,28 @@ PATH GEM remote: http://rubygems.org/ specs: - diff-lcs (1.1.3) - rspec (2.10.0) - rspec-core (~> 2.10.0) - rspec-expectations (~> 2.10.0) - rspec-mocks (~> 2.10.0) - rspec-core (2.10.1) - rspec-expectations (2.10.0) - diff-lcs (~> 1.1.3) - rspec-mocks (2.10.1) + diff-lcs (1.3) + rspec (3.4.0) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-core (3.4.3) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) PLATFORMS ruby DEPENDENCIES apns! - rspec + rspec (~> 3.4.0) + rspec-mocks (~> 3.4.0) + +BUNDLED WITH + 1.13.6 diff --git a/apns.gemspec b/apns.gemspec index ec17887..579051c 100644 --- a/apns.gemspec +++ b/apns.gemspec @@ -21,6 +21,7 @@ DESC s.rubygems_version = %q{1.3.5} s.summary = %q{Simple Apple push notification service gem} - s.add_development_dependency 'rspec' + s.add_development_dependency 'rspec', '~>3.4.0' + s.add_development_dependency 'rspec-mocks', '~>3.4.0' end diff --git a/lib/apns/core.rb b/lib/apns/core.rb index c371d4e..a42e181 100644 --- a/lib/apns/core.rb +++ b/lib/apns/core.rb @@ -6,20 +6,24 @@ module APNS @host = 'gateway.sandbox.push.apple.com' @port = 2195 # openssl pkcs12 -in mycert.p12 -out client-cert.pem -nodes -clcerts - @pem = nil # this should be the path of the pem file not the contentes + @pems = {} + # this should be the path of the pem file not the contentes + # Example: + # { some_ios_app : "file/to/pem" } + @pass = nil class << self - attr_accessor :host, :pem, :port, :pass + attr_accessor :host, :pems, :port, :pass end - def self.send_notification(device_token, message) + def self.send_notification(device_token, message, app) n = APNS::Notification.new(device_token, message) - self.send_notifications([n]) + self.send_notifications([n], app) end - def self.send_notifications(notifications) - sock, ssl = self.open_connection + def self.send_notifications(notifications, app) + sock, ssl = self.open_connection(app) packed_nofications = self.packed_nofications(notifications) @@ -45,8 +49,8 @@ def self.packed_nofications(notifications) bytes end - def self.feedback - sock, ssl = self.feedback_connection + def self.feedback(app) + sock, ssl = self.feedback_connection(app) apns_feedback = [] @@ -63,36 +67,51 @@ def self.feedback protected - def self.open_connection - raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless self.pem - raise "The path to your pem file does not exist!" unless File.exist?(self.pem) + def self.open_connection(app) + pem = self.pems[app] + raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless pem + raise "The path to your pem file does not exist!" unless File.exist?(pem) - context = OpenSSL::SSL::SSLContext.new - context.cert = OpenSSL::X509::Certificate.new(File.read(self.pem)) - context.key = OpenSSL::PKey::RSA.new(File.read(self.pem), self.pass) + context = build_context(pem) - sock = TCPSocket.new(self.host, self.port) - ssl = OpenSSL::SSL::SSLSocket.new(sock,context) + sock = build_socket(self.host, self.port) + ssl = build_ssl(context, sock) ssl.connect return sock, ssl end - def self.feedback_connection - raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless self.pem - raise "The path to your pem file does not exist!" unless File.exist?(self.pem) + def self.feedback_connection(app) + pem = self.pems[app] + raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless pem + raise "The path to your pem file does not exist!" unless File.exist?(pem) - context = OpenSSL::SSL::SSLContext.new - context.cert = OpenSSL::X509::Certificate.new(File.read(self.pem)) - context.key = OpenSSL::PKey::RSA.new(File.read(self.pem), self.pass) + context = build_context(pem) fhost = self.host.gsub('gateway','feedback') puts fhost - sock = TCPSocket.new(fhost, 2196) - ssl = OpenSSL::SSL::SSLSocket.new(sock,context) + sock = build_socket(fhost, 2196) + ssl = build_ssl(context, sock) ssl.connect return sock, ssl end + + def self.build_context(pem) + context = OpenSSL::SSL::SSLContext.new + pem_content = File.read(pem) + context.cert = OpenSSL::X509::Certificate.new(pem_content) + context.key = OpenSSL::PKey::RSA.new(pem_content, self.pass) + context + end + + def self.build_socket(host, port) + TCPSocket.new(host, port) + end + + def self.build_ssl(context, socket) + ssl = OpenSSL::SSL::SSLSocket.new(socket,context) + return ssl + end end diff --git a/spec/apns/core_spec.rb b/spec/apns/core_spec.rb new file mode 100644 index 0000000..1bb4b45 --- /dev/null +++ b/spec/apns/core_spec.rb @@ -0,0 +1,46 @@ +require File.dirname(__FILE__) + '/../spec_helper' + +describe APNS do + before do + ::APNS.pems = { app1: 'cert1'} + ::APNS.host = "gateway.abc.com" + ::APNS.port = 1234 + allow(File).to receive(:exist?) { true } + end + + describe ".send_notification" do + it "accepts an app key" do + context = OpenStruct.new + socket = OpenStruct.new + ssl = OpenStruct.new + + expect(socket).to receive(:close) + expect(ssl).to receive(:close) + expect(ssl).to receive(:write) + + expect(APNS).to receive(:build_context).with('cert1') { context } + expect(APNS).to receive(:build_socket).with(::APNS.host, ::APNS.port) { socket } + expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } + + ::APNS.send_notification("device_token1", "message1", :app1) + end + end + + describe ".feedback" do + it "accepts an app key" do + context = OpenStruct.new + socket = OpenStruct.new + ssl = OpenStruct.new + + expect(socket).to receive(:close) + expect(ssl).to receive(:read).with(38) + expect(ssl).to receive(:close) + + expect(APNS).to receive(:build_context).with('cert1') { context } + expect(APNS).to receive(:build_socket).with("feedback.abc.com", 2196) { socket } + expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } + + ::APNS.feedback(:app1) + end + end +end diff --git a/spec/apns/notification_spec.rb b/spec/apns/notification_spec.rb index b9ab67e..d7c6b90 100644 --- a/spec/apns/notification_spec.rb +++ b/spec/apns/notification_spec.rb @@ -15,7 +15,7 @@ it "should have a priority if content_availible is set" do n = APNS::Notification.new('device_token', {:content_available => true}) - n.content_available.should be_true + n.content_available.should be_truthy n.priority.should eql(5) end @@ -48,7 +48,7 @@ describe '#packaged_notification' do it "should package the token" do n = APNS::Notification.new('device_token', {:alert => 'Hello iPhone', :badge => 3, :sound => 'awesome.caf'}) - n.stub!(:message_identifier).and_return('aaaa') # make sure the message_identifier is not random + allow(n).to receive(:message_identifier).and_return('aaaa') # make sure the message_identifier is not random Base64.encode64(n.packaged_notification).should == "AQAG3vLO/YTnAgBAeyJhcHMiOnsiYWxlcnQiOiJIZWxsbyBpUGhvbmUiLCJi\nYWRnZSI6Mywic291bmQiOiJhd2Vzb21lLmNhZiJ9fQMABGFhYWEEAAQAAAAA\nBQABCg==\n" end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9edde4a..33d8c2e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'rubygems' gem 'rspec', '>= 1.2.8' require 'rspec' +require 'rspec/mocks' require File.join(File.dirname(__FILE__), '..', 'lib', 'apns') require 'base64' From 2cd1f53e2aac0d45d7b2c07b1f78846ca9c7bfb8 Mon Sep 17 00:00:00 2001 From: Mengxi Liao Date: Wed, 19 Jul 2017 11:49:43 -0700 Subject: [PATCH 2/4] modify readme and order of params --- README.textile | 10 +++++----- lib/apns/core.rb | 9 ++++----- spec/apns/core_spec.rb | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/README.textile b/README.textile index b7f5d7f..a9be520 100644 --- a/README.textile +++ b/README.textile @@ -25,7 +25,7 @@ After you have your .pem file. Set what host, port, certificate file location on APNS.host = 'gateway.push.apple.com' # gateway.sandbox.push.apple.com is default - APNS.pem = '/path/to/pem/file' + APNS.pems = { app1: '/path/to/pem/file/of/app1', app2: '/path/to/pem/file/of/app2' } # this is the file you just created APNS.port = 2195 @@ -41,9 +41,9 @@ Then to send a push notification you can either just send a string as the alert device_token = '123abc456def' - APNS.send_notification(device_token, 'Hello iPhone!' ) + APNS.send_notification(device_token, :app1, 'Hello iPhone!') - APNS.send_notification(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default') + APNS.send_notification(device_token, :app2, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default') @@ -59,7 +59,7 @@ You can also send multiple notifications using the same connection to Apple: n2 = APNS::Notification.new(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default') - APNS.send_notifications([n1, n2]) + APNS.send_notifications(:app1, [n1, n2]) @@ -70,7 +70,7 @@ You can send other application specific information as well.
   
-    APNS.send_notification(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default',
+    APNS.send_notification(device_token, :app1, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default',
                                          :other => {:sent => 'with apns gem'})
   
 
diff --git a/lib/apns/core.rb b/lib/apns/core.rb index a42e181..b7f917b 100644 --- a/lib/apns/core.rb +++ b/lib/apns/core.rb @@ -17,12 +17,12 @@ class << self attr_accessor :host, :pems, :port, :pass end - def self.send_notification(device_token, message, app) + def self.send_notification(device_token, app, message) n = APNS::Notification.new(device_token, message) - self.send_notifications([n], app) + self.send_notifications(app, [n]) end - def self.send_notifications(notifications, app) + def self.send_notifications(app, notifications) sock, ssl = self.open_connection(app) packed_nofications = self.packed_nofications(notifications) @@ -111,7 +111,6 @@ def self.build_socket(host, port) end def self.build_ssl(context, socket) - ssl = OpenSSL::SSL::SSLSocket.new(socket,context) - return ssl + OpenSSL::SSL::SSLSocket.new(socket,context) end end diff --git a/spec/apns/core_spec.rb b/spec/apns/core_spec.rb index 1bb4b45..1daa0c2 100644 --- a/spec/apns/core_spec.rb +++ b/spec/apns/core_spec.rb @@ -22,7 +22,7 @@ expect(APNS).to receive(:build_socket).with(::APNS.host, ::APNS.port) { socket } expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } - ::APNS.send_notification("device_token1", "message1", :app1) + ::APNS.send_notification("device_token1", :app1, "message1") end end From 66b101024bbb6ef70a0905e217a191dae8a45856 Mon Sep 17 00:00:00 2001 From: Mengxi Liao Date: Wed, 19 Jul 2017 16:52:22 -0700 Subject: [PATCH 3/4] make it backwards compatible --- lib/apns/core.rb | 14 +++++++++----- spec/apns/core_spec.rb | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/apns/core.rb b/lib/apns/core.rb index b7f917b..ecda91d 100644 --- a/lib/apns/core.rb +++ b/lib/apns/core.rb @@ -17,12 +17,16 @@ class << self attr_accessor :host, :pems, :port, :pass end - def self.send_notification(device_token, app, message) + def self.pem=(file_path) + @pems = { default: file_path } + end + + def self.send_notification(device_token, app = :default, message) n = APNS::Notification.new(device_token, message) self.send_notifications(app, [n]) end - def self.send_notifications(app, notifications) + def self.send_notifications(app = :default, notifications) sock, ssl = self.open_connection(app) packed_nofications = self.packed_nofications(notifications) @@ -49,7 +53,7 @@ def self.packed_nofications(notifications) bytes end - def self.feedback(app) + def self.feedback(app = :default) sock, ssl = self.feedback_connection(app) apns_feedback = [] @@ -67,7 +71,7 @@ def self.feedback(app) protected - def self.open_connection(app) + def self.open_connection(app = :default) pem = self.pems[app] raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless pem raise "The path to your pem file does not exist!" unless File.exist?(pem) @@ -81,7 +85,7 @@ def self.open_connection(app) return sock, ssl end - def self.feedback_connection(app) + def self.feedback_connection(app = :default) pem = self.pems[app] raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless pem raise "The path to your pem file does not exist!" unless File.exist?(pem) diff --git a/spec/apns/core_spec.rb b/spec/apns/core_spec.rb index 1daa0c2..43b67a2 100644 --- a/spec/apns/core_spec.rb +++ b/spec/apns/core_spec.rb @@ -2,14 +2,33 @@ describe APNS do before do - ::APNS.pems = { app1: 'cert1'} ::APNS.host = "gateway.abc.com" ::APNS.port = 1234 allow(File).to receive(:exist?) { true } end describe ".send_notification" do + it "accepts default app and pem" do + ::APNS.pem = 'default_pem' + + context = OpenStruct.new + socket = OpenStruct.new + ssl = OpenStruct.new + + expect(socket).to receive(:close) + expect(ssl).to receive(:close) + expect(ssl).to receive(:write) + + expect(APNS).to receive(:build_context).with('default_pem') { context } + expect(APNS).to receive(:build_socket).with(::APNS.host, ::APNS.port) { socket } + expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } + + ::APNS.send_notification("device_token1", message: "message", otherstuff: "others") + end + it "accepts an app key" do + ::APNS.pems = { app1: 'cert1'} + context = OpenStruct.new socket = OpenStruct.new ssl = OpenStruct.new @@ -27,7 +46,27 @@ end describe ".feedback" do + it "accept default app and pem" do + ::APNS.pem = 'default_pem' + + context = OpenStruct.new + socket = OpenStruct.new + ssl = OpenStruct.new + + expect(socket).to receive(:close) + expect(ssl).to receive(:read).with(38) + expect(ssl).to receive(:close) + + expect(APNS).to receive(:build_context).with('default_pem') { context } + expect(APNS).to receive(:build_socket).with("feedback.abc.com", 2196) { socket } + expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } + + ::APNS.feedback + end + it "accepts an app key" do + ::APNS.pems = { app1: 'cert1'} + context = OpenStruct.new socket = OpenStruct.new ssl = OpenStruct.new From 5edda6e07b3065b794d317776c94eff7b45c0a83 Mon Sep 17 00:00:00 2001 From: Mengxi Liao Date: Thu, 20 Jul 2017 10:01:59 -0700 Subject: [PATCH 4/4] change method signature to make it backwards compatible without using the weird beginning default param feature --- README.textile | 21 ++++++++++++++----- lib/apns/core.rb | 24 +++++++++++++++++---- spec/apns/core_spec.rb | 47 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/README.textile b/README.textile index a9be520..e7a3900 100644 --- a/README.textile +++ b/README.textile @@ -25,9 +25,14 @@ After you have your .pem file. Set what host, port, certificate file location on APNS.host = 'gateway.push.apple.com' # gateway.sandbox.push.apple.com is default + APNS.pem = '/path/to/pem/file' + # these the file you just created + + # or if you have more than one app APNS.pems = { app1: '/path/to/pem/file/of/app1', app2: '/path/to/pem/file/of/app2' } - # this is the file you just created - + # actually it's identical to + APNS.pems = { default: '/path/to/pem/file' } + APNS.port = 2195 # this is also the default. Shouldn't ever have to set this, but just in case Apple goes crazy, you can. @@ -41,9 +46,12 @@ Then to send a push notification you can either just send a string as the alert device_token = '123abc456def' - APNS.send_notification(device_token, :app1, 'Hello iPhone!') + APNS.send_notification(:app1, device_token, 'Hello iPhone!') - APNS.send_notification(device_token, :app2, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default') + APNS.send_notification(:app2, device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default') + + # if you only have one app and uses APNS.pem = '/path/to/pem/file' to setup the pem, just do + APNS.send_notification(device_token, 'Hello iPhone!') @@ -60,6 +68,9 @@ You can also send multiple notifications using the same connection to Apple: n2 = APNS::Notification.new(device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default') APNS.send_notifications(:app1, [n1, n2]) + + # if you only have one app and uses APNS.pem = '/path/to/pem/file' to setup the pem, just do + APNS.send_notifications([n1, n2]) @@ -70,7 +81,7 @@ You can send other application specific information as well.
   
-    APNS.send_notification(device_token, :app1, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default',
+    APNS.send_notification(:app1, device_token, :alert => 'Hello iPhone!', :badge => 1, :sound => 'default',
                                          :other => {:sent => 'with apns gem'})
   
 
diff --git a/lib/apns/core.rb b/lib/apns/core.rb index ecda91d..17fe692 100644 --- a/lib/apns/core.rb +++ b/lib/apns/core.rb @@ -17,16 +17,32 @@ class << self attr_accessor :host, :pems, :port, :pass end - def self.pem=(file_path) - @pems = { default: file_path } + def self.pem=(default_pem_path) + @pems = { default: default_pem_path } end - def self.send_notification(device_token, app = :default, message) + def self.send_notification(app_or_device_token, device_token_or_message, message = nil) + app, device_token, message = begin + if message.nil? + [:default, app_or_device_token, device_token_or_message] + else + [app_or_device_token, device_token_or_message, message] + end + end + n = APNS::Notification.new(device_token, message) self.send_notifications(app, [n]) end - def self.send_notifications(app = :default, notifications) + def self.send_notifications(app_or_notifications, notifications = nil) + app, notifications = begin + if notifications.nil? + [:default, app_or_notifications] + else + [app_or_notifications, notifications] + end + end + sock, ssl = self.open_connection(app) packed_nofications = self.packed_nofications(notifications) diff --git a/spec/apns/core_spec.rb b/spec/apns/core_spec.rb index 43b67a2..ce469c3 100644 --- a/spec/apns/core_spec.rb +++ b/spec/apns/core_spec.rb @@ -7,6 +7,51 @@ allow(File).to receive(:exist?) { true } end + describe ".send_notifications" do + it "accepts default app and pem" do + ::APNS.pem = 'default_pem' + + n1 = APNS::Notification.new("token1", "message1") + n2 = APNS::Notification.new("token1", "message2") + + context = OpenStruct.new + socket = OpenStruct.new + ssl = OpenStruct.new + + expect(socket).to receive(:close) + expect(ssl).to receive(:close) + expect(ssl).to receive(:write).twice + + expect(APNS).to receive(:build_context).with('default_pem') { context } + expect(APNS).to receive(:build_socket).with(::APNS.host, ::APNS.port) { socket } + expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } + + ::APNS.send_notifications([n1, n2]) + end + + + it "accepts an app key" do + ::APNS.pems = { app1: 'cert1'} + + n1 = APNS::Notification.new("token1", "message1") + n2 = APNS::Notification.new("token1", "message2") + + context = OpenStruct.new + socket = OpenStruct.new + ssl = OpenStruct.new + + expect(socket).to receive(:close) + expect(ssl).to receive(:close) + expect(ssl).to receive(:write).twice + + expect(APNS).to receive(:build_context).with('cert1') { context } + expect(APNS).to receive(:build_socket).with(::APNS.host, ::APNS.port) { socket } + expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } + + ::APNS.send_notifications(:app1, [n1, n2]) + end + end + describe ".send_notification" do it "accepts default app and pem" do ::APNS.pem = 'default_pem' @@ -41,7 +86,7 @@ expect(APNS).to receive(:build_socket).with(::APNS.host, ::APNS.port) { socket } expect(APNS).to receive(:build_ssl).with(context, socket) { ssl } - ::APNS.send_notification("device_token1", :app1, "message1") + ::APNS.send_notification(:app1, "device_token1", "message1") end end