Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

1.7 upstream refresh #8

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ff78e00
Support Proxy setting.
n-miyo Oct 22, 2018
0cfcd9e
Relax connection pool dependency.
ostinelli Mar 8, 2019
ff72fae
Merge pull request #78 from n-miyo/support-proxy
ostinelli Mar 8, 2019
363e989
Relax bundler dependency for Travis.
ostinelli Mar 8, 2019
a8cc3e9
Bump version to 1.5.0.
ostinelli Mar 8, 2019
7541595
Fix to ensure one stream is always available.
benubois Jun 2, 2019
f85b372
Add apns-push-type header
noefroidevaux Aug 9, 2019
2e06257
Check the value of content-available in background_notification?
noefroidevaux Aug 9, 2019
b431b57
Merge pull request #85 from noefroidevaux/apns-push-type
ostinelli Aug 23, 2019
b71af58
Merge pull request #82 from feedbin/token_fix
ostinelli Aug 23, 2019
36a2e36
Bump version to 1.6.0.
ostinelli Aug 23, 2019
c6f1dd4
Upgrade dev ruby version to 2.7.1 (also, switch to asdf).
ostinelli Sep 24, 2020
1c0bec0
Upgrade ruby rake dependency (security).
ostinelli Sep 24, 2020
1ae07ad
Update README.
ostinelli Sep 24, 2020
49e105d
Update Travis' ruby versions.
ostinelli Sep 24, 2020
e30528b
Add timeout to .join call.
ostinelli Oct 1, 2020
09fe3e9
Bump version to 1.6.1.
ostinelli Oct 1, 2020
567fffe
Add target-content-id & interruption-level & relevance-score to aps p…
Sai Aug 16, 2021
2e49648
Merge pull request #106 from Sai/add-ios15-payload
benubois Sep 7, 2021
f43cdba
Bump to 1.7.0.
ostinelli Sep 22, 2021
664ac2f
Switch to monotonic clock.
benubois Nov 3, 2021
f191ebb
Fix typo
soffes Nov 9, 2021
9cfe7bc
Merge pull request #110 from soffes/patch-1
benubois Nov 11, 2021
3f994bb
Pin OpenSSL security_level to 1 in development.
benubois Nov 17, 2021
bb86a33
Indent method descriptions under method names.
benubois Nov 18, 2021
3a2eb41
Document :auth_method options.
benubois Nov 18, 2021
d7e7689
Merge pull request #111 from ostinelli/monotonic
benubois Nov 18, 2021
ec0e3f1
Merge pull request #112 from ostinelli/security_level
benubois Nov 18, 2021
c3d5ff6
chore: add ruby 3 to travis CI
clarkedb Feb 4, 2022
a777626
Merge pull request #115 from clarkedb/chore-add-ruby-3-to-travis-ci
benubois Feb 5, 2022
9b4d3b5
Renamed file.
benubois Feb 5, 2022
5e804cd
Change CI to GitHub Actions.
benubois Feb 5, 2022
6021988
Merge remote-tracking branch 'ostinelli/master' into upstream
joshhayes-sheen-vt Sep 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1']
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- name: Run tests
run: bundle exec rake
1 change: 0 additions & 1 deletion .ruby-gemset

This file was deleted.

1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 2.7.1
11 changes: 0 additions & 11 deletions .travis.yml

This file was deleted.

98 changes: 54 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ end
connection.push_async(push)

# wait for all requests to be completed
connection.join
connection.join(timeout: 5)

# close the connection
connection.close
Expand Down Expand Up @@ -201,12 +201,19 @@ To create a new persistent connection:
Apnotic::Connection.new(options)
```

| Option | Description
|-----|-----
| :cert_path | Required. The path to a valid APNS push certificate in `.pem` or `.p12` format, or any object that responds to `:read`.
| :cert_pass | Optional. The certificate's password.
| :url | Optional. Defaults to https://api.push.apple.com:443.
| :connect_timeout | Optional. Expressed in seconds, defaults to 30.
| Option | Description
|------------------|------------
| :cert_path | `Required` The path to a valid APNS push certificate or any object that responds to `:read`. Supported formats: `.pem`, `.p12` (`:cert` auth), or `.p8` (`:token` auth).
| :cert_pass | `Optional` The certificate's password.
| :auth_method | `Optional` The options are `:cert` or `:token`. Defaults to `:cert`.
| :team_id | `Required for :token auth` Team ID from [Membership Details](https://developer.apple.com/account/#!/membership/).
| :key_id | `Required for :token auth` ID from [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/authkeys).
| :url | `Optional` Defaults to https://api.push.apple.com:443.
| :connect_timeout | `Optional` Expressed in seconds, defaults to 30.
| :proxy_addr | `Optional` Proxy server. e.g. http://proxy.example.com
| :proxy_port | `Optional` Proxy port. e.g. 8080
| :proxy_user | `Optional` User name for proxy authentication. e.g. user_name
| :proxy_pass | `Optional` Password for proxy authentication. e.g. pass_word

Note that since `:cert_path` can be any object that responds to `:read`, it is possible to pass in a certificate string directly by wrapping it up in a `StringIO` object:

Expand All @@ -220,47 +227,47 @@ It is also possible to create a connection that points to the Apple Development
Apnotic::Connection.development(options)
```

