lorkhan 0.0.4 → 1.0.0

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