lorkhan 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|