> The concepts of PRODUCTION and DEVELOPMENT are different from what they used to be in previous specifications. Anything built directly from XCode and loaded on your phone will have the app generate DEVELOPMENT tokens, while everything else (TestFlight, Apple Store, HockeyApp, ...) will be considered as PRODUCTION environment.
> The concepts of PRODUCTION and DEVELOPMENT are different from what they used to be in previous specifications. Anything built directly from Xcode and loaded on your phone will have the app generate DEVELOPMENT tokens, while everything else (TestFlight, Apple Store, HockeyApp, ...) will be considered as PRODUCTION environment.

#### Methods

* **cert_path** → **`string`**
- **cert_path** → **`string`**

Returns the path to the certificate.
Returns the path to the certificate.

* **on(event, &block)**
- **on(event, &block)**

Allows to set a callback for the connection. The only available event is `:error`, which allows to set a callback when an error is raised at socket level, hence in the underlying socket thread.
Allows to set a callback for the connection. The only available event is `:error`, which allows to set a callback when an error is raised at socket level, hence in the underlying socket thread.

```ruby
connection.on(:error) { |exception| puts "Exception has been raised: #{exception}" }
```
```ruby
connection.on(:error) { |exception| puts "Exception has been raised: #{exception}" }
```

> If the `:error` callback is not set, the underlying socket thread may raise an error in the main thread at unexpected execution times.
> If the `:error` callback is not set, the underlying socket thread may raise an error in the main thread at unexpected execution times.

* **url** → **`URL`**
- **url** → **`URL`**

Returns the URL of the APNS endpoint.
Returns the URL of the APNS endpoint.

##### Blocking calls

* **push(notification, timeout: 30)** → **`Apnotic::Response` or `nil`**
- **push(notification, timeout: 30)** → **`Apnotic::Response` or `nil`**

Sends a notification. Returns `nil` in case a timeout occurs.
Sends a notification. Returns `nil` in case a timeout occurs.

##### Non-blocking calls

* **prepare_push(notification)** → **`Apnotic::Push`**
- **prepare_push(notification)** → **`Apnotic::Push`**

Prepares an async push.
Prepares an async push.

```ruby
push = client.prepare_push(notification)
```
```ruby
push = client.prepare_push(notification)
```

* **push_async(push)**
- **push_async(push)**

Sends the push asynchronously.
Sends the push asynchronously.


