lorkhan 0.0.4 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b8c3fb041b617f47b7301f48c86f41b06c0db14a
4
- data.tar.gz: 56a83e2fff46bbf4499ec092bc8f367d2ec3f602
3
+ metadata.gz: c9d9e98e45358c0cd329ab15535946be86a84a2b
4
+ data.tar.gz: ad11e57d08eb593634f3f741a0d532a57673ab86
5
5
  SHA512:
6
- metadata.gz: 869f5ce31fb9c8a52ff2b7cfbe851073072a04bf32aa3b02723d4a4779624bfdc97742a93b51e281d195ab824897f8497b3468f1c19826bce266444c5c4a4733
7
- data.tar.gz: 214eecef306334378480d276d184b9e896072b356f9cc8e58c86d02bf8d93ffe8c2903ccb4d39edb04e0d225be86ab2b1ec03af2fb8bc75d6af29f78e705dfcc
6
+ metadata.gz: ff895526c5d9d028eedb0a6bc0c14bd3e864ed6bed44b11ae25ec92328c7e7896710ab670dccbac6b393e9e471d57ee492622d2b1a715cd03ebbb6d50634b0c1
7
+ data.tar.gz: e06ac2239cb27cbd11966f1566e721a1340d5b3521484432e4dfeb1593e2363de2cbeb2fc4efc93c1f5fabd33fe6b091bede2dc0d8deaab7cabb028dd9d1c0d9
data/.rubocop.yml CHANGED
@@ -12,6 +12,12 @@ Metrics/LineLength:
12
12
  Metrics/MethodLength:
13
13
  Max: 16
14
14
 
15
+ Metrics/CyclomaticComplexity:
16
+ Max: 10
17
+
18
+ Metrics/PerceivedComplexity:
19
+ Max: 10
20
+
15
21
  Style/Documentation:
16
22
  Enabled: false
17
23
 
data/CHANGELOG.md CHANGED
@@ -1,7 +1,3 @@
1
- ### 0.0.4
2
-
3
- - Added `Lorkhan::Notification::DEFAULT_SOUND_NAME`
4
-
5
1
  ### 0.0.3
6
2
 
7
3
  - Sending a `content_available` push, now excludes the `alert`, `badge`, `sound` keys.
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lorkhan (1.0.0)
5
+ connection_pool (>= 2.0)
6
+ jwt (>= 1.5.6)
7
+ net-http2 (>= 0.14.1, < 2)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ coderay (1.1.1)
13
+ connection_pool (2.2.1)
14
+ diff-lcs (1.3)
15
+ http-2 (0.8.3)
16
+ jwt (1.5.6)
17
+ method_source (0.8.2)
18
+ net-http2 (0.14.1)
19
+ http-2 (~> 0.8.2)
20
+ pry (0.10.4)
21
+ coderay (~> 1.1.0)
22
+ method_source (~> 0.8.1)
23
+ slop (~> 3.4)
24
+ rake (10.5.0)
25
+ rspec (3.5.0)
26
+ rspec-core (~> 3.5.0)
27
+ rspec-expectations (~> 3.5.0)
28
+ rspec-mocks (~> 3.5.0)
29
+ rspec-core (3.5.4)
30
+ rspec-support (~> 3.5.0)
31
+ rspec-expectations (3.5.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.5.0)
34
+ rspec-mocks (3.5.0)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.5.0)
37
+ rspec-support (3.5.0)
38
+ slop (3.6.0)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (~> 1.3)
45
+ lorkhan!
46
+ pry
47
+ rake (~> 10.0)
48
+ rspec (~> 3.5)
49
+
50
+ BUNDLED WITH
51
+ 1.13.6
data/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # THIS IS DEPRECATED
1
+ ## Lorkhan
2
2
 
