diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..0d74852 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,10 @@ +version: 2 +jobs: + build: + working_directory: /app + docker: + - image: ruby:2.1.2 + steps: + - checkout + - run: bundle install + - run: rspec diff --git a/.ruby-version b/.ruby-version index 67b8bc0..eca07e4 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-1.9.3 +2.1.2 diff --git a/lib/pushmeup/apns/core.rb b/lib/pushmeup/apns/core.rb index fe9d79a..bd772d0 100644 --- a/lib/pushmeup/apns/core.rb +++ b/lib/pushmeup/apns/core.rb @@ -9,34 +9,34 @@ module APNS # 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 @pass = nil - + @persistent = false @mutex = Mutex.new @retries = 3 # TODO: check if we really need this - + @sock = nil @ssl = nil - + class << self attr_accessor :host, :pem, :port, :pass end - + def self.start_persistence @persistent = true end - + def self.stop_persistence @persistent = false - + @ssl.close @sock.close end - + def self.send_notification(device_token, message) n = APNS::Notification.new(device_token, message) self.send_notifications([n]) end - + def self.send_notifications(notifications) @mutex.synchronize do self.with_connection do @@ -46,8 +46,9 @@ def self.send_notifications(notifications) end end end - + def self.feedback + assert_pem_setup! sock, ssl = self.feedback_connection apns_feedback = [] @@ -63,30 +64,31 @@ def self.feedback return apns_feedback end - + protected - + def self.with_connection + assert_pem_setup! attempts = 1 - - begin + + begin # If no @ssl is created or if @ssl is closed we need to start it if @ssl.nil? || @sock.nil? || @ssl.closed? || @sock.closed? @sock, @ssl = self.open_connection end - + yield - + rescue StandardError, Errno::EPIPE raise unless attempts < @retries - - @ssl.close - @sock.close - + + @ssl.close unless @ssl.nil? + @sock.close unless @ssl.nil? + attempts += 1 retry end - + # Only force close if not persistent unless @persistent @ssl.close @@ -95,11 +97,8 @@ def self.with_connection @sock = nil end end - + 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) - 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) @@ -110,22 +109,23 @@ def self.open_connection 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) - 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) - + fhost = self.host.gsub('gateway','feedback') - + sock = TCPSocket.new(fhost, 2196) ssl = OpenSSL::SSL::SSLSocket.new(sock, context) ssl.connect return sock, ssl end - + + def self.assert_pem_setup! + 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) + end end diff --git a/spec/lib/apns/core_spec.rb b/spec/lib/apns/core_spec.rb new file mode 100644 index 0000000..c329ec1 --- /dev/null +++ b/spec/lib/apns/core_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe APNS do + describe '.send_notifications' do + let(:cert) { 'spec/support/dummy.pem' } + let(:tcp_socket) { instance_double(TCPSocket).as_null_object } + let(:ssl_socket) { instance_double(OpenSSL::SSL::SSLSocket).as_null_object } + + before do + allow(APNS).to receive(:pem).and_return cert + allow(TCPSocket).to receive(:new).with(APNS.host, APNS.port).and_return(tcp_socket) + allow(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, an_instance_of(OpenSSL::SSL::SSLContext)) + .and_return(ssl_socket) + end + + let(:notification) { APNS::Notification.new '123', 'hi' } + + subject { described_class.send_notifications [notification] } + + it { expect { subject }.not_to raise_error } + + context 'when there is connection error' do + let(:error) { StandardError.new } + + before do + allow(ssl_socket).to receive(:connect).and_raise error + end + + it { expect { subject }.to raise_error error } + end + end +end diff --git a/spec/lib/pushmeup_spec.rb b/spec/lib/pushmeup_spec.rb index 80264af..38f9175 100644 --- a/spec/lib/pushmeup_spec.rb +++ b/spec/lib/pushmeup_spec.rb @@ -3,7 +3,7 @@ describe Pushmeup do describe "APNS" do it "should have a APNS object" do - defined?(APNS).should_not be_false + defined?(APNS).should_not be false end it "should not forget the APNS default parameters" do @@ -31,7 +31,7 @@ describe "GCM" do it "should have a GCM object" do - defined?(GCM).should_not be_false + defined?(GCM).should_not be false end describe "Notifications" do @@ -42,27 +42,27 @@ it "should allow only notifications with device_tokens as array" do n = GCM::Notification.new("id", @options) - n.device_tokens.is_a?(Array).should be_true + n.device_tokens.is_a?(Array).should be true n.device_tokens = ["a" "b", "c"] - n.device_tokens.is_a?(Array).should be_true + n.device_tokens.is_a?(Array).should be true n.device_tokens = "a" - n.device_tokens.is_a?(Array).should be_true + n.device_tokens.is_a?(Array).should be true end it "should allow only notifications with data as hash with :data root" do n = GCM::Notification.new("id", { :data => "data" }) - n.data.is_a?(Hash).should be_true + n.data.is_a?(Hash).should be true n.data.should == {:data => "data"} n.data = {:a => ["a", "b", "c"]} - n.data.is_a?(Hash).should be_true + n.data.is_a?(Hash).should be true n.data.should == {:a => ["a", "b", "c"]} n.data = {:a => "a"} - n.data.is_a?(Hash).should be_true + n.data.is_a?(Hash).should be true n.data.should == {:a => "a"} end @@ -78,4 +78,4 @@ end end -end \ No newline at end of file +end diff --git a/spec/support/dummy.pem b/spec/support/dummy.pem new file mode 100644 index 0000000..ab03f19 --- /dev/null +++ b/spec/support/dummy.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIICsDCCAhmgAwIBAgIJALwzrJEIBOaeMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTEwOTMwMTUyNjM2WhcNMjEwOTI3MTUyNjM2WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQC88Ckwru9VR2p2KJ1WQyqesLzr95taNbhkYfsd0j8Tl0MGY5h+dczCaMQz0YY3 +xHXuU5yAQQTZjiks+D3KA3cx+iKDf2p1q77oXxQcx5CkrXBWTaX2oqVtHm3aX23B +AIORGuPk00b4rT3cld7VhcEFmzRNbyI0EqLMAxIwceUKSQIDAQABo4GnMIGkMB0G +A1UdDgQWBBSGmOdvSXKXclic5UOKPW35JLMEEjB1BgNVHSMEbjBsgBSGmOdvSXKX +clic5UOKPW35JLMEEqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt +U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJALwzrJEI +BOaeMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAcPfWn49pgAX54ji5 +SiUPFFNCuQGSSTHh2I+TMrs1G1Mb3a0X1dV5CNLRyXyuVxsqhiM/H2veFnTz2Q4U +wdY/kPxE19Auwcz9AvCkw7ol1LIlLfJvBzjzOjEpZJNtkXTx8ROSooNrDeJl3HyN +cciS5hf80XzIFqwhzaVS9gmiyM8= +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA6Lr0lAvQrywXhMnXpJaTBL6edduZUY20piiT82Vasere343B +wcQFUMwGV9CrjSeOMI9jN/0PEw5ZJ5s4AwqYePxy3K05t6MV5UrDx6na7POfUOmC +sfp+KgURY4jQUtOvbHeXr4CjXiMEOJcTTuRaiqy24tjcwkNsHW4/QRyZ1BdKI+8H +x9HqpdwaVn2rCx36ZVukMUqv6y7m8QkDog02oLrneYQy8oMaVwkIcxNxFJzVqobN +SDGXH2Wdh7usoF8P3PD+EuzCgCeP/kqLym4BYXp6tzJj3hT/85xxpsOjIulLuubK +wJz9hngJ/C14DBjXjUmkN38R7XEkR3jvp0K09wIDAQABAoIBAF0irEwu6kWf/I18 +hRrt00obyqhZyGKVtgykwoiuJauCpSgY0q5rdsEd1RABhxXHFaUjTM6ULBsxK8ao +3GKDM/9+76yWejmeP13ybKUTuXQIDuK/gDkfiKviOVI+5zeuVU6wEXj/nuFGXCMV +enmg8wb6FXp01Ou9NaAVhaTWAE2afOQSZnI52NUNdV8qvic7+CXpq/W5A/eRIR9v +aGaEzglyXPq3N8LUEjqx5abFijCT3fcLwvO2yOs8gxVhwrS0cr30JXnRxqmEghk9 +GsHyJvUyIEz2BFuzJ8sd0xVgej5wxPcdPcmVnGPGYZ/l+tSSw8XaXqmiBhPEElaa +wBET0qECgYEA/aJrYviMbKzS0FuTLNVjEM/Zj60xgVMSZZ9GOqrm4nyIkxFLHmGC +rnfeLiAnT0FlCZlO2vA71vXj8dNbhWSxD8EOCvu/oCcAJc0rmkmOgPIRrQMGcd7I +s3RbT0ZykuX+JW2SlnMWIPPW4QK5MXfqNux42J/6jC/q0iaeGNPBC9ECgYEA6uaf ++YdIHgEYkgn8WjY3i8rX98QmXL9565nFmQe7RmzBZrzFLyKd7FjxjYIz1hk+CfFS +jhMb4WNdeVvV8AVPTiTHomCxdVasfadfadsfdPFCHbkbizenFJf0DNx14qe7eSds +2gQSZ3eTD7XdFNxqwF1dvPXPtDbVBa8SHlijDkcCgYB1tX4e9Xi+Ksq/tfAsu295 +auzuOBOkkDgWf3+pVI1IiUEc98aj998dNzYes/9qUdAhT0wAYcNztLQwE8YCt0NR +K2hoAoPhQJhZ8skMlpyTDUTUxXWlPR5p4lNKDEi6EhELr7l7Jzga3O9Zh9kIsz04 +djBzYHN3wfk5xIBUx1ltMQKBgBMA8XRIg4cZ45j9AdNyi2/dyzcaQVhDjWOIHzpQ +K9B4v/TF1NYJYOlcEL64B+WMST6YrWsdFKZZWZiV22r9ovrZcuUqGXE7PMgpXGcE +jZZZTlYFQbszl2rNGEtqEodxtnMIw3+n0K1aOSWOOwKTCnfhldHRuSoFPZqmHTsj +RJ3FAoGAXpx+ulBu99kOxOWWofmP7ff9DF3doF+Jmjwo6KdOAIq++74hNRgYCIKM +X+4TSnmu8qBOt++r+EaBgxb+m+36iQxJRhU25ggzlA97v7n7KSxdyt7OLCe5zXP5 +iiMWAO+qJD6ZyHlzVz0wwu3nAvkhjTxQ2c3+7xjUwCZK1lXjvvM= +-----END RSA PRIVATE KEY-----