lorkhan 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +3 -0
- data/README.md +3 -0
- data/Rakefile +6 -0
- data/bin/console +18 -0
- data/lib/lorkhan/connection.rb +73 -0
- data/lib/lorkhan/connection_pool.rb +6 -0
- data/lib/lorkhan/errors/apple_errors.rb +64 -0
- data/lib/lorkhan/errors/base.rb +6 -0
- data/lib/lorkhan/errors/http_error.rb +13 -0
- data/lib/lorkhan/errors/missing_topic_error.rb +6 -0
- data/lib/lorkhan/errors/timeout_error.rb +6 -0
- data/lib/lorkhan/errors.rb +5 -0
- data/lib/lorkhan/notification.rb +41 -0
- data/lib/lorkhan/provider_token.rb +41 -0
- data/lib/lorkhan/request.rb +27 -0
- data/lib/lorkhan/response.rb +26 -0
- data/lib/lorkhan/version.rb +3 -0
- data/lib/lorkhan.rb +11 -0
- data/lorkhan.gemspec +29 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b8c3fb041b617f47b7301f48c86f41b06c0db14a
|
4
|
+
data.tar.gz: 56a83e2fff46bbf4499ec092bc8f367d2ec3f602
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 869f5ce31fb9c8a52ff2b7cfbe851073072a04bf32aa3b02723d4a4779624bfdc97742a93b51e281d195ab824897f8497b3468f1c19826bce266444c5c4a4733
|
7
|
+
data.tar.gz: 214eecef306334378480d276d184b9e896072b356f9cc8e58c86d02bf8d93ffe8c2903ccb4d39edb04e0d225be86ab2b1ec03af2fb8bc75d6af29f78e705dfcc
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.4
|
3
|
+
Exclude:
|
4
|
+
- spec/**/*
|
5
|
+
|
6
|
+
Metrics/AbcSize:
|
7
|
+
Max: 30
|
8
|
+
|
9
|
+
Metrics/LineLength:
|
10
|
+
Max: 120
|
11
|
+
|
12
|
+
Metrics/MethodLength:
|
13
|
+
Max: 16
|
14
|
+
|
15
|
+
Style/Documentation:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/ClassAndModuleChildren:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Style/FrozenStringLiteralComment:
|
22
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
### 0.0.4
|
2
|
+
|
3
|
+
- Added `Lorkhan::Notification::DEFAULT_SOUND_NAME`
|
4
|
+
|
5
|
+
### 0.0.3
|
6
|
+
|
7
|
+
- Sending a `content_available` push, now excludes the `alert`, `badge`, `sound` keys.
|
8
|
+
|
9
|
+
### 0.0.2
|
10
|
+
|
11
|
+
- Calling `Lorkan::Connection#push` will automatically check when the auth token was generated and refresh the token if needed.
|
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'lorkhan'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
DEVICE_TOKEN = ENV['LORKHAN_TEST_DEVICE_TOKEN']
|
8
|
+
AUTH_TOKEN_PATH = File.expand_path(ENV['LORKHAN_TEST_AUTH_KEY_PATH'])
|
9
|
+
TEAM_ID = ENV['LORKHAN_TEST_TEAM_ID']
|
10
|
+
KEY_ID = ENV['LORKHAN_TEST_AUTH_KEY_ID']
|
11
|
+
|
12
|
+
raise "Missing device token ENV['LORKHAN_TEST_DEVICE_TOKEN']" if DEVICE_TOKEN.nil?
|
13
|
+
raise "Missing device token ENV['LORKHAN_TEST_AUTH_KEY_PATH']" if AUTH_TOKEN_PATH.nil?
|
14
|
+
raise 'Missing auth token file' unless File.exist?(AUTH_TOKEN_PATH)
|
15
|
+
raise "Missing device token ENV['LORKHAN_TEST_TEAM_ID']" if TEAM_ID.nil?
|
16
|
+
raise "Missing device token ENV['LORKHAN_TEST_AUTH_KEY_ID']" if KEY_ID.nil?
|
17
|
+
|
18
|
+
Pry.start
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'net-http2'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Lorkhan
|
5
|
+
APNS_PRODUCTION_HOST = 'api.push.apple.com'.freeze
|
6
|
+
APNS_DEVELOPMENT_HOST = 'api.development.push.apple.com'.freeze
|
7
|
+
TOKEN_EXPIRE_TIME_SEC = 3300 # 55 minutes (Apple timeout in 60)
|
8
|
+
|
9
|
+
class Connection
|
10
|
+
attr_reader :host, :token
|
11
|
+
|
12
|
+
def initialize(token:, production: true, timeout: 30)
|
13
|
+
@host = production ? APNS_PRODUCTION_HOST : APNS_DEVELOPMENT_HOST
|
14
|
+
@timeout = timeout
|
15
|
+
@token = token
|
16
|
+
@client = NetHttp2::Client.new(url, connect_timeout: @connect_timeout)
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
@client.close
|
21
|
+
end
|
22
|
+
|
23
|
+
def join
|
24
|
+
@client.join
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh_token
|
28
|
+
@auth_token = nil
|
29
|
+
close
|
30
|
+
end
|
31
|
+
|
32
|
+
def push(notification)
|
33
|
+
check_token_should_refresh
|
34
|
+
request = Request.new(notification)
|
35
|
+
request.validate!
|
36
|
+
headers = request.headers
|
37
|
+
headers['authorization'] = "bearer #{auth_token}"
|
38
|
+
raw_response = @client.call(:post, request.path, body: request.body, headers: headers, timeout: 5)
|
39
|
+
raise Errors::TimeoutError if raw_response.nil?
|
40
|
+
response = Response.new(raw_response)
|
41
|
+
handle_http_error(response) unless response.ok?
|
42
|
+
response
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def auth_token
|
48
|
+
@auth_token ||= begin
|
49
|
+
@refresh_token_at = Time.now.to_i + TOKEN_EXPIRE_TIME_SEC
|
50
|
+
token.encode
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def url
|
55
|
+
"https://#{host}:443"
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_http_error(response)
|
59
|
+
if response.body
|
60
|
+
if (reason = response.body['reason'])
|
61
|
+
klass = Lorkhan::Errors::Apple::MAPPINGS[reason]
|
62
|
+
raise klass, response if klass
|
63
|
+
end
|
64
|
+
end
|
65
|
+
raise Errors::HTTPError, response
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_token_should_refresh
|
69
|
+
return if @refresh_token_at.nil?
|
70
|
+
refresh_token if Time.now.to_i >= @refresh_token_at
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'lorkhan/errors/base'
|
2
|
+
require 'lorkhan/errors/http_error'
|
3
|
+
|
4
|
+
module Lorkhan
|
5
|
+
module Errors
|
6
|
+
module Apple
|
7
|
+
class BadCollapseId < Errors::HTTPError; end
|
8
|
+
class BadDeviceToken < Errors::HTTPError; end
|
9
|
+
class BadExpirationDate < Errors::HTTPError; end
|
10
|
+
class BadMessageId < Errors::HTTPError; end
|
11
|
+
class BadPriority < Errors::HTTPError; end
|
12
|
+
class BadTopic < Errors::HTTPError; end
|
13
|
+
class DeviceTokenNotForTopic < Errors::HTTPError; end
|
14
|
+
class DuplicateHeaders < Errors::HTTPError; end
|
15
|
+
class IdleTimeout < Errors::HTTPError; end
|
16
|
+
class MissingDeviceToken < Errors::HTTPError; end
|
17
|
+
class MissingTopic < Errors::HTTPError; end
|
18
|
+
class PayloadEmpty < Errors::HTTPError; end
|
19
|
+
class TopicDisallowed < Errors::HTTPError; end
|
20
|
+
class ExpiredProviderToken < Errors::HTTPError; end
|
21
|
+
class Forbidden < Errors::HTTPError; end
|
22
|
+
class InvalidProviderToken < Errors::HTTPError; end
|
23
|
+
class MissingProviderToken < Errors::HTTPError; end
|
24
|
+
class BadPath < Errors::HTTPError; end
|
25
|
+
class MethodNotAllowed < Errors::HTTPError; end
|
26
|
+
class Unregistered < Errors::HTTPError; end
|
27
|
+
class PayloadTooLarge < Errors::HTTPError; end
|
28
|
+
class TooManyProviderTokenUpdates < Errors::HTTPError; end
|
29
|
+
class TooManyRequests < Errors::HTTPError; end
|
30
|
+
class InternalServerError < Errors::HTTPError; end
|
31
|
+
class ServiceUnavailable < Errors::HTTPError; end
|
32
|
+
class Shutdown < Errors::HTTPError; end
|
33
|
+
|
34
|
+
MAPPINGS = {
|
35
|
+
'BadCollapseId' => BadCollapseId,
|
36
|
+
'BadDeviceToken' => BadDeviceToken,
|
37
|
+
'BadExpirationDate' => BadExpirationDate,
|
38
|
+
'BadMessageId' => BadMessageId,
|
39
|
+
'BadPriority' => BadPriority,
|
40
|
+
'BadTopic' => BadTopic,
|
41
|
+
'DeviceTokenNotForTopic' => DeviceTokenNotForTopic,
|
42
|
+
'DuplicateHeaders' => DuplicateHeaders,
|
43
|
+
'IdleTimeout' => IdleTimeout,
|
44
|
+
'MissingDeviceToken' => MissingDeviceToken,
|
45
|
+
'MissingTopic' => MissingTopic,
|
46
|
+
'PayloadEmpty' => PayloadEmpty,
|
47
|
+
'TopicDisallowed' => TopicDisallowed,
|
48
|
+
'ExpiredProviderToken' => ExpiredProviderToken,
|
49
|
+
'Forbidden' => Forbidden,
|
50
|
+
'InvalidProviderToken' => InvalidProviderToken,
|
51
|
+
'MissingProviderToken' => MissingProviderToken,
|
52
|
+
'BadPath' => BadPath,
|
53
|
+
'MethodNotAllowed' => MethodNotAllowed,
|
54
|
+
'Unregistered' => Unregistered,
|
55
|
+
'PayloadTooLarge' => PayloadTooLarge,
|
56
|
+
'TooManyProviderTokenUpdates' => TooManyProviderTokenUpdates,
|
57
|
+
'TooManyRequests' => TooManyRequests,
|
58
|
+
'InternalServerError' => InternalServerError,
|
59
|
+
'ServiceUnavailable' => ServiceUnavailable,
|
60
|
+
'Shutdown' => Shutdown
|
61
|
+
}.freeze
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Lorkhan
|
5
|
+
class Notification
|
6
|
+
DEFAULT_SOUND_NAME = 'default'.freeze
|
7
|
+
|
8
|
+
attr_reader :token, :apns_id
|
9
|
+
attr_accessor :custom_payload, :alert, :badge, :sound, :category, :content_available, :url_args, :mutable_content
|
10
|
+
attr_accessor :expiration, :priority, :topic, :collapse_id
|
11
|
+
|
12
|
+
def initialize(token)
|
13
|
+
@token = token
|
14
|
+
@apns_id = SecureRandom.uuid
|
15
|
+
@expiration = 0
|
16
|
+
@priority = 10
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
JSON.dump(to_h).force_encoding(Encoding::BINARY)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{}.tap do |root|
|
25
|
+
root[:aps] = {}.tap do |aps|
|
26
|
+
if content_available
|
27
|
+
aps['content-available'] = 1
|
28
|
+
else
|
29
|
+
aps[:alert] = alert if alert
|
30
|
+
aps[:badge] = badge if badge
|
31
|
+
aps[:sound] = sound if sound
|
32
|
+
end
|
33
|
+
aps[:category] = category if category
|
34
|
+
aps['url-args'] = url_args if url_args
|
35
|
+
aps['mutable-content'] = mutable_content if mutable_content
|
36
|
+
end
|
37
|
+
root.merge!(custom_payload) if custom_payload
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Lorkhan
|
5
|
+
class ProviderToken
|
6
|
+
ALGORITHM = 'ES256'.freeze
|
7
|
+
|
8
|
+
def initialize(key_id:, team_id:, secret:)
|
9
|
+
@key_id = key_id
|
10
|
+
@team_id = team_id
|
11
|
+
@secret = secret
|
12
|
+
end
|
13
|
+
|
14
|
+
def encode
|
15
|
+
JWT.encode(payload, p_key, ALGORITHM, headers)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def p_key
|
21
|
+
@p_key ||= begin
|
22
|
+
sec = OpenSSL::PKey::EC.new(@secret)
|
23
|
+
sec.check_key
|
24
|
+
sec
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def payload
|
29
|
+
{
|
30
|
+
iss: @team_id,
|
31
|
+
iat: Time.now.utc.to_i
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def headers
|
36
|
+
{
|
37
|
+
kid: @key_id
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Lorkhan
|
2
|
+
class Request
|
3
|
+
attr_reader :path, :headers, :body
|
4
|
+
|
5
|
+
def initialize(notification)
|
6
|
+
@path = "/3/device/#{notification.token}"
|
7
|
+
@body = notification.body
|
8
|
+
@headers = build_headers(notification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate!
|
12
|
+
raise Errors::MissingTopicError if headers['apns-topic'].nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def build_headers(notif)
|
18
|
+
{}.tap do |head|
|
19
|
+
head['apns-id'] = notif.apns_id if notif.apns_id
|
20
|
+
head['apns-expiration'] = notif.expiration.to_s if notif.expiration
|
21
|
+
head['apns-priority'] = notif.priority.to_s if notif.priority
|
22
|
+
head['apns-topic'] = notif.topic if notif.topic
|
23
|
+
head['apns-collapse-id'] = notif.collapse_id if notif.collapse_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Lorkhan
|
4
|
+
class Response
|
5
|
+
attr_reader :headers, :body
|
6
|
+
|
7
|
+
def initialize(raw)
|
8
|
+
@headers = raw.headers
|
9
|
+
@body = JSON.parse(raw.body)
|
10
|
+
rescue JSON::ParserError
|
11
|
+
@body = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def ok?
|
15
|
+
status == 200
|
16
|
+
end
|
17
|
+
|
18
|
+
def status
|
19
|
+
headers[':status'].to_i
|
20
|
+
end
|
21
|
+
|
22
|
+
def apns_id
|
23
|
+
headers['apns-id']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/lorkhan.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'lorkhan/connection_pool'
|
2
|
+
require 'lorkhan/connection'
|
3
|
+
require 'lorkhan/errors'
|
4
|
+
require 'lorkhan/notification'
|
5
|
+
require 'lorkhan/provider_token'
|
6
|
+
require 'lorkhan/request'
|
7
|
+
require 'lorkhan/response'
|
8
|
+
require 'lorkhan/version'
|
9
|
+
|
10
|
+
module Lorkhan
|
11
|
+
end
|
data/lorkhan.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lorkhan/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'lorkhan'
|
8
|
+
spec.version = Lorkhan::VERSION
|
9
|
+
spec.licenses = ['MIT']
|
10
|
+
spec.authors = ['Skylar Schipper']
|
11
|
+
spec.email = ['skylar@pco.bz']
|
12
|
+
spec.summary = 'APNS HTTP/2 Client'
|
13
|
+
spec.homepage = 'http://github.com/ministrycentered/lorkhan'
|
14
|
+
spec.required_ruby_version = '>=2.3.0'
|
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 = %w(lib)
|
20
|
+
|
21
|
+
spec.add_dependency 'net-http2', '>= 0.14.1', '< 2'
|
22
|
+
spec.add_dependency 'connection_pool', '>= 2.0'
|
23
|
+
spec.add_dependency 'jwt', '>= 1.5.6'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.5'
|
28
|
+
spec.add_development_dependency 'pry'
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lorkhan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Skylar Schipper
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-http2
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.14.1
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.14.1
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: connection_pool
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jwt
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.5.6
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.5.6
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.3'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.3'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '10.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '10.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.5'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.5'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: pry
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
description:
|
118
|
+
email:
|
119
|
+
- skylar@pco.bz
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- ".gitignore"
|
125
|
+
- ".rspec"
|
126
|
+
- ".rubocop.yml"
|
127
|
+
- CHANGELOG.md
|
128
|
+
- Gemfile
|
129
|
+
- README.md
|
130
|
+
- Rakefile
|
131
|
+
- bin/console
|
132
|
+
- lib/lorkhan.rb
|
133
|
+
- lib/lorkhan/connection.rb
|
134
|
+
- lib/lorkhan/connection_pool.rb
|
135
|
+
- lib/lorkhan/errors.rb
|
136
|
+
- lib/lorkhan/errors/apple_errors.rb
|
137
|
+
- lib/lorkhan/errors/base.rb
|
138
|
+
- lib/lorkhan/errors/http_error.rb
|
139
|
+
- lib/lorkhan/errors/missing_topic_error.rb
|
140
|
+
- lib/lorkhan/errors/timeout_error.rb
|
141
|
+
- lib/lorkhan/notification.rb
|
142
|
+
- lib/lorkhan/provider_token.rb
|
143
|
+
- lib/lorkhan/request.rb
|
144
|
+
- lib/lorkhan/response.rb
|
145
|
+
- lib/lorkhan/version.rb
|
146
|
+
- lorkhan.gemspec
|
147
|
+
homepage: http://github.com/ministrycentered/lorkhan
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
metadata: {}
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 2.3.0
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
requirements: []
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 2.6.13
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: APNS HTTP/2 Client
|
171
|
+
test_files: []
|