From ff78e007361e44548d5415d86e3e270579a9a286 Mon Sep 17 00:00:00 2001 From: "MIYOKAWA, Nobuyoshi" Date: Mon, 22 Oct 2018 23:13:57 +0900 Subject: [PATCH 01/24] Support Proxy setting. --- README.md | 4 ++++ lib/apnotic/connection.rb | 11 ++++++++++- spec/apnotic/connection_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 36aa1bf..b0da27a 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,10 @@ Apnotic::Connection.new(options) | :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. +| :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: diff --git a/lib/apnotic/connection.rb b/lib/apnotic/connection.rb index e583402..080a077 100644 --- a/lib/apnotic/connection.rb +++ b/lib/apnotic/connection.rb @@ -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 @@ -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={}) diff --git a/spec/apnotic/connection_spec.rb b/spec/apnotic/connection_spec.rb index 6398da7..eed158f 100644 --- a/spec/apnotic/connection_spec.rb +++ b/spec/apnotic/connection_spec.rb @@ -3,12 +3,26 @@ describe Apnotic::Connection do let(:url) { "https://localhost" } let(:cert_path) { apn_file_path } + let(:proxy_settings) { + { + proxy_addr: "http://proxy", + proxy_port: "8080", + proxy_user: "proxy-user", + proxy_pass: "proxy-pass" + } + } let(:connection) do Apnotic::Connection.new({ url: url, cert_path: cert_path }) end + let(:connection_proxy) do + Apnotic::Connection.new({ + url: url, + cert_path: cert_path + }.merge(proxy_settings)) + end describe ".new" do @@ -64,6 +78,17 @@ end end end + + describe "option: proxy family" do + context "when proxy is set" do + it "has proxy-assigned NetHttp2 instance" do + client = connection_proxy.instance_variable_get(:@client) + proxy_settings.each do |k, v| + expect(client.instance_variable_get("@#{k}")).to eq v + end + end + end + end end describe ".development" do From 0cfcd9ee4d5566de12cd991647a4a2a4860c51d3 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Fri, 8 Mar 2019 08:03:16 -0800 Subject: [PATCH 02/24] Relax connection pool dependency. --- apnotic.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apnotic.gemspec b/apnotic.gemspec index 935f240..e326512 100644 --- a/apnotic.gemspec +++ b/apnotic.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "net-http2", ">= 0.18", "< 2" - spec.add_dependency "connection_pool", "~> 2.0" + spec.add_dependency "connection_pool", "~> 2" spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake", "~> 10.0" From 363e989c3053c75e656ada845c08672ffa990f47 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Fri, 8 Mar 2019 08:09:31 -0800 Subject: [PATCH 03/24] Relax bundler dependency for Travis. --- apnotic.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apnotic.gemspec b/apnotic.gemspec index e326512..4058d99 100644 --- a/apnotic.gemspec +++ b/apnotic.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.add_dependency "net-http2", ">= 0.18", "< 2" spec.add_dependency "connection_pool", "~> 2" - spec.add_development_dependency "bundler", "~> 1.3" + spec.add_development_dependency "bundler" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.0" end From a8cc3e95e9d8d8f28f88dea27ff9c5c96187bc16 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Fri, 8 Mar 2019 08:10:14 -0800 Subject: [PATCH 04/24] Bump version to 1.5.0. --- lib/apnotic/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/apnotic/version.rb b/lib/apnotic/version.rb index 552d7ca..f53d423 100644 --- a/lib/apnotic/version.rb +++ b/lib/apnotic/version.rb @@ -1,3 +1,3 @@ module Apnotic - VERSION = '1.4.1'.freeze + VERSION = '1.5.0'.freeze end From 75415950576598e947b518219517fee700bfe8b8 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Sun, 2 Jun 2019 12:48:25 -0700 Subject: [PATCH 05/24] Fix to ensure one stream is always available. --- lib/apnotic/connection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/apnotic/connection.rb b/lib/apnotic/connection.rb index 080a077..a50b2a9 100644 --- a/lib/apnotic/connection.rb +++ b/lib/apnotic/connection.rb @@ -101,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 From f85b372682729558a9d08208d61c0437a8199d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9=20Froidevaux?= Date: Fri, 9 Aug 2019 18:10:42 +0200 Subject: [PATCH 06/24] Add apns-push-type header --- lib/apnotic/abstract_notification.rb | 4 +++ lib/apnotic/notification.rb | 4 +++ lib/apnotic/request.rb | 1 + spec/apnotic/notification_spec.rb | 29 +++++++++++++++++++++ spec/apnotic/request_spec.rb | 38 +++++++++++++++++++++------- 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/lib/apnotic/abstract_notification.rb b/lib/apnotic/abstract_notification.rb index 501b341..1bdea27 100644 --- a/lib/apnotic/abstract_notification.rb +++ b/lib/apnotic/abstract_notification.rb @@ -26,6 +26,10 @@ def authorization_header authorization ? "bearer #{authorization}" : nil end + def background_notification? + false + end + private def to_hash diff --git a/lib/apnotic/notification.rb b/lib/apnotic/notification.rb index a87397a..c5315c3 100644 --- a/lib/apnotic/notification.rb +++ b/lib/apnotic/notification.rb @@ -5,6 +5,10 @@ module Apnotic class Notification < AbstractNotification attr_accessor :alert, :badge, :sound, :content_available, :category, :custom_payload, :url_args, :mutable_content, :thread_id + def background_notification? + aps.count == 1 && aps.key?('content-available') + end + private def aps diff --git a/lib/apnotic/request.rb b/lib/apnotic/request.rb index 63bdaeb..95ea607 100644 --- a/lib/apnotic/request.rb +++ b/lib/apnotic/request.rb @@ -16,6 +16,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 diff --git a/spec/apnotic/notification_spec.rb b/spec/apnotic/notification_spec.rb index e8f12a7..6083450 100644 --- a/spec/apnotic/notification_spec.rb +++ b/spec/apnotic/notification_spec.rb @@ -142,4 +142,33 @@ ) } end end + + describe "#background_notification?" do + subject { notification.background_notification? } + + context "when content-available is not set" do + before do + notification.alert = "An alert" + end + + it { expect(subject).to eq false } + end + + context "when only content-available is set to 1" do + before do + notification.content_available = 1 + end + + it { expect(subject).to eq true } + end + + context "when content-available is set to 1 with others attributes" do + before do + notification.alert = "An alert" + notification.content_available = 1 + end + + it { expect(subject).to eq false } + end + end end diff --git a/spec/apnotic/request_spec.rb b/spec/apnotic/request_spec.rb index bcf84c3..63fe48f 100644 --- a/spec/apnotic/request_spec.rb +++ b/spec/apnotic/request_spec.rb @@ -38,14 +38,34 @@ def build_headers subject { build_headers } - it { is_expected.to eq ( - { - "apns-id" => "apns-id", - "apns-expiration" => "1461491082", - "apns-priority" => "10", - "apns-topic" => "com.example.myapp", - "apns-collapse-id" => "collapse-id" - } - ) } + context "when it's an alert notification" do + it { is_expected.to eq ( + { + "apns-id" => "apns-id", + "apns-expiration" => "1461491082", + "apns-priority" => "10", + "apns-push-type" => "alert", + "apns-topic" => "com.example.myapp", + "apns-collapse-id" => "collapse-id" + } + ) } + end + + context "when it's a background notification" do + before do + notification.content_available = 1 + end + + it { is_expected.to eq ( + { + "apns-id" => "apns-id", + "apns-expiration" => "1461491082", + "apns-priority" => "10", + "apns-push-type" => "background", + "apns-topic" => "com.example.myapp", + "apns-collapse-id" => "collapse-id" + } + ) } + end end end From 2e062575ae98936f8b5cf7379d5bb430ed7e85a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9=20Froidevaux?= Date: Fri, 9 Aug 2019 18:15:32 +0200 Subject: [PATCH 07/24] Check the value of content-available in background_notification? --- lib/apnotic/notification.rb | 2 +- spec/apnotic/notification_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/apnotic/notification.rb b/lib/apnotic/notification.rb index c5315c3..43527d2 100644 --- a/lib/apnotic/notification.rb +++ b/lib/apnotic/notification.rb @@ -6,7 +6,7 @@ class Notification < AbstractNotification attr_accessor :alert, :badge, :sound, :content_available, :category, :custom_payload, :url_args, :mutable_content, :thread_id def background_notification? - aps.count == 1 && aps.key?('content-available') + aps.count == 1 && aps.key?('content-available') && aps['content-available'] == 1 end private diff --git a/spec/apnotic/notification_spec.rb b/spec/apnotic/notification_spec.rb index 6083450..9f603a0 100644 --- a/spec/apnotic/notification_spec.rb +++ b/spec/apnotic/notification_spec.rb @@ -162,6 +162,14 @@ it { expect(subject).to eq true } end + context "when only content-available is set to 0" do + before do + notification.content_available = 0 + end + + it { expect(subject).to eq false } + end + context "when content-available is set to 1 with others attributes" do before do notification.alert = "An alert" From 36a2e36e0b89c5aec627226c83c7955f68f0b3f5 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Fri, 23 Aug 2019 13:19:49 +0200 Subject: [PATCH 08/24] Bump version to 1.6.0. --- lib/apnotic/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/apnotic/version.rb b/lib/apnotic/version.rb index f53d423..136d62a 100644 --- a/lib/apnotic/version.rb +++ b/lib/apnotic/version.rb @@ -1,3 +1,3 @@ module Apnotic - VERSION = '1.5.0'.freeze + VERSION = '1.6.0'.freeze end From c6f1dd4598c8f65326f09b0e7d74bb5b79408250 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Thu, 24 Sep 2020 14:53:33 +0200 Subject: [PATCH 09/24] Upgrade dev ruby version to 2.7.1 (also, switch to asdf). --- .ruby-gemset | 1 - .ruby-version | 1 - .tool-versions | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .ruby-gemset delete mode 100644 .ruby-version create mode 100644 .tool-versions diff --git a/.ruby-gemset b/.ruby-gemset deleted file mode 100644 index cbcbe92..0000000 --- a/.ruby-gemset +++ /dev/null @@ -1 +0,0 @@ -apnotic diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index c1026d2..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -ruby-2.3.1 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..a9e31a4 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.7.1 From 1c0bec02fe0f2378f2a8e5ac8731f5203b8050e1 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Thu, 24 Sep 2020 14:53:45 +0200 Subject: [PATCH 10/24] Upgrade ruby rake dependency (security). --- apnotic.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apnotic.gemspec b/apnotic.gemspec index 4058d99..4f2af2e 100644 --- a/apnotic.gemspec +++ b/apnotic.gemspec @@ -22,6 +22,6 @@ Gem::Specification.new do |spec| spec.add_dependency "connection_pool", "~> 2" spec.add_development_dependency "bundler" - spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rake", ">= 12.3.3" spec.add_development_dependency "rspec", "~> 3.0" end From 1ae07add9f813808cb094be4270251473ae4421c Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Thu, 24 Sep 2020 14:55:33 +0200 Subject: [PATCH 11/24] Update README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0da27a..a2af4da 100644 --- a/README.md +++ b/README.md @@ -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 From 49e105d02134831f5b96b48a15a1fcf839555fe9 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Thu, 24 Sep 2020 14:57:03 +0200 Subject: [PATCH 12/24] Update Travis' ruby versions. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 71824df..9e0feb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,14 @@ language: ruby rvm: - - 2.1 - - 2.2 - 2.3 - 2.4 - 2.5 + - 2.6 + - 2.7 branches: only: - master + +before_install: + - gem install bundler From e30528bdd7194a183a9bbf235e98a3410f43bf80 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Thu, 1 Oct 2020 21:58:27 +0200 Subject: [PATCH 13/24] Add timeout to .join call. --- apnotic.gemspec | 2 +- lib/apnotic/connection.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apnotic.gemspec b/apnotic.gemspec index 4f2af2e..415f180 100644 --- a/apnotic.gemspec +++ b/apnotic.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "net-http2", ">= 0.18", "< 2" + spec.add_dependency "net-http2", ">= 0.18.3", "< 2" spec.add_dependency "connection_pool", "~> 2" spec.add_development_dependency "bundler" diff --git a/lib/apnotic/connection.rb b/lib/apnotic/connection.rb index a50b2a9..edefc48 100644 --- a/lib/apnotic/connection.rb +++ b/lib/apnotic/connection.rb @@ -72,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) From 09fe3e9468ec8a9ce1e887a4c628c0755faf8ae1 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Thu, 1 Oct 2020 21:58:48 +0200 Subject: [PATCH 14/24] Bump version to 1.6.1. --- lib/apnotic/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/apnotic/version.rb b/lib/apnotic/version.rb index 136d62a..14341a9 100644 --- a/lib/apnotic/version.rb +++ b/lib/apnotic/version.rb @@ -1,3 +1,3 @@ module Apnotic - VERSION = '1.6.0'.freeze + VERSION = '1.6.1'.freeze end From 567fffe6718ffd9e733d54a1709f88bcaf428ea6 Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 16 Aug 2021 17:24:28 +0800 Subject: [PATCH 15/24] Add target-content-id & interruption-level & relevance-score to aps payload --- README.md | 3 ++ lib/apnotic/notification.rb | 6 +++- spec/apnotic/notification_spec.rb | 50 +++++++++++++++++++------------ 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a2af4da..8d0fcf8 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,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` | " diff --git a/lib/apnotic/notification.rb b/lib/apnotic/notification.rb index 43527d2..d521ac7 100644 --- a/lib/apnotic/notification.rb +++ b/lib/apnotic/notification.rb @@ -4,7 +4,8 @@ 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 @@ -21,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 diff --git a/spec/apnotic/notification_spec.rb b/spec/apnotic/notification_spec.rb index 9f603a0..b5cf604 100644 --- a/spec/apnotic/notification_spec.rb +++ b/spec/apnotic/notification_spec.rb @@ -7,17 +7,20 @@ subject { notification } - # + # describe "remote notification payload" do before do - notification.alert = "Something for you!" - notification.badge = 22 - notification.sound = "sound.wav" - notification.content_available = false - notification.category = "action_one" - notification.thread_id = 'action_id' - notification.custom_payload = { acme1: "bar" } + notification.alert = "Something for you!" + notification.badge = 22 + notification.sound = "sound.wav" + notification.content_available = false + notification.category = "action_one" + notification.thread_id = "action_id" + notification.target_content_id = "target_content_id" + notification.interruption_level = "passive" + notification.relevance_score = 0.8 + notification.custom_payload = { acme1: "bar" } end it { is_expected.to have_attributes(token: "token") } @@ -27,6 +30,9 @@ it { is_expected.to have_attributes(content_available: false) } it { is_expected.to have_attributes(category: "action_one") } it { is_expected.to have_attributes(thread_id: "action_id") } + it { is_expected.to have_attributes(target_content_id: "target_content_id") } + it { is_expected.to have_attributes(interruption_level: "passive") } + it { is_expected.to have_attributes(relevance_score: 0.8) } it { is_expected.to have_attributes(custom_payload: { acme1: "bar" }) } end @@ -91,14 +97,17 @@ context "when everything is specified" do before do - notification.alert = "Something for you!" - notification.badge = 22 - notification.sound = "sound.wav" - notification.content_available = 1 - notification.category = "action_one" - notification.thread_id = 'action_id' - notification.custom_payload = { acme1: "bar" } - notification.mutable_content = 1 + notification.alert = "Something for you!" + notification.badge = 22 + notification.sound = "sound.wav" + notification.content_available = 1 + notification.category = "action_one" + notification.thread_id = 'action_id' + notification.target_content_id = "target_content_id" + notification.interruption_level = "passive" + notification.relevance_score = 0.8 + notification.custom_payload = { acme1: "bar" } + notification.mutable_content = 1 end it { is_expected.to eq ( @@ -108,9 +117,12 @@ badge: 22, sound: "sound.wav", category: "action_one", - 'content-available' => 1, - 'mutable-content' => 1, - 'thread-id' => 'action_id' + 'content-available' => 1, + 'mutable-content' => 1, + 'thread-id' => 'action_id', + 'target-content-id' => 'target_content_id', + 'interruption-level' => 'passive', + 'relevance-score' => 0.8 }, acme1: "bar" }.to_json From f43cdbafdf126bb977c08001499088fed3d14b98 Mon Sep 17 00:00:00 2001 From: Roberto Ostinelli Date: Wed, 22 Sep 2021 14:22:24 +0200 Subject: [PATCH 16/24] Bump to 1.7.0. --- lib/apnotic/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/apnotic/version.rb b/lib/apnotic/version.rb index 14341a9..f138dca 100644 --- a/lib/apnotic/version.rb +++ b/lib/apnotic/version.rb @@ -1,3 +1,3 @@ module Apnotic - VERSION = '1.6.1'.freeze + VERSION = '1.7.0'.freeze end From 664ac2f6f56d46ae7bf69963b58c92caebf2abe9 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Wed, 3 Nov 2021 15:54:43 +0100 Subject: [PATCH 17/24] Switch to monotonic clock. This will insulate time calculations from system time. --- lib/apnotic/instance_cache.rb | 8 ++++++-- lib/apnotic/provider_token.rb | 2 +- spec/apnotic/instance_cache_spec.rb | 9 +++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/apnotic/instance_cache.rb b/lib/apnotic/instance_cache.rb index 341f6e4..32d46bb 100644 --- a/lib/apnotic/instance_cache.rb +++ b/lib/apnotic/instance_cache.rb @@ -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 \ No newline at end of file diff --git a/lib/apnotic/provider_token.rb b/lib/apnotic/provider_token.rb index c658961..81b1085 100644 --- a/lib/apnotic/provider_token.rb +++ b/lib/apnotic/provider_token.rb @@ -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 diff --git a/spec/apnotic/instance_cache_spec.rb b/spec/apnotic/instance_cache_spec.rb index b5f4336..1405748 100644 --- a/spec/apnotic/instance_cache_spec.rb +++ b/spec/apnotic/instance_cache_spec.rb @@ -2,6 +2,7 @@ describe Apnotic::InstanceCache do let(:seconds) { 60 } + let(:now) { Process.clock_gettime(Process::CLOCK_MONOTONIC) } let(:instance_cache) do Apnotic::InstanceCache.new(Time, :now, seconds) end @@ -11,15 +12,15 @@ it "has the same token in the same period" do original = subject.call - valid_time = Time.now + seconds - 1 - allow(Time).to receive(:now).and_return(valid_time) + valid_time = now + seconds - 1 + allow(Process).to receive(:clock_gettime).and_return(valid_time) expect(original).to eq subject.call end it "should change after ttl expires" do original = subject.call - expired_time = Time.now + seconds - allow(Time).to receive(:now).and_return(expired_time) + expired_time = now + seconds + allow(Process).to receive(:clock_gettime).and_return(expired_time) expect(original).not_to eq subject.call end end From f191ebbebd842e3abbcb5b653d8baee6b405700a Mon Sep 17 00:00:00 2001 From: Sam Soffes Date: Tue, 9 Nov 2021 10:15:26 -0800 Subject: [PATCH 18/24] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d0fcf8..acdaf57 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ 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 From 3f994bbc85574b667ee13434bc5956c4cc41b9fb Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Wed, 17 Nov 2021 17:24:33 +0100 Subject: [PATCH 19/24] Pin OpenSSL security_level to 1 in development. Apple generates development/sandbox SHA-1 signed certificates. If OpenSSL is configured with a SECLEVEL of 2 or above, an `OpenSSL::SSL::SSLError: SSL_CTX_use_certificate` exception is raised. --- lib/apnotic/connection.rb | 4 ++++ spec/apnotic/connection_spec.rb | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/lib/apnotic/connection.rb b/lib/apnotic/connection.rb index edefc48..24e3634 100644 --- a/lib/apnotic/connection.rb +++ b/lib/apnotic/connection.rb @@ -114,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 @@ -146,5 +147,8 @@ def provider_token @provider_token_cache.call end + def development? + url == APPLE_DEVELOPMENT_SERVER_URL + end end end diff --git a/spec/apnotic/connection_spec.rb b/spec/apnotic/connection_spec.rb index eed158f..07cd814 100644 --- a/spec/apnotic/connection_spec.rb +++ b/spec/apnotic/connection_spec.rb @@ -94,6 +94,8 @@ describe ".development" do let(:options) { { url: "will-be-overwritten", other: "options" } } + subject { Apnotic::Connection.development(cert_path: cert_path) } + it "initializes a connection object with url set to APPLE DEVELOPMENT" do expect(Apnotic::Connection).to receive(:new).with(options.merge({ url: "https://api.sandbox.push.apple.com:443" @@ -101,6 +103,10 @@ Apnotic::Connection.development(options) end + + it "responds to development?" do + expect(subject.send(:development?)).to eq true + end end describe "#on" do From bb86a3386c84d2064b63327ba2e816da574984a6 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Thu, 18 Nov 2021 14:49:32 +0100 Subject: [PATCH 20/24] Indent method descriptions under method names. --- README.md | 72 +++++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index acdaf57..fec503a 100644 --- a/README.md +++ b/README.md @@ -228,43 +228,43 @@ Apnotic::Connection.development(options) #### 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` @@ -355,21 +355,21 @@ 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` @@ -377,21 +377,21 @@ 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 From 3a2eb41ae73e72769e341f926b1f5a5097b50700 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Thu, 18 Nov 2021 15:10:11 +0100 Subject: [PATCH 21/24] Document :auth_method options. --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fec503a..926e67f 100644 --- a/README.md +++ b/README.md @@ -201,16 +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. -| :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 +| 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: From c3d5ff65b32b280f0d384628ef7be0670894003d Mon Sep 17 00:00:00 2001 From: Clark Brown Date: Fri, 4 Feb 2022 09:08:59 -0700 Subject: [PATCH 22/24] chore: add ruby 3 to travis CI --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9e0feb2..099c18a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ rvm: - 2.5 - 2.6 - 2.7 + - '3.0' + - 3.1 branches: only: From 9b4d3b5899d447adcd36452be93313ccbaf4e5a8 Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Sat, 5 Feb 2022 20:27:33 +0100 Subject: [PATCH 23/24] Renamed file. --- .travis.yml => .github/workflows/ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .travis.yml => .github/workflows/ci.yml (100%) diff --git a/.travis.yml b/.github/workflows/ci.yml similarity index 100% rename from .travis.yml rename to .github/workflows/ci.yml From 5e804cd0c9b394472e17c84f679727ec4501766f Mon Sep 17 00:00:00 2001 From: Ben Ubois Date: Sat, 5 Feb 2022 20:28:18 +0100 Subject: [PATCH 24/24] Change CI to GitHub Actions. --- .github/workflows/ci.yml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 099c18a..8aa1418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,19 @@ -language: ruby -rvm: - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - '3.0' - - 3.1 +name: CI -branches: - only: - - master +on: [push, pull_request] -before_install: - - gem install bundler +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