### `Apnotic::ConnectionPool`
Expand Down Expand Up @@ -311,6 +318,9 @@ These are all Accessor attributes.
| `category` | "
| `custom_payload` | "
| `thread_id` | "
| `target_content_id` | "
| `interruption_level` | Refer to [Payload Key Reference](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification#2943363) for details. iOS 15+
| `relevance_score` | Refer to [Payload Key Reference](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification#2943363) for details. iOS 15+
| `apns_id` | Refer to [Communicating with APNs](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) for details.
| `expiration` | "
| `priority` | "
Expand Down Expand Up @@ -348,43 +358,43 @@ The response to a call to `connection.push`.

#### Methods

* **body** → **`hash` or `string`**
- **body** → **`hash` or `string`**

Returns the body of the response in Hash format if a valid JSON was returned, otherwise just the RAW body.
Returns the body of the response in Hash format if a valid JSON was returned, otherwise just the RAW body.

* **headers** → **`hash`**
- **headers** → **`hash`**

Returns a Hash containing the Headers of the response.
Returns a Hash containing the Headers of the response.

* **ok?** → **`boolean`**
- **ok?** → **`boolean`**

Returns if the push was successful.
Returns if the push was successful.

* **status** → **`string`**
- **status** → **`string`**

Returns the status code.
Returns the status code.


### `Apnotic::Push`
The push object to be sent in an async call.

#### Methods

* **http2_request** → **`NetHttp2::Request`**
- **http2_request** → **`NetHttp2::Request`**

Returns the HTTP/2 request of the push.
Returns the HTTP/2 request of the push.

* **on(event, &block)**
- **on(event, &block)**

Allows to set a callback for the request. Available events are:
Allows to set a callback for the request. Available events are:

* `:response`: triggered when a response is fully received (called once).
`:response`: triggered when a response is fully received (called once).

Even if Apnotic is thread-safe, the async callbacks will be executed in a different thread, so ensure that your code in the callbacks is thread-safe.
Even if Apnotic is thread-safe, the async callbacks will be executed in a different thread, so ensure that your code in the callbacks is thread-safe.

```ruby
push.on(:response) { |response| p response.headers }
```
```ruby
push.on(:response) { |response| p response.headers }
```

## Getting Your APNs Certificate

Expand Down
4 changes: 4 additions & 0 deletions lib/apnotic/abstract_notification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def authorization_header
authorization ? "bearer #{authorization}" : nil
end

def background_notification?
false
end

private

def to_hash
Expand Down
21 changes: 17 additions & 4 deletions lib/apnotic/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Apnotic

APPLE_DEVELOPMENT_SERVER_URL = "https://api.sandbox.push.apple.com:443"
APPLE_PRODUCTION_SERVER_URL = "https://api.push.apple.com:443"
PROXY_SETTINGS_KEYS = [:proxy_addr, :proxy_port, :proxy_user, :proxy_pass]

class Connection
attr_reader :url, :cert_path
Expand All @@ -28,7 +29,15 @@ def initialize(options={})

raise "Cert file not found: #{@cert_path}" unless @cert_path && (@cert_path.respond_to?(:read) || File.exist?(@cert_path))

@client = NetHttp2::Client.new(@url, ssl_context: ssl_context, connect_timeout: @connect_timeout)
http2_options = {
ssl_context: ssl_context,
connect_timeout: @connect_timeout
}
PROXY_SETTINGS_KEYS.each do |key|
http2_options[key] = options[key] if options[key]
end

@client = NetHttp2::Client.new(@url, http2_options)
end

def push(notification, options={})
Expand Down Expand Up @@ -63,8 +72,8 @@ def close
@client.close
end

def join
@client.join
def join(timeout: nil)
@client.join(timeout: timeout)
end

def on(event, &block)
Expand Down Expand Up @@ -92,7 +101,7 @@ def streams_available?
def remote_max_concurrent_streams
# 0x7fffffff is the default value from http-2 gem (2^31)
if @client.remote_settings[:settings_max_concurrent_streams] == 0x7fffffff
0
1
else
@client.remote_settings[:settings_max_concurrent_streams]
end
Expand All @@ -105,6 +114,7 @@ def ssl_context
def build_ssl_context
@build_ssl_context ||= begin
ctx = OpenSSL::SSL::SSLContext.new
ctx.security_level = 1 if development? && ctx.respond_to?(:security_level)
begin
p12 = OpenSSL::PKCS12.new(certificate, @cert_pass)
ctx.key = p12.key
Expand Down Expand Up @@ -137,5 +147,8 @@ def provider_token
@provider_token_cache.call
end

def development?
url == APPLE_DEVELOPMENT_SERVER_URL
end
end
end
8 changes: 6 additions & 2 deletions lib/apnotic/instance_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ def call
private

def expired?
Time.now - @cached_at >= @ttl
now - @cached_at >= @ttl
end

def new_value
@cached_at = Time.now
@cached_at = now
@cached_value = @instance.send(@method)
end

def now
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
end
end
8 changes: 8 additions & 0 deletions lib/apnotic/notification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ module Apnotic

class Notification < AbstractNotification
attr_accessor :alert, :badge, :sound, :content_available, :category, :custom_payload, :url_args, :mutable_content, :thread_id
attr_accessor :target_content_id, :interruption_level, :relevance_score

def background_notification?
aps.count == 1 && aps.key?('content-available') && aps['content-available'] == 1
end

private

Expand All @@ -17,6 +22,9 @@ def aps
result.merge!('url-args' => url_args) if url_args
result.merge!('mutable-content' => mutable_content) if mutable_content
result.merge!('thread-id' => thread_id) if thread_id
result.merge!('target-content-id' => target_content_id) if target_content_id
result.merge!('interruption-level' => interruption_level) if interruption_level
result.merge!('relevance-score' => relevance_score) if relevance_score
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/apnotic/provider_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def header
def payload
JSON.generate({
iss: @team_id,
iat: Time.now.to_i
iat: Time.now.utc.to_i
})
end

Expand Down
1 change: 1 addition & 0 deletions lib/apnotic/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def build_headers_for(notification)
h.merge!('apns-id' => notification.apns_id) if notification.apns_id
h.merge!('apns-expiration' => notification.expiration) if notification.expiration
h.merge!('apns-priority' => notification.priority) if notification.priority
h.merge!('apns-push-type' => notification.background_notification? ? 'background' : 'alert' )
h.merge!('apns-topic' => notification.topic) if notification.topic
h.merge!('apns-collapse-id' => notification.apns_collapse_id) if notification.apns_collapse_id
h.merge!('authorization' => notification.authorization_header) if notification.authorization_header
Expand Down
2 changes: 1 addition & 1 deletion lib/apnotic/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Apnotic
VERSION = '1.4.2'.freeze
VERSION = '1.7.0'.freeze
end
Loading