3
- `gem 'lorkhan', '~> 1.0'`
3
+ Apple Push Notification Services client using the HTTP/2 API and Provider Authentication Tokens
4
+
5
+ ### Getting Provider Authentication Tokens
6
+
7
+ See: "Generate a universal provider token signing key" under "Configure push notifications." in [Xcode Help](http://help.apple.com/xcode)
8
+
9
+ ### Sending a Push Notification
10
+
11
+ ```ruby
12
+ token = Lorkhan::ProviderToken.new(key_id: '<token key id>', team_id: '<developer team id>', secret: '<PAT secret>')
13
+ connection = Lorkhan::Connection.new(production: true, token: token)
14
+
15
+ notification = Lorkhan::Notification.new('<device token>')
16
+ notification.topic = '<your app ID>'
17
+ notification.alert = 'Hello from Lorkhan'
18
+
19
+ connection.push(notification)
20
+ ```
21
+
22
+ ### Other Gotcha's
23
+
24
+ - Your OpenSSL implementation __MUST__ support the `ES256` elliptical curve.
25
+
26
+ ### Extra reading
27
+
28
+ ##### [Local and Remote Notification Programming Guide](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1)
@@ -4,31 +4,53 @@ require 'openssl'
4
4
  module Lorkhan
5
5
  APNS_PRODUCTION_HOST = 'api.push.apple.com'.freeze
6
6
  APNS_DEVELOPMENT_HOST = 'api.development.push.apple.com'.freeze
7
- TOKEN_EXPIRE_TIME_SEC = 3300 # 55 minutes (Apple timeout in 60)
7
+ TOKEN_EXPIRE_TIME_SEC = 3300 # 55 minutes (Apple timeout is 60)
8
8
 
9
+ ##
10
+ # The Connection class manages the HTTP/2 connection to Apple's servers
11
+ #
12
+ # token: (Lorkhan::ProviderToken) Used to authenticate with Apple's servers
13
+ # production: (Boolean) Used to determine which endpoint to establish the connection
14
+ # timeout: (Int) Number of seconds to wait before timing out opening the connection
15
+ ##
9
16
  class Connection
10
17
  attr_reader :host, :token
11
18
 
12
19
  def initialize(token:, production: true, timeout: 30)
20
+ raise ArgumentError, 'Token must be a Lorkhan::ProviderToken' unless token.is_a?(Lorkhan::ProviderToken)
21
+
13
22
  @host = production ? APNS_PRODUCTION_HOST : APNS_DEVELOPMENT_HOST
14
23
  @timeout = timeout
15
24
  @token = token
16
25
  @client = NetHttp2::Client.new(url, connect_timeout: @connect_timeout)
17
26
  end
18
27
 
28
+ ##
29
+ # Closes the connection to apple.
30
+ ##
19
31
  def close
20
32
  @client.close
21
33
  end
22
34
 
23
- def join
24
- @client.join
25
- end
26
-
35
+ ##
36
+ # Closes the connection and resets the authentication token.
37
+ #
38
+ # This should happen automatically 55 minutes after the original token was generated.
39
+ ##
27
40
  def refresh_token
28
41
  @auth_token = nil
29
42
  close
30
43
  end
31
44
 
45
+ ##
46
+ # Deliver a Lorkhan::Notification to Apple's servers.
47
+ #
48
+ # If the connection is not open, this will attempt to establish the connection.
49
+ #
50
+ # notification: (Lorkhan::Notification) The notification to deliver.
51
+ #
52
+ # return: The HTTP response from Apple
53
+ ##
32
54
  def push(notification)
33
55
  check_token_should_refresh
34
56
  request = Request.new(notification)
@@ -4,31 +4,87 @@ require 'lorkhan/errors/http_error'
4
4
  module Lorkhan
5
5
  module Errors
6
6
  module Apple
7
+ # The collapse identifier exceeds the maximum allowed size
7
8
  class BadCollapseId < Errors::HTTPError; end
9
+
10
+ # The specified device token was bad.
11
+ # Verify that the request contains a valid token and that the token matches the environment.
8
12
  class BadDeviceToken < Errors::HTTPError; end
13
+
14
+ # The apns-expiration value is bad.
9
15
  class BadExpirationDate < Errors::HTTPError; end
16
+
17
+ # The apns-id value is bad.
10
18
  class BadMessageId < Errors::HTTPError; end
19
+
20
+ # The apns-priority value is bad.
11
21
  class BadPriority < Errors::HTTPError; end
22
+
23
+ # The apns-topic was invalid.
12
24
  class BadTopic < Errors::HTTPError; end
25
+
26
+ # The device token does not match the specified topic.
13
27
  class DeviceTokenNotForTopic < Errors::HTTPError; end
28
+
29
+ # One or more headers were repeated.
14
30
  class DuplicateHeaders < Errors::HTTPError; end
31
+
32
+ # Idle time out.
15
33
  class IdleTimeout < Errors::HTTPError; end
34
+
35
+ # The device token is not specified in the request :path. Verify that the :path header contains the device token.
16
36
  class MissingDeviceToken < Errors::HTTPError; end
37
+
38
+ # The apns-topic header of the request was not specified and was required.
39
+ # The apns-topic header is mandatory when the client is
40
+ # connected using a certificate that supports multiple topics.
17
41
  class MissingTopic < Errors::HTTPError; end
42
+
43
+ # The message payload was empty.
18
44
  class PayloadEmpty < Errors::HTTPError; end
45
+
46
+ # Pushing to this topic is not allowed.
19
47
  class TopicDisallowed < Errors::HTTPError; end
48
+
49
+ # The provider token is stale and a new token should be generated.
20
50
  class ExpiredProviderToken < Errors::HTTPError; end
51
+
52
+ # The specified action is not allowed.
21
53
  class Forbidden < Errors::HTTPError; end
54
+
55
+ # The provider token is not valid or the token signature could not be verified
22
56
  class InvalidProviderToken < Errors::HTTPError; end
57
+
58
+ # No provider certificate was used to connect to APNs and Authorization
59
+ # header was missing or no provider token was specified.
60
+ # The specified :method was not POST.
23
61
  class MissingProviderToken < Errors::HTTPError; end
62
+
63
+ # The request contained a bad :path value.
24
64
  class BadPath < Errors::HTTPError; end
65
+
66
+ # The specified :method was not POST.
25
67
  class MethodNotAllowed < Errors::HTTPError; end
68
+
69
+ # The device token is inactive for the specified topic.
26
70
  class Unregistered < Errors::HTTPError; end
71
+
72
+ # The message payload was too large.
27
73
  class PayloadTooLarge < Errors::HTTPError; end
74
+
75
+ # The provider token is being updated too often.
28
76
  class TooManyProviderTokenUpdates < Errors::HTTPError; end
77
+
78
+ # Too many requests were made consecutively to the same device token.
29
79
  class TooManyRequests < Errors::HTTPError; end
80
+
81
+ # An internal server error occurred.
30
82
  class InternalServerError < Errors::HTTPError; end
83
+
84
+ # The service is unavailable.
31
85
  class ServiceUnavailable < Errors::HTTPError; end
86
+
87
+ # The server is shutting down.
32
88
  class Shutdown < Errors::HTTPError; end
33
89
 
34
90
  MAPPINGS = {
@@ -2,18 +2,65 @@ require 'json'
2
2
  require 'securerandom'
3
3
 
4
4
  module Lorkhan
5
+ ##
6
+ # Describes a push notification
7
+ #
8
+ # Required attributes
9
+ #
10
+ # - token: The device token to deliver the notification to
11
+ # - apns_id: The APNS id for the notification.
12
+ # This *must* be a UUID and is created when the instance is initialized.
13
+ # - priority: The priority of the notification. Apple will use this to determine how to deliver the notification.
14
+ # See `PRIORITY_DELIVER_IMMEDIATELY` & `PRIORITY_DELIVER_BACKGROUND` for more info.
15
+ # - expiration: A UNIX UTC epoch expressed in seconds for how long Apple will retain the notification,
16
+ # and attempt redelivery. A 0 time will attempt a single delivery then disguard the message.
17
+ # - topic: The topic of the remote notification, which is typically the bundle ID for your app.
18
+ # The certificate you create in your developer account must include the capability for this topic.
19
+ #
20
+ # Optional attributes
21
+ #
22
+ # - alert: The message displayed to the user.
23
+ # - badge: The badge number displayed on the app icon.
24
+ # - category: The category for the notification. Used to provide direct notification actions.
25
+ # - collapse_id: Used to group multiple notifications on the screen.
26
+ # - content_available: A boolean if this should be a content available push.
27
+ # This will deliver a silent notification to the device.
28
+ # Your app's entitlements must be enabled.
29
+ # If the value for this is truthy, the alert, badge, sound will be ignored.
30
+ # - custom_payload: A custom hash of data to send. Must be JSON encodable.
31
+ # - mutable_content: A `1` if the notificaiton is mutable.
32
+ # See `UNNotificationServiceExtension` (https://developer.apple.com/reference/usernotifications/unnotificationserviceextension)
33
+ # - sound: The name of a bundled sound file to play.
34
+ # - url_args: Used in conjunction with the `urlFormatString` in
35
+ # the `website.json` file for sending web notifications.
36
+ ##
5
37
  class Notification
6
- DEFAULT_SOUND_NAME = 'default'.freeze
38
+ ##
39
+ # Send the push message immediately.
40
+ # Notifications with this priority must trigger an alert, sound, or badge on the target device.
41
+ ##
42
+ PRIORITY_DELIVER_IMMEDIATELY = 10
43
+
44
+ ##
45
+ # Send the push message at a time that takes into account power considerations for the device.
46
+ # Notifications with this priority might be grouped and delivered in bursts.
47
+ ##
48
+ PRIORITY_DELIVER_BACKGROUND = 5
7
49
 
8
50
  attr_reader :token, :apns_id
9
51
  attr_accessor :custom_payload, :alert, :badge, :sound, :category, :content_available, :url_args, :mutable_content
10
52
  attr_accessor :expiration, :priority, :topic, :collapse_id
11
53
 
54
+ ##
55
+ # Create a new notification
56
+ #
57
+ # token: The Device token that the notification will be delivered to
58
+ ##
12
59
  def initialize(token)
13
60
  @token = token
14
61
  @apns_id = SecureRandom.uuid
15
62
  @expiration = 0
16
- @priority = 10
63
+ @priority = PRIORITY_DELIVER_IMMEDIATELY
17
64
  end
18
65
 
19
66
  def body
@@ -32,7 +79,7 @@ module Lorkhan
32
79
  end
33
80
  aps[:category] = category if category
34
81
  aps['url-args'] = url_args if url_args
35
- aps['mutable-content'] = mutable_content if mutable_content
82
+ aps['mutable-content'] = 1 if mutable_content
36
83
  end
37
84
  root.merge!(custom_payload) if custom_payload
38
85
  end
@@ -2,9 +2,22 @@ require 'jwt'
2
2
  require 'openssl'
3
3
 
4
4
  module Lorkhan
5
+ ##
6
+ # Wrapper for creating the authentication token for communicating with Apple's servers.
7
+ #
8
+ # See the "Provider Authentication Tokens" from Apple's "Local and Remote Notification Programming Guide"
9
+ # https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1
10
+ ##
5
11
  class ProviderToken
6
12
  ALGORITHM = 'ES256'.freeze
7
13
 
14
+ ##
15
+ # Create a new token
16
+ #
17
+ # key_id: An alphanumeric string obtained from the developer portal.
18
+ # team_id: The team id for your developer account. Obtained from the developer portal.
19
+ # secret: The content of the Authentication Token key file obtained from the developer portal.
20
+ ##
8
21
  def initialize(key_id:, team_id:, secret:)
9
22
  @key_id = key_id
10
23
  @team_id = team_id
@@ -1,9 +1,13 @@
1
1
  require 'json'
2
2
 
3
3
  module Lorkhan
4
+ ##
5
+ # A wrapper around the HTTP/2 response from Apple
6
+ ##
4
7
  class Response
5
8
  attr_reader :headers, :body
6
9
 
10
+ # This class should never be instantiated directly.
7
11
  def initialize(raw)
8
12
  @headers = raw.headers
9
13
  @body = JSON.parse(raw.body)
@@ -11,14 +15,23 @@ module Lorkhan
11
15
  @body = nil
12
16
  end
13
17
 
18
+ ##
19
+ # Convenience method to check if the status is 200
20
+ ##
14
21
  def ok?
15
22
  status == 200
16
23
  end
17
24
 
25
+ ##
26
+ # Get the HTTP status
27
+ ##
18
28
  def status
19
29
  headers[':status'].to_i
20
30
  end
21
31
 
32
+ ##
33
+ # Get the APNS id for the notification
34
+ ##
22
35
  def apns_id
23
36
  headers['apns-id']
24
37
  end
@@ -1,3 +1,3 @@
1
1
  module Lorkhan
2
- VERSION = '0.0.4'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
data/lorkhan.gemspec CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Lorkhan::VERSION
9
9
  spec.licenses = ['MIT']
10
10
  spec.authors = ['Skylar Schipper']
11
- spec.email = ['skylar@pco.bz']
11
+ spec.email = ['me@skylarsch.com']
12
12
  spec.summary = 'APNS HTTP/2 Client'
13
- spec.homepage = 'http://github.com/ministrycentered/lorkhan'
13
+ spec.homepage = 'http://github.com/skylarsch/lorkhan'
14
14
  spec.required_ruby_version = '>=2.3.0'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lorkhan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Skylar Schipper
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-20 00:00:00.000000000 Z
11
+ date: 2017-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-http2
@@ -116,16 +116,16 @@ dependencies:
116
116
  version: '0'
117
117
  description:
118
118
  email:
119
- - skylar@pco.bz
119
+ - me@skylarsch.com
120
120
  executables: []
121
121
  extensions: []
122
122
  extra_rdoc_files: []
123
123
  files:
124
- - ".gitignore"
125
124
  - ".rspec"
126
125
  - ".rubocop.yml"
127
126
  - CHANGELOG.md
128
127
  - Gemfile
128
+ - Gemfile.lock
129
129
  - README.md
130
130
  - Rakefile
131
131
  - bin/console
@@ -144,7 +144,7 @@ files:
144
144
  - lib/lorkhan/response.rb
145
145
  - lib/lorkhan/version.rb
146
146
  - lorkhan.gemspec
147
- homepage: http://github.com/ministrycentered/lorkhan
147
+ homepage: http://github.com/skylarsch/lorkhan
148
148
  licenses:
149
149
  - MIT
150
150
  metadata: {}
@@ -164,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
164
  version: '0'
165
165
  requirements: []
166
166
  rubyforge_project:
167
- rubygems_version: 2.6.13
167
+ rubygems_version: 2.5.2
168
168
  signing_key:
169
169
  specification_version: 4
170
170
  summary: APNS HTTP/2 Client
data/.gitignore DELETED
@@ -1,3 +0,0 @@
1
- *.gem
2
- Gemfile.lock
3
- .DS_Store