apns_kit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d2a8a7b77cfad1dc15ed5ecd77a1f94c3e573d26
4
+ data.tar.gz: e1f79c125380960ff087382fcdf258b3752bac36
5
+ SHA512:
6
+ metadata.gz: 7e4efdda5e415dc9921b75ddd4133debb854c7fae033a2e796473894cee9652b195c608a80429e6cdcb92cfc201d1a601b0adf31b475c514e682116cd412ef4f
7
+ data.tar.gz: 7a8c8b7fad2b2cebfbbe6ea9e296bee7e39732d71ac81581887b24f37cb399c76b30f32014a78647c6c9f363ef6039074f4f71bab89a18dc67659ad114bf1903
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in apns_kit.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Chris Recalis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # ApnsKit
2
+
3
+ **NOTE!** this gem is currently under development and no tests have been written yet.
4
+
5
+ A simple to use gem that interfaces with Apple's new HTTP/2 APNs Service
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'apns_kit', '~> 0.1.0'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install apns_kit
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ require 'apns_kit'
26
+
27
+ certificate = ApnsKit::Certificate.new(File.read("path_to_certificate.pem"), "password_or_nil")
28
+
29
+ # create a production client (you can also call ApnsKit::Client.development with the same options)
30
+ # pool_size is the number of open connections defaults to 1 (advisable to keep the default value)
31
+ # heartbeat_interval sends a ping to APNs servers to check if the connection is still alive defaults to 60 seconds
32
+ client = ApnsKit::Client.production(certificate, pool_size: 1, heartbeat_interval: 30)
33
+
34
+ # Build the notification
35
+ notification = ApnsKit::Notification.new (
36
+ token: "a1ee474316e40f6cfb028c6c508dd0c4e49a2855e55765586789896d0fd03e22",
37
+ alert: "Hello!",
38
+ badge: 1,
39
+ sound: "mysound.caf",
40
+ content_available: true,
41
+ data: { event_id: 1 } # data can be named to anything. Supports multiple custom keys as well
42
+ )
43
+ ```
44
+ ### Blocking send
45
+ This will block the calling thread until all notifications have been sent and we get a response for all
46
+ ```ruby
47
+ # Can send an individual notifications or an array of them
48
+ responses = client.send(notification)
49
+ # [#<ApnsKit::Response:0x007fc0bc065520 200 (Success) notification=#<ApnsKit::Notification:0x007fc0bc0b68d0>>]
50
+ ```
51
+ ### Non Blocking send
52
+ This will not block the calling thread but instead use a callback for individual responses
53
+ ```ruby
54
+ client.send_async(notification) do |response|
55
+ if response.success?
56
+ puts "Awesome!"
57
+ else
58
+ puts "Failed: #{response.message} reason: #{response.reason}
59
+ end
60
+ end
61
+ ```
62
+
63
+ ### Fire and forget
64
+ You can also skip passing the block
65
+ ```ruby
66
+ client.send_async(notification)
67
+ ```
68
+
69
+ ### Client considerations
70
+ If you do not provide a topic for a notification the client will use the app bundle id in your certificate as the topic.
71
+
72
+ Do not setup and forget about clients. If you are using short term connections you need to call `client.shutdown` to terminate the connection and the threads that it creates. If however you are using the client as a long running connection you can leave them open. If for some reason the connection is dropped the client will reinitiate the connection on your behalf.
73
+
74
+ ## Logger
75
+ ApnsKit will use the Rails logger if its present. If not it creates its own logger to `STDOUT`. You can change and modify the logger however you like
76
+ ```ruby
77
+ new_logger = Logger.new("some_path.log")
78
+ ApnsKit.logger = new_logger
79
+ ```
80
+
81
+ # Classes
82
+ ### ApnsKit::Response
83
+ ```ruby
84
+ # response = <ApnsKit::Response:0x007fc0bc065520 200 (Success) notification=#<ApnsKit::Notification:0x007fc0bc0b68d0>>
85
+ response.id # returns the id of the notification
86
+ response.status # returns the http status
87
+ response.message # converts the status to a meaningful message
88
+ response.success? # convenience method checking if the status was 200
89
+ response.body # the json body of the response
90
+ response.failure_reason # convenience method to pull out the failure reason from the body
91
+ response.invalid_token? # returns true if the token was invalid
92
+ response.unregistered? # returns true if the token wasn't registered
93
+ response.bad_device_token? # returns true if the token wasn't properly formatted
94
+ response.device_token_not_for_topic? # The device token does not match the specified topic
95
+ response.notification # the ApnsKit::Notification for this response
96
+ ```
97
+
98
+ ### ApnsKit::Notification
99
+ ```ruby
100
+ notification = ApnsKit::Notification.new (
101
+ token: "a1ee474316e40f6cfb028c6c508dd0c4e49a2855e55765586789896d0fd03e22",
102
+ alert: "Hello!",
103
+ badge: 1,
104
+ sound: "",
105
+ category: "",
106
+ expiry: 1460992609 # A UNIX epoch date expressed in seconds (UTC),
107
+ priority: 5,
108
+ content_available: true,
109
+ data: { event_id: 1 } # data can be named to anything. Supports multiple custom keys as well
110
+ )
111
+ ```
112
+ ### ApnsKit::Certificate
113
+ ```ruby
114
+ certificate = ApnsKit::Certificate.new(File.read("path_to_certificate.pem"), "password_or_nil")
115
+
116
+ certificate.production? # returns true if the certificate can be used to connect to APNs production environment
117
+ certificate.development? # returns true if the certificate can be used to connect to APNs development environment
118
+ certificate.universal? # returns true if the certificate can be used to connect to APNs production and development environment
119
+ certificate.app_bundle_id # the app bundle id this certificate was issued for
120
+ ```
121
+ ## Development
122
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
123
+
124
+ ## License
125
+
126
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/apns_kit.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'apns_kit/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "apns_kit"
8
+ spec.version = ApnsKit::VERSION
9
+ spec.authors = ["Chris Recalis"]
10
+ spec.email = ["chris@rover.io"]
11
+
12
+ spec.summary = %q{Send push notifications using Apple's new http/2 APNs service}
13
+ spec.homepage = "https://github.com/RoverPlatform/apns-kit"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "http-2", "~> 0.8.1"
22
+ spec.add_dependency "concurrent-ruby", ">= 1.0.1"
23
+ spec.add_dependency "json", "> 0"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.11"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "apns_kit"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/apns_kit.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "apns_kit/version"
2
+ require "apns_kit/certificate"
3
+ require "apns_kit/connection"
4
+ require "apns_kit/request"
5
+ require "apns_kit/response"
6
+ require "apns_kit/notification"
7
+ require "apns_kit/client"
8
+ require "logger"
9
+
10
+ module ApnsKit
11
+ class << self
12
+
13
+ def logger
14
+ return @logger if defined?(@logger)
15
+ @logger = rails_logger || default_logger
16
+ end
17
+
18
+ def logger=(logger)
19
+ @logger = logger
20
+ end
21
+
22
+ def default_logger
23
+ logger = Logger.new($stdout)
24
+ logger.level = Logger::INFO
25
+ logger.formatter = proc do |severity, datetime, progname, msg|
26
+ "[#{datetime} ##{$$}] #{severity} -- : APNs Kit | #{msg}\n"
27
+ end
28
+ logger
29
+ end
30
+
31
+ def rails_logger
32
+ defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ module ApnsKit
2
+ class Certificate
3
+
4
+ def initialize(cert_data, passphrase = nil)
5
+ @key = OpenSSL::PKey::RSA.new(cert_data, passphrase)
6
+ @certificate = OpenSSL::X509::Certificate.new(cert_data)
7
+ end
8
+
9
+ def ssl_context
10
+ @ssl_context ||= OpenSSL::SSL::SSLContext.new.tap do |context|
11
+ context.key = @key
12
+ context.cert = @certificate
13
+ end
14
+ end
15
+
16
+ def production?
17
+ extension(PRODUCTION_ENV_EXTENSION).present?
18
+ end
19
+
20
+ def development?
21
+ extension(DEVELOPMENT_ENV_EXTENSION).present?
22
+ end
23
+
24
+ def universal?
25
+ extension(UNIVERSAL_CERTIFICATE_EXTENSION).present?
26
+ end
27
+
28
+ def app_bundle_id
29
+ @app_bundle_id ||= @certificate.subject.to_a.find { |key, *_| key == "UID" }[1]
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,79 @@
1
+ require 'uri'
2
+ require 'concurrent'
3
+
4
+ module ApnsKit
5
+
6
+ APPLE_PRODUCTION_API_URI = URI.parse("https://api.push.apple.com:443").freeze
7
+ APPLE_DEVELOPMENT_API_URI = URI.parse("https://api.development.push.apple.com:443").freeze
8
+
9
+ class Client
10
+
11
+ attr_reader :pool_size
12
+
13
+ attr_reader :connection_pool
14
+
15
+ attr_reader :default_topic
16
+
17
+ class << self
18
+
19
+ def production(certificate, pool_size: 1, heartbeat_interval: 60)
20
+ client = self.new(APPLE_PRODUCTION_API_URI, certificate, pool_size: pool_size, heartbeat_interval: heartbeat_interval)
21
+ client
22
+ end
23
+
24
+ def development(certificate, pool_size: 1, heartbeat_interval: 60)
25
+ client = self.new(APPLE_DEVELOPMENT_API_URI, certificate, pool_size: pool_size, heartbeat_interval: heartbeat_interval)
26
+ end
27
+
28
+ end
29
+
30
+ def initialize(uri, certificate, pool_size: 1, heartbeat_interval: 60)
31
+ @pool_size = pool_size
32
+ @connection_pool = @pool_size.times.map { ApnsKit::Connection.new(uri, certificate) }.freeze
33
+ @default_topic = certificate.app_bundle_id
34
+ if heartbeat_interval > 0
35
+ ApnsKit.logger.info("Setting up heartbeat checker")
36
+ @heartbeat_checker = Concurrent::TimerTask.new { @connection_pool.each(&:ping) }
37
+ @heartbeat_checker.execution_interval = heartbeat_interval
38
+ @heartbeat_checker.execute
39
+ end
40
+ end
41
+
42
+ def shutdown
43
+ @heartbeat_checker.shutdown if @heartbeat_checker
44
+ @connection_pool.each(&:close)
45
+ return true
46
+ end
47
+
48
+ def send_async(*notifications, &block)
49
+ notifications.flatten!
50
+ notifications.each { |notification| notification.topic = default_topic if notification.topic.nil? }
51
+ request = ApnsKit::Request.new(notifications)
52
+
53
+ if block
54
+ Concurrent::Future.execute{ request.perform_nonblocking_send(connection_pool.sample, &block) }
55
+ else
56
+ Concurrent::Future.execute{ request.perform_nonblocking_send(connection_pool.sample) }
57
+ end
58
+
59
+ return true
60
+ end
61
+
62
+ def send(*notifications)
63
+ return [] if notifications.empty?
64
+ notifications.flatten!
65
+ notifications.each { |notification| notification.topic = default_topic if notification.topic.nil? }
66
+ request = ApnsKit::Request.new(notifications)
67
+ return Concurrent::Future.execute{ request.perform_blocking_send(connection_pool.sample) }.value
68
+ end
69
+
70
+ def to_s
71
+ "uri=#{connection_pool.first.uri} connected=#{connection_pool.map(&:connected)} pool_size=#{pool_size}"
72
+ end
73
+
74
+ def inspect
75
+ "#<ApnsKit::Client:#{"0x00%x" % (object_id << 1)} #{to_s}>"
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,110 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+ require 'http/2'
4
+ require 'thread'
5
+ require 'uri'
6
+
7
+ module ApnsKit
8
+ class Connection
9
+
10
+ attr_reader :connected
11
+
12
+ attr_reader :uri
13
+
14
+ attr_reader :http
15
+
16
+ def initialize(uri, certificate)
17
+ @uri = uri
18
+ @certificate = certificate
19
+ @connected = false
20
+ @mutex = Mutex.new
21
+ end
22
+
23
+ def open
24
+ if !connected && (@thread.nil? || @thread.stop?)
25
+ start
26
+ end
27
+ end
28
+
29
+ def close
30
+ shutdown if !@thread.nil?
31
+ end
32
+
33
+ def ping
34
+ if @http
35
+ ApnsKit.logger.debug("Sending ping")
36
+ @http.ping("whatever")
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def setup_connection!
43
+ @mutex.synchronize do
44
+ ApnsKit.logger.info("Setting up connection")
45
+ ctx = @certificate.ssl_context
46
+ tcp = TCPSocket.new(@uri.host, @uri.port)
47
+
48
+ @socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
49
+
50
+ @socket.sync_close = true
51
+ @socket.hostname = @uri.hostname
52
+ @socket.connect
53
+
54
+ @connected = true
55
+
56
+ @http = HTTP2::Client.new
57
+ @http.on(:frame) do |bytes|
58
+ ApnsKit.logger.debug("Sending bytes: #{bytes.unpack("H*").first}")
59
+ @socket.print bytes
60
+ @socket.flush
61
+ end
62
+
63
+ ping
64
+ ApnsKit.logger.info("Connection established")
65
+ end
66
+ end
67
+
68
+ def start
69
+ setup_connection!
70
+ @thread = Thread.new {
71
+ loop do
72
+ begin
73
+ if @socket.closed?
74
+ close_connection!
75
+ ApnsKit.logger.warn("Socket was closed")
76
+ break
77
+ elsif !@socket.eof?
78
+ data = @socket.readpartial(1024)
79
+ ApnsKit.logger.debug("Received bytes: #{data.unpack("H*").first}")
80
+ @http << data
81
+ end
82
+ rescue => e
83
+ close_connection!
84
+ ApnsKit.logger.warn("#{e.class} exception: #{e.message} - closing socket")
85
+ e.backtrace.each { |l| ApnsKit.logger.debug(l) }
86
+ raise e
87
+ end
88
+ end
89
+ }
90
+ return true
91
+ end
92
+
93
+ def shutdown
94
+ @thread.exit
95
+ @thread.join
96
+ close_connection!
97
+ end
98
+
99
+ def close_connection!
100
+ @mutex.synchronize do
101
+ ApnsKit.logger.info("Closing connection")
102
+ @socket.close if @socket
103
+ @connected = false
104
+ @http = nil
105
+ ApnsKit.logger.info("Connection closed")
106
+ end
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,69 @@
1
+ require 'json'
2
+
3
+ module ApnsKit
4
+ class Notification
5
+
6
+ MAXIMUM_PAYLOAD_SIZE = 4096
7
+
8
+ attr_accessor :token
9
+
10
+ attr_accessor :id
11
+
12
+ attr_accessor :alert
13
+
14
+ attr_accessor :topic
15
+
16
+ attr_accessor :custom_data
17
+
18
+ def initialize(options)
19
+ @token = options.delete(:token) || options.delete(:device)
20
+ @alert = options.delete(:alert)
21
+ @badge = options.delete(:badge)
22
+ @sound = options.delete(:sound)
23
+ @category = options.delete(:category)
24
+ @expiry = options.delete(:expiry)
25
+ @id = options.delete(:id)
26
+ @priority = options.delete(:priority)
27
+ @content_available = options.delete(:content_available)
28
+ @topic = options.delete(:topic)
29
+
30
+ @custom_data = options
31
+ end
32
+
33
+ def id
34
+ @id ||= SecureRandom.uuid.upcase
35
+ end
36
+
37
+ def valid?
38
+ payload.bytesize <= MAXIMUM_PAYLOAD_SIZE
39
+ end
40
+
41
+ def header
42
+ json = {
43
+ ':scheme' => 'https',
44
+ ':method' => 'POST',
45
+ ':path' => "/3/device/#{token}",
46
+ 'apns-id' => id,
47
+ 'content-length' => payload.bytesize.to_s,
48
+ 'apns-topic' => topic
49
+ }
50
+
51
+ json.merge!({ "apns-expiry" => @expiry }) if @expiry
52
+ json.merge!({ "apns-priority" => @priority }) if @priority
53
+ return json
54
+ end
55
+
56
+ def payload
57
+ json = {}.merge(@custom_data || {}).inject({}){|h,(k,v)| h[k.to_s] = v; h}
58
+
59
+ json['aps'] ||= {}
60
+ json['aps']['alert'] = @alert if @alert
61
+ json['aps']['badge'] = @badge.to_i rescue 0 if @badge
62
+ json['aps']['sound'] = @sound if @sound
63
+ json['aps']['category'] = @category if @category
64
+ json['aps']['content-available'] = 1 if @content_available
65
+
66
+ JSON.dump(json)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ require 'concurrent'
2
+
3
+ module ApnsKit
4
+ class Request
5
+
6
+
7
+ def initialize(notifications)
8
+ @notifications = notifications
9
+ end
10
+
11
+ def perform_blocking_send(connection)
12
+ connection.open
13
+
14
+ responses = Concurrent::Array.new
15
+ latch = Concurrent::CountDownLatch.new(@notifications.size)
16
+
17
+ perform_nonblocking_send(connection) do |response|
18
+ responses.push(response)
19
+ latch.count_down
20
+ end
21
+
22
+ latch.wait
23
+ return responses
24
+ end
25
+
26
+ def perform_nonblocking_send(connection)
27
+ connection.open
28
+
29
+ ApnsKit.logger.info("Sending #{@notifications.size} notifications")
30
+ @notifications.each do |notification|
31
+ stream = connection.http.new_stream
32
+
33
+ response = ApnsKit::Response.new
34
+ response.notification = notification
35
+
36
+ stream.on(:headers) do |headers|
37
+ headers = Hash[*headers.flatten]
38
+ response.headers = headers
39
+ ApnsKit.logger.debug("Received headers #{headers}")
40
+ if response.success?
41
+ yield response if block_given?
42
+ end
43
+ end
44
+
45
+ stream.on(:data) do |data|
46
+ response.raw_body ||= ""
47
+ response.raw_body << data
48
+ ApnsKit.logger.debug("Received data #{data}")
49
+ yield response if block_given?
50
+ end
51
+
52
+ stream.headers(notification.header, end_stream: false)
53
+ stream.data(notification.payload)
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,70 @@
1
+ require 'json'
2
+
3
+ module ApnsKit
4
+ class Response
5
+
6
+ STATUS_CODES = {
7
+ 200 => "Success",
8
+ 400 => "Bad request",
9
+ 403 => "There was an error with the certificate",
10
+ 405 => "The request used a bad :method value. Only POST requests are supported",
11
+ 410 => "The device token is no longer active for the topic",
12
+ 413 => "The notification payload was too large",
13
+ 429 => "The server received too many requests for the same device token",
14
+ 500 => "Internal server error",
15
+ 503 => "The server is shutting down and unavailable",
16
+ }.freeze
17
+
18
+ INVALID_TOKEN_REASONS = Set.new(["Unregistered", "BadDeviceToken", "DeviceTokenNotForTopic"]).freeze
19
+
20
+ attr_accessor :headers, :raw_body, :notification
21
+
22
+ def id
23
+ headers["apns-id"]
24
+ end
25
+
26
+ def status
27
+ headers[":status"].to_i
28
+ end
29
+
30
+ def message
31
+ STATUS_CODES[status]
32
+ end
33
+
34
+ def success?
35
+ status == 200
36
+ end
37
+
38
+ def body
39
+ @body ||= raw_body.nil? ? {} : JSON.load(raw_body)
40
+ end
41
+
42
+ def failure_reason
43
+ body["reason"]
44
+ end
45
+
46
+ def invalid_token?
47
+ !success? && INVALID_TOKEN_REASONS.include?(failure_reason)
48
+ end
49
+
50
+ def unregistered?
51
+ !success? && failure_reason == "Unregistered"
52
+ end
53
+
54
+ def bad_device_token?
55
+ !success? && failure_reason == "BadDeviceToken"
56
+ end
57
+
58
+ def device_token_not_for_topic?
59
+ !success? && failure_reason == "DeviceTokenNotForTopic"
60
+ end
61
+
62
+ def to_s
63
+ "#{status} (#{message}) notification=#{notification}"
64
+ end
65
+
66
+ def inspect
67
+ "#<ApnsKit::Response:#{"0x00%x" % (object_id << 1)} #{to_s}>"
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module ApnsKit
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apns_kit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Recalis
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http-2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ description:
98
+ email:
99
+ - chris@rover.io
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - apns_kit.gemspec
112
+ - bin/console
113
+ - bin/setup
114
+ - lib/apns_kit.rb
115
+ - lib/apns_kit/certificate.rb
116
+ - lib/apns_kit/client.rb
117
+ - lib/apns_kit/connection.rb
118
+ - lib/apns_kit/notification.rb
119
+ - lib/apns_kit/request.rb
120
+ - lib/apns_kit/response.rb
121
+ - lib/apns_kit/version.rb
122
+ homepage: https://github.com/RoverPlatform/apns-kit
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.4.8
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Send push notifications using Apple's new http/2 APNs service
146
+ test_files: []