rpush_extended 3.2.5
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 +7 -0
- data/CHANGELOG.md +365 -0
- data/LICENSE +7 -0
- data/README.md +393 -0
- data/bin/rpush +4 -0
- data/lib/generators/rpush_config_generator.rb +7 -0
- data/lib/generators/rpush_migration_generator.rb +66 -0
- data/lib/generators/templates/add_adm.rb +23 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
- data/lib/generators/templates/add_gcm.rb +117 -0
- data/lib/generators/templates/add_rpush.rb +402 -0
- data/lib/generators/templates/add_wpns.rb +16 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +25 -0
- data/lib/generators/templates/create_rapns_notifications.rb +36 -0
- data/lib/generators/templates/rename_rapns_to_rpush.rb +87 -0
- data/lib/generators/templates/rpush.rb +135 -0
- data/lib/generators/templates/rpush_2_0_0_updates.rb +79 -0
- data/lib/generators/templates/rpush_2_1_0_updates.rb +11 -0
- data/lib/generators/templates/rpush_2_6_0_updates.rb +10 -0
- data/lib/generators/templates/rpush_2_7_0_updates.rb +12 -0
- data/lib/generators/templates/rpush_3_0_0_updates.rb +11 -0
- data/lib/generators/templates/rpush_3_0_1_updates.rb +13 -0
- data/lib/generators/templates/rpush_3_1_0_add_pushy.rb +9 -0
- data/lib/generators/templates/rpush_3_1_1_updates.rb +15 -0
- data/lib/generators/templates/rpush_3_2_0_add_apns_p8.rb +15 -0
- data/lib/generators/templates/rpush_3_2_4_updates.rb +9 -0
- data/lib/generators/templates/rpush_3_3_0_updates.rb +9 -0
- data/lib/generators/templates/rpush_3_3_1_updates.rb +11 -0
- data/lib/rpush/apns_feedback.rb +17 -0
- data/lib/rpush/cli.rb +213 -0
- data/lib/rpush/client/active_model/adm/app.rb +23 -0
- data/lib/rpush/client/active_model/adm/data_validator.rb +14 -0
- data/lib/rpush/client/active_model/adm/notification.rb +28 -0
- data/lib/rpush/client/active_model/apns/app.rb +37 -0
- data/lib/rpush/client/active_model/apns/binary_notification_validator.rb +16 -0
- data/lib/rpush/client/active_model/apns/device_token_format_validator.rb +14 -0
- data/lib/rpush/client/active_model/apns/notification.rb +104 -0
- data/lib/rpush/client/active_model/apns2/app.rb +15 -0
- data/lib/rpush/client/active_model/apns2/notification.rb +9 -0
- data/lib/rpush/client/active_model/apnsp8/app.rb +23 -0
- data/lib/rpush/client/active_model/apnsp8/notification.rb +9 -0
- data/lib/rpush/client/active_model/gcm/app.rb +19 -0
- data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +14 -0
- data/lib/rpush/client/active_model/gcm/notification.rb +59 -0
- data/lib/rpush/client/active_model/notification.rb +22 -0
- data/lib/rpush/client/active_model/payload_data_size_validator.rb +13 -0
- data/lib/rpush/client/active_model/pushy/app.rb +20 -0
- data/lib/rpush/client/active_model/pushy/notification.rb +31 -0
- data/lib/rpush/client/active_model/pushy/time_to_live_validator.rb +14 -0
- data/lib/rpush/client/active_model/registration_ids_count_validator.rb +13 -0
- data/lib/rpush/client/active_model/wns/app.rb +23 -0
- data/lib/rpush/client/active_model/wns/notification.rb +32 -0
- data/lib/rpush/client/active_model/wpns/app.rb +13 -0
- data/lib/rpush/client/active_model/wpns/notification.rb +28 -0
- data/lib/rpush/client/active_model.rb +34 -0
- data/lib/rpush/client/active_record/adm/app.rb +11 -0
- data/lib/rpush/client/active_record/adm/notification.rb +11 -0
- data/lib/rpush/client/active_record/apns/app.rb +11 -0
- data/lib/rpush/client/active_record/apns/feedback.rb +18 -0
- data/lib/rpush/client/active_record/apns/notification.rb +40 -0
- data/lib/rpush/client/active_record/apns2/app.rb +11 -0
- data/lib/rpush/client/active_record/apns2/notification.rb +10 -0
- data/lib/rpush/client/active_record/apnsp8/app.rb +11 -0
- data/lib/rpush/client/active_record/apnsp8/notification.rb +10 -0
- data/lib/rpush/client/active_record/app.rb +13 -0
- data/lib/rpush/client/active_record/gcm/app.rb +11 -0
- data/lib/rpush/client/active_record/gcm/notification.rb +11 -0
- data/lib/rpush/client/active_record/notification.rb +42 -0
- data/lib/rpush/client/active_record/pushy/app.rb +11 -0
- data/lib/rpush/client/active_record/pushy/notification.rb +11 -0
- data/lib/rpush/client/active_record/wns/app.rb +11 -0
- data/lib/rpush/client/active_record/wns/badge_notification.rb +15 -0
- data/lib/rpush/client/active_record/wns/notification.rb +11 -0
- data/lib/rpush/client/active_record/wns/raw_notification.rb +13 -0
- data/lib/rpush/client/active_record/wpns/app.rb +11 -0
- data/lib/rpush/client/active_record/wpns/notification.rb +11 -0
- data/lib/rpush/client/active_record.rb +33 -0
- data/lib/rpush/client/redis/adm/app.rb +14 -0
- data/lib/rpush/client/redis/adm/notification.rb +11 -0
- data/lib/rpush/client/redis/apns/app.rb +11 -0
- data/lib/rpush/client/redis/apns/feedback.rb +20 -0
- data/lib/rpush/client/redis/apns/notification.rb +11 -0
- data/lib/rpush/client/redis/apns2/app.rb +11 -0
- data/lib/rpush/client/redis/apns2/notification.rb +11 -0
- data/lib/rpush/client/redis/apnsp8/app.rb +11 -0
- data/lib/rpush/client/redis/apnsp8/notification.rb +11 -0
- data/lib/rpush/client/redis/app.rb +29 -0
- data/lib/rpush/client/redis/gcm/app.rb +11 -0
- data/lib/rpush/client/redis/gcm/notification.rb +11 -0
- data/lib/rpush/client/redis/notification.rb +74 -0
- data/lib/rpush/client/redis/pushy/app.rb +16 -0
- data/lib/rpush/client/redis/pushy/notification.rb +18 -0
- data/lib/rpush/client/redis/wns/app.rb +14 -0
- data/lib/rpush/client/redis/wns/badge_notification.rb +15 -0
- data/lib/rpush/client/redis/wns/notification.rb +11 -0
- data/lib/rpush/client/redis/wns/raw_notification.rb +11 -0
- data/lib/rpush/client/redis/wpns/app.rb +11 -0
- data/lib/rpush/client/redis/wpns/notification.rb +11 -0
- data/lib/rpush/client/redis.rb +56 -0
- data/lib/rpush/configuration.rb +115 -0
- data/lib/rpush/daemon/adm/delivery.rb +226 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/apns/delivery.rb +43 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +90 -0
- data/lib/rpush/daemon/apns.rb +17 -0
- data/lib/rpush/daemon/apns2/delivery.rb +127 -0
- data/lib/rpush/daemon/apns2.rb +10 -0
- data/lib/rpush/daemon/apnsp8/delivery.rb +166 -0
- data/lib/rpush/daemon/apnsp8/token.rb +43 -0
- data/lib/rpush/daemon/apnsp8.rb +10 -0
- data/lib/rpush/daemon/app_runner.rb +190 -0
- data/lib/rpush/daemon/batch.rb +138 -0
- data/lib/rpush/daemon/constants.rb +59 -0
- data/lib/rpush/daemon/delivery.rb +46 -0
- data/lib/rpush/daemon/delivery_error.rb +27 -0
- data/lib/rpush/daemon/dispatcher/apns_http2.rb +51 -0
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +152 -0
- data/lib/rpush/daemon/dispatcher/apnsp8_http2.rb +33 -0
- data/lib/rpush/daemon/dispatcher/http.rb +21 -0
- data/lib/rpush/daemon/dispatcher/tcp.rb +22 -0
- data/lib/rpush/daemon/dispatcher_loop.rb +73 -0
- data/lib/rpush/daemon/errors.rb +18 -0
- data/lib/rpush/daemon/feeder.rb +69 -0
- data/lib/rpush/daemon/gcm/delivery.rb +241 -0
- data/lib/rpush/daemon/gcm.rb +9 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +24 -0
- data/lib/rpush/daemon/loggable.rb +33 -0
- data/lib/rpush/daemon/proc_title.rb +17 -0
- data/lib/rpush/daemon/pushy/delivery.rb +90 -0
- data/lib/rpush/daemon/pushy.rb +9 -0
- data/lib/rpush/daemon/queue_payload.rb +12 -0
- data/lib/rpush/daemon/retry_header_parser.rb +23 -0
- data/lib/rpush/daemon/retryable_error.rb +22 -0
- data/lib/rpush/daemon/ring_buffer.rb +16 -0
- data/lib/rpush/daemon/rpc/client.rb +27 -0
- data/lib/rpush/daemon/rpc/server.rb +82 -0
- data/lib/rpush/daemon/rpc.rb +9 -0
- data/lib/rpush/daemon/service_config_methods.rb +51 -0
- data/lib/rpush/daemon/signal_handler.rb +75 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +80 -0
- data/lib/rpush/daemon/store/active_record.rb +214 -0
- data/lib/rpush/daemon/store/interface.rb +20 -0
- data/lib/rpush/daemon/store/redis.rb +166 -0
- data/lib/rpush/daemon/string_helpers.rb +15 -0
- data/lib/rpush/daemon/synchronizer.rb +62 -0
- data/lib/rpush/daemon/tcp_connection.rb +190 -0
- data/lib/rpush/daemon/wns/badge_request.rb +32 -0
- data/lib/rpush/daemon/wns/delivery.rb +178 -0
- data/lib/rpush/daemon/wns/post_request.rb +33 -0
- data/lib/rpush/daemon/wns/raw_request.rb +22 -0
- data/lib/rpush/daemon/wns/toast_request.rb +54 -0
- data/lib/rpush/daemon/wns.rb +9 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -0
- data/lib/rpush/daemon/wpns.rb +9 -0
- data/lib/rpush/daemon.rb +179 -0
- data/lib/rpush/deprecatable.rb +24 -0
- data/lib/rpush/deprecation.rb +26 -0
- data/lib/rpush/embed.rb +41 -0
- data/lib/rpush/logger.rb +92 -0
- data/lib/rpush/multi_json_helper.rb +16 -0
- data/lib/rpush/plugin.rb +44 -0
- data/lib/rpush/push.rb +11 -0
- data/lib/rpush/reflectable.rb +13 -0
- data/lib/rpush/reflection_collection.rb +44 -0
- data/lib/rpush/reflection_public_methods.rb +11 -0
- data/lib/rpush/version.rb +14 -0
- data/lib/rpush.rb +43 -0
- data/lib/tasks/quality.rake +35 -0
- data/lib/tasks/test.rake +69 -0
- data/spec/.rubocop.yml +4 -0
- data/spec/functional/adm_spec.rb +50 -0
- data/spec/functional/apns2_spec.rb +232 -0
- data/spec/functional/apns_spec.rb +162 -0
- data/spec/functional/cli_spec.rb +36 -0
- data/spec/functional/embed_spec.rb +49 -0
- data/spec/functional/gcm_spec.rb +46 -0
- data/spec/functional/new_app_spec.rb +44 -0
- data/spec/functional/pushy_spec.rb +22 -0
- data/spec/functional/retry_spec.rb +42 -0
- data/spec/functional/synchronization_spec.rb +97 -0
- data/spec/functional/wpns_spec.rb +71 -0
- data/spec/functional_spec_helper.rb +32 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/support/active_record_setup.rb +73 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/support/config/database.yml +44 -0
- data/spec/support/simplecov_helper.rb +24 -0
- data/spec/support/simplecov_quality_formatter.rb +12 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/unit/apns_feedback_spec.rb +28 -0
- data/spec/unit/client/active_record/adm/app_spec.rb +58 -0
- data/spec/unit/client/active_record/adm/notification_spec.rb +43 -0
- data/spec/unit/client/active_record/apns/app_spec.rb +29 -0
- data/spec/unit/client/active_record/apns/feedback_spec.rb +9 -0
- data/spec/unit/client/active_record/apns/notification_spec.rb +324 -0
- data/spec/unit/client/active_record/app_spec.rb +30 -0
- data/spec/unit/client/active_record/gcm/app_spec.rb +4 -0
- data/spec/unit/client/active_record/gcm/notification_spec.rb +67 -0
- data/spec/unit/client/active_record/notification_spec.rb +21 -0
- data/spec/unit/client/active_record/pushy/app_spec.rb +17 -0
- data/spec/unit/client/active_record/pushy/notification_spec.rb +65 -0
- data/spec/unit/client/active_record/wns/badge_notification_spec.rb +15 -0
- data/spec/unit/client/active_record/wns/raw_notification_spec.rb +26 -0
- data/spec/unit/client/active_record/wpns/app_spec.rb +4 -0
- data/spec/unit/client/active_record/wpns/notification_spec.rb +21 -0
- data/spec/unit/configuration_spec.rb +46 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +253 -0
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +108 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +119 -0
- data/spec/unit/daemon/app_runner_spec.rb +188 -0
- data/spec/unit/daemon/batch_spec.rb +169 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_spec.rb +51 -0
- data/spec/unit/daemon/dispatcher/http_spec.rb +34 -0
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +32 -0
- data/spec/unit/daemon/dispatcher_loop_spec.rb +53 -0
- data/spec/unit/daemon/feeder_spec.rb +96 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +387 -0
- data/spec/unit/daemon/proc_title_spec.rb +11 -0
- data/spec/unit/daemon/pushy/delivery_spec.rb +159 -0
- data/spec/unit/daemon/retryable_error_spec.rb +14 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +36 -0
- data/spec/unit/daemon/signal_handler_spec.rb +99 -0
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +165 -0
- data/spec/unit/daemon/store/active_record_spec.rb +357 -0
- data/spec/unit/daemon/store/redis_spec.rb +365 -0
- data/spec/unit/daemon/tcp_connection_spec.rb +292 -0
- data/spec/unit/daemon/wns/delivery_spec.rb +176 -0
- data/spec/unit/daemon/wns/post_request_spec.rb +117 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +167 -0
- data/spec/unit/daemon_spec.rb +138 -0
- data/spec/unit/deprecatable_spec.rb +32 -0
- data/spec/unit/deprecation_spec.rb +15 -0
- data/spec/unit/embed_spec.rb +47 -0
- data/spec/unit/logger_spec.rb +127 -0
- data/spec/unit/notification_shared.rb +53 -0
- data/spec/unit/plugin_spec.rb +36 -0
- data/spec/unit/push_spec.rb +34 -0
- data/spec/unit/reflectable_spec.rb +27 -0
- data/spec/unit/reflection_collection_spec.rb +26 -0
- data/spec/unit/rpush_spec.rb +8 -0
- data/spec/unit_spec_helper.rb +26 -0
- metadata +709 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
module Apnsp8
|
|
4
|
+
# https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
|
|
5
|
+
|
|
6
|
+
HTTP2_HEADERS_KEY = 'headers'
|
|
7
|
+
|
|
8
|
+
class Delivery < Rpush::Daemon::Delivery
|
|
9
|
+
RETRYABLE_CODES = [ 429, 500, 503 ]
|
|
10
|
+
|
|
11
|
+
def initialize(app, http2_client, token_provider, batch)
|
|
12
|
+
@app = app
|
|
13
|
+
@client = http2_client
|
|
14
|
+
@batch = batch
|
|
15
|
+
@first_push = true
|
|
16
|
+
@token_provider = token_provider
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def perform
|
|
20
|
+
@client.on(:error) { |err| mark_batch_retryable(Time.now + 10.seconds, err) }
|
|
21
|
+
|
|
22
|
+
@batch.each_notification do |notification|
|
|
23
|
+
prepare_async_post(notification)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Send all preprocessed requests at once
|
|
27
|
+
@client.join
|
|
28
|
+
rescue Errno::ECONNREFUSED, SocketError, HTTP2::Error::StreamLimitExceeded => error
|
|
29
|
+
# TODO restart connection when StreamLimitExceeded
|
|
30
|
+
mark_batch_retryable(Time.now + 10.seconds, error)
|
|
31
|
+
raise
|
|
32
|
+
rescue StandardError => error
|
|
33
|
+
mark_batch_failed(error)
|
|
34
|
+
raise
|
|
35
|
+
ensure
|
|
36
|
+
@batch.all_processed
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
protected
|
|
40
|
+
######################################################################
|
|
41
|
+
|
|
42
|
+
def prepare_async_post(notification)
|
|
43
|
+
response = {}
|
|
44
|
+
|
|
45
|
+
request = build_request(notification)
|
|
46
|
+
log_warn(request)
|
|
47
|
+
http_request = @client.prepare_request(:post, request[:path],
|
|
48
|
+
body: request[:body],
|
|
49
|
+
headers: request[:headers]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
http_request.on(:headers) do |hdrs|
|
|
53
|
+
response[:code] = hdrs[':status'].to_i
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
http_request.on(:body_chunk) do |body_chunk|
|
|
57
|
+
next unless body_chunk.present?
|
|
58
|
+
|
|
59
|
+
response[:failure_reason] = JSON.parse(body_chunk)['reason']
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
http_request.on(:close) { handle_response(notification, response) }
|
|
63
|
+
|
|
64
|
+
if @first_push
|
|
65
|
+
@first_push = false
|
|
66
|
+
@client.call_async(http_request)
|
|
67
|
+
else
|
|
68
|
+
delayed_push_async(http_request)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def delayed_push_async(http_request)
|
|
73
|
+
until streams_available? do
|
|
74
|
+
sleep 0.001
|
|
75
|
+
end
|
|
76
|
+
@client.call_async(http_request)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def streams_available?
|
|
80
|
+
remote_max_concurrent_streams - @client.stream_count > 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def remote_max_concurrent_streams
|
|
84
|
+
# 0x7fffffff is the default value from http-2 gem (2^31)
|
|
85
|
+
if @client.remote_settings[:settings_max_concurrent_streams] == 0x7fffffff
|
|
86
|
+
0
|
|
87
|
+
else
|
|
88
|
+
@client.remote_settings[:settings_max_concurrent_streams]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def handle_response(notification, response)
|
|
93
|
+
code = response[:code]
|
|
94
|
+
case code
|
|
95
|
+
when 200
|
|
96
|
+
ok(notification)
|
|
97
|
+
when *RETRYABLE_CODES
|
|
98
|
+
service_unavailable(notification, response)
|
|
99
|
+
else
|
|
100
|
+
reflect(:notification_id_failed,
|
|
101
|
+
@app,
|
|
102
|
+
notification.id, code,
|
|
103
|
+
response[:failure_reason])
|
|
104
|
+
@batch.mark_failed(notification, response[:code], response[:failure_reason])
|
|
105
|
+
failed_message_to_log(notification, response)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def ok(notification)
|
|
110
|
+
log_info("#{notification.id} sent to #{notification.device_token}")
|
|
111
|
+
@batch.mark_delivered(notification)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def service_unavailable(notification, response)
|
|
115
|
+
@batch.mark_retryable(notification, Time.now + 10.seconds)
|
|
116
|
+
# Logs should go last as soon as we need to initialize
|
|
117
|
+
# retry time to display it in log
|
|
118
|
+
failed_message_to_log(notification, response)
|
|
119
|
+
retry_message_to_log(notification)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_request(notification)
|
|
123
|
+
{
|
|
124
|
+
path: "/3/device/#{notification.device_token}",
|
|
125
|
+
headers: prepare_headers(notification),
|
|
126
|
+
body: prepare_body(notification)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def prepare_body(notification)
|
|
131
|
+
hash = notification.as_json.except(HTTP2_HEADERS_KEY)
|
|
132
|
+
JSON.dump(hash).force_encoding(Encoding::BINARY)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def prepare_headers(notification)
|
|
136
|
+
jwt_token = @token_provider.token
|
|
137
|
+
|
|
138
|
+
headers = {}
|
|
139
|
+
|
|
140
|
+
headers['content-type'] = 'application/json'
|
|
141
|
+
headers['apns-expiration'] = '0'
|
|
142
|
+
headers['apns-priority'] = '10'
|
|
143
|
+
headers['apns-topic'] = @app.bundle_id
|
|
144
|
+
headers['authorization'] = "bearer #{jwt_token}"
|
|
145
|
+
|
|
146
|
+
headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def notification_data(notification)
|
|
150
|
+
notification.data || {}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def retry_message_to_log(notification)
|
|
154
|
+
log_warn("Notification #{notification.id} will be retried after "\
|
|
155
|
+
"#{notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} "\
|
|
156
|
+
"(retry #{notification.retries}).")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def failed_message_to_log(notification, response)
|
|
160
|
+
log_error("Notification #{notification.id} failed, "\
|
|
161
|
+
"#{response[:code]}/#{response[:failure_reason]}")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
module Apnsp8
|
|
4
|
+
TOKEN_TTL = 30 * 60
|
|
5
|
+
class Token
|
|
6
|
+
def initialize(app)
|
|
7
|
+
@app = app
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def token
|
|
11
|
+
if @cached_token && !expired_token?
|
|
12
|
+
@cached_token
|
|
13
|
+
else
|
|
14
|
+
new_token
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def new_token
|
|
21
|
+
@cached_token_at = Time.now
|
|
22
|
+
ec_key = OpenSSL::PKey::EC.new(@app.apn_key)
|
|
23
|
+
@cached_token = JWT.encode(
|
|
24
|
+
{
|
|
25
|
+
iss: @app.team_id,
|
|
26
|
+
iat: Time.now.to_i
|
|
27
|
+
},
|
|
28
|
+
ec_key,
|
|
29
|
+
'ES256',
|
|
30
|
+
{
|
|
31
|
+
alg: 'ES256',
|
|
32
|
+
kid: @app.apn_key_id
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def expired_token?
|
|
38
|
+
Time.now - @cached_token_at >= TOKEN_TTL
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module Rpush
|
|
4
|
+
module Daemon
|
|
5
|
+
class AppRunner
|
|
6
|
+
extend Reflectable
|
|
7
|
+
include Reflectable
|
|
8
|
+
include Loggable
|
|
9
|
+
extend Loggable
|
|
10
|
+
include StringHelpers
|
|
11
|
+
extend StringHelpers
|
|
12
|
+
|
|
13
|
+
@runners = {}
|
|
14
|
+
|
|
15
|
+
def self.enqueue(notifications)
|
|
16
|
+
notifications.group_by(&:app_id).each do |app_id, group|
|
|
17
|
+
start_app_with_id(app_id) unless @runners[app_id]
|
|
18
|
+
@runners[app_id].enqueue(group) if @runners[app_id]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
ProcTitle.update
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.start_app_with_id(app_id)
|
|
25
|
+
start_app(Rpush::Daemon.store.app(app_id))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.start_app(app)
|
|
29
|
+
Rpush.logger.info("[#{app.name}] Starting #{pluralize(app.connections, 'dispatcher')}... ", true)
|
|
30
|
+
runner = @runners[app.id] = new(app)
|
|
31
|
+
runner.start_dispatchers
|
|
32
|
+
puts ANSI.green { '✔' } if Rpush.config.foreground
|
|
33
|
+
runner.start_loops
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
@runners.delete(app.id)
|
|
36
|
+
Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
|
|
37
|
+
Rpush.logger.error(e)
|
|
38
|
+
reflect(:error, e)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.stop_app(app_id)
|
|
42
|
+
runner = @runners.delete(app_id)
|
|
43
|
+
if runner
|
|
44
|
+
runner.stop
|
|
45
|
+
log_info("[#{runner.app.name}] Stopped.")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.app_with_id(app_id)
|
|
50
|
+
@runners[app_id].app
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.app_running?(app)
|
|
54
|
+
@runners.key?(app.id)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.app_ids
|
|
58
|
+
@runners.keys
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.stop
|
|
62
|
+
@runners.values.map(&:stop)
|
|
63
|
+
@runners.clear
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.total_dispatchers
|
|
67
|
+
@runners.values.sum(&:num_dispatcher_loops)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.total_queued
|
|
71
|
+
@runners.values.sum(&:queue_size)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.num_dispatchers_for_app(app)
|
|
75
|
+
runner = @runners[app.id]
|
|
76
|
+
runner ? runner.num_dispatcher_loops : 0
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.decrement_dispatchers(app, num)
|
|
80
|
+
@runners[app.id].decrement_dispatchers(num)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.increment_dispatchers(app, num)
|
|
84
|
+
@runners[app.id].increment_dispatchers(num)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.status
|
|
88
|
+
{ app_runners: @runners.values.map(&:status) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
attr_reader :app
|
|
92
|
+
delegate :size, to: :queue, prefix: true
|
|
93
|
+
|
|
94
|
+
def initialize(app)
|
|
95
|
+
@app = app
|
|
96
|
+
@loops = []
|
|
97
|
+
@dispatcher_loops = []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def start_dispatchers
|
|
101
|
+
app.connections.times { @dispatcher_loops.push(new_dispatcher_loop) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def start_loops
|
|
105
|
+
@loops = service.loop_instances(@app)
|
|
106
|
+
@loops.map(&:start)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def stop
|
|
110
|
+
wait_until_idle
|
|
111
|
+
stop_dispatcher_loops
|
|
112
|
+
stop_loops
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def wait_until_idle
|
|
116
|
+
sleep 0.5 while queue.size > 0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def enqueue(notifications)
|
|
120
|
+
if service.batch_deliveries?
|
|
121
|
+
batch_size = (notifications.size / num_dispatcher_loops.to_f).ceil
|
|
122
|
+
notifications.in_groups_of(batch_size, false).each do |batch_notifications|
|
|
123
|
+
batch = Batch.new(batch_notifications)
|
|
124
|
+
queue.push(QueuePayload.new(batch))
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
batch = Batch.new(notifications)
|
|
128
|
+
notifications.each do |notification|
|
|
129
|
+
queue.push(QueuePayload.new(batch, notification))
|
|
130
|
+
reflect(:notification_enqueued, notification)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def decrement_dispatchers(num)
|
|
136
|
+
num.times { @dispatcher_loops.pop.stop }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def increment_dispatchers(num)
|
|
140
|
+
num.times { @dispatcher_loops.push(new_dispatcher_loop) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def status
|
|
144
|
+
dispatcher_details = {}
|
|
145
|
+
|
|
146
|
+
@dispatcher_loops.each_with_index do |dispatcher_loop, i|
|
|
147
|
+
dispatcher_details[i] = {
|
|
148
|
+
started_at: dispatcher_loop.started_at.iso8601,
|
|
149
|
+
dispatched: dispatcher_loop.dispatch_count,
|
|
150
|
+
thread_status: dispatcher_loop.thread_status
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
{ app_name: @app.name, dispatchers: dispatcher_details, queued: queue_size }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def num_dispatcher_loops
|
|
158
|
+
@dispatcher_loops.size
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def stop_loops
|
|
164
|
+
@loops.map(&:stop)
|
|
165
|
+
@loops = []
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def stop_dispatcher_loops
|
|
169
|
+
@dispatcher_loops.map(&:stop)
|
|
170
|
+
@dispatcher_loops.clear
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def new_dispatcher_loop
|
|
174
|
+
dispatcher = service.new_dispatcher(@app)
|
|
175
|
+
dispatcher_loop = Rpush::Daemon::DispatcherLoop.new(queue, dispatcher)
|
|
176
|
+
dispatcher_loop.start
|
|
177
|
+
dispatcher_loop
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def service
|
|
181
|
+
return @service if defined? @service
|
|
182
|
+
@service = "Rpush::Daemon::#{@app.service_name.camelize}".constantize
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def queue
|
|
186
|
+
@queue ||= Queue.new
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
class Batch
|
|
4
|
+
include Reflectable
|
|
5
|
+
|
|
6
|
+
attr_reader :num_processed, :notifications, :delivered, :failed, :retryable
|
|
7
|
+
|
|
8
|
+
def initialize(notifications)
|
|
9
|
+
@notifications = notifications
|
|
10
|
+
@num_processed = 0
|
|
11
|
+
@delivered = []
|
|
12
|
+
@failed = {}
|
|
13
|
+
@retryable = {}
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def complete?
|
|
18
|
+
@complete == true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def each_notification(&blk)
|
|
22
|
+
@notifications.each(&blk)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def each_delivered(&blk)
|
|
26
|
+
@delivered.each(&blk)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def mark_retryable(notification, deliver_after)
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
@retryable[deliver_after] ||= []
|
|
32
|
+
@retryable[deliver_after] << notification
|
|
33
|
+
end
|
|
34
|
+
Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mark_all_retryable(deliver_after)
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
@retryable[deliver_after] = @notifications
|
|
40
|
+
end
|
|
41
|
+
each_notification do |notification|
|
|
42
|
+
Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def mark_delivered(notification)
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
@delivered << notification
|
|
49
|
+
end
|
|
50
|
+
Rpush::Daemon.store.mark_delivered(notification, Time.now, persist: false)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def mark_all_delivered
|
|
54
|
+
@mutex.synchronize do
|
|
55
|
+
@delivered = @notifications
|
|
56
|
+
end
|
|
57
|
+
each_notification do |notification|
|
|
58
|
+
Rpush::Daemon.store.mark_delivered(notification, Time.now, persist: false)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def mark_failed(notification, code, description)
|
|
63
|
+
key = [code, description]
|
|
64
|
+
@mutex.synchronize do
|
|
65
|
+
@failed[key] ||= []
|
|
66
|
+
@failed[key] << notification
|
|
67
|
+
end
|
|
68
|
+
Rpush::Daemon.store.mark_failed(notification, code, description, Time.now, persist: false)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def mark_all_failed(code, message)
|
|
72
|
+
key = [code, message]
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
@failed[key] = @notifications
|
|
75
|
+
end
|
|
76
|
+
each_notification do |notification|
|
|
77
|
+
Rpush::Daemon.store.mark_failed(notification, code, message, Time.now, persist: false)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def notification_processed
|
|
82
|
+
@mutex.synchronize do
|
|
83
|
+
@num_processed += 1
|
|
84
|
+
complete if @num_processed >= @notifications.size
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def all_processed
|
|
89
|
+
@mutex.synchronize do
|
|
90
|
+
@num_processed = @notifications.size
|
|
91
|
+
complete
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def complete
|
|
98
|
+
return if complete?
|
|
99
|
+
|
|
100
|
+
[:complete_delivered, :complete_failed, :complete_retried].each do |method|
|
|
101
|
+
begin
|
|
102
|
+
send(method)
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
Rpush.logger.error(e)
|
|
105
|
+
reflect(:error, e)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@complete = true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def complete_delivered
|
|
113
|
+
Rpush::Daemon.store.mark_batch_delivered(@delivered)
|
|
114
|
+
@delivered.each do |notification|
|
|
115
|
+
reflect(:notification_delivered, notification)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def complete_failed
|
|
120
|
+
@failed.each do |(code, description), notifications|
|
|
121
|
+
Rpush::Daemon.store.mark_batch_failed(notifications, code, description)
|
|
122
|
+
notifications.each do |notification|
|
|
123
|
+
reflect(:notification_failed, notification)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def complete_retried
|
|
129
|
+
@retryable.each do |deliver_after, notifications|
|
|
130
|
+
Rpush::Daemon.store.mark_batch_retryable(notifications, deliver_after)
|
|
131
|
+
notifications.each do |notification|
|
|
132
|
+
reflect(:notification_will_retry, notification)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
HTTP_STATUS_CODES = {
|
|
4
|
+
100 => 'Continue',
|
|
5
|
+
101 => 'Switching Protocols',
|
|
6
|
+
102 => 'Processing',
|
|
7
|
+
200 => 'OK',
|
|
8
|
+
201 => 'Created',
|
|
9
|
+
202 => 'Accepted',
|
|
10
|
+
203 => 'Non-Authoritative Information',
|
|
11
|
+
204 => 'No Content',
|
|
12
|
+
205 => 'Reset Content',
|
|
13
|
+
206 => 'Partial Content',
|
|
14
|
+
207 => 'Multi-Status',
|
|
15
|
+
226 => 'IM Used',
|
|
16
|
+
300 => 'Multiple Choices',
|
|
17
|
+
301 => 'Moved Permanently',
|
|
18
|
+
302 => 'Found',
|
|
19
|
+
303 => 'See Other',
|
|
20
|
+
304 => 'Not Modified',
|
|
21
|
+
305 => 'Use Proxy',
|
|
22
|
+
306 => 'Reserved',
|
|
23
|
+
307 => 'Temporary Redirect',
|
|
24
|
+
400 => 'Bad Request',
|
|
25
|
+
401 => 'Unauthorized',
|
|
26
|
+
402 => 'Payment Required',
|
|
27
|
+
403 => 'Forbidden',
|
|
28
|
+
404 => 'Not Found',
|
|
29
|
+
405 => 'Method Not Allowed',
|
|
30
|
+
406 => 'Not Acceptable',
|
|
31
|
+
407 => 'Proxy Authentication Required',
|
|
32
|
+
408 => 'Request Timeout',
|
|
33
|
+
409 => 'Conflict',
|
|
34
|
+
410 => 'Gone',
|
|
35
|
+
411 => 'Length Required',
|
|
36
|
+
412 => 'Precondition Failed',
|
|
37
|
+
413 => 'Request Entity Too Large',
|
|
38
|
+
414 => 'Request-URI Too Long',
|
|
39
|
+
415 => 'Unsupported Media Type',
|
|
40
|
+
416 => 'Requested Range Not Satisfiable',
|
|
41
|
+
417 => 'Expectation Failed',
|
|
42
|
+
418 => "I'm a Teapot",
|
|
43
|
+
422 => 'Unprocessable Entity',
|
|
44
|
+
423 => 'Locked',
|
|
45
|
+
424 => 'Failed Dependency',
|
|
46
|
+
426 => 'Upgrade Required',
|
|
47
|
+
429 => 'Too Many Requests',
|
|
48
|
+
500 => 'Internal Server Error',
|
|
49
|
+
501 => 'Not Implemented',
|
|
50
|
+
502 => 'Bad Gateway',
|
|
51
|
+
503 => 'Service Unavailable',
|
|
52
|
+
504 => 'Gateway Timeout',
|
|
53
|
+
505 => 'HTTP Version Not Supported',
|
|
54
|
+
506 => 'Variant Also Negotiates',
|
|
55
|
+
507 => 'Insufficient Storage',
|
|
56
|
+
510 => 'Not Extended'
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
class Delivery
|
|
4
|
+
include Reflectable
|
|
5
|
+
include Loggable
|
|
6
|
+
|
|
7
|
+
def mark_retryable(notification, deliver_after, error = nil)
|
|
8
|
+
if notification.fail_after && notification.fail_after < Time.now
|
|
9
|
+
@batch.mark_failed(notification, nil, "Notification failed to be delivered before #{notification.fail_after.strftime('%Y-%m-%d %H:%M:%S')}.")
|
|
10
|
+
else
|
|
11
|
+
if error
|
|
12
|
+
log_warn("Will retry notification #{notification.id} after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
|
|
13
|
+
end
|
|
14
|
+
@batch.mark_retryable(notification, deliver_after)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def mark_retryable_exponential(notification)
|
|
19
|
+
mark_retryable(notification, Time.now + 2**(notification.retries + 1))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def mark_batch_retryable(deliver_after, error)
|
|
23
|
+
log_warn("Will retry #{@batch.notifications.size} notifications after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
|
|
24
|
+
@batch.mark_all_retryable(deliver_after)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def mark_delivered
|
|
28
|
+
@batch.mark_delivered(@notification)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def mark_batch_delivered
|
|
32
|
+
@batch.mark_all_delivered
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mark_failed(error)
|
|
36
|
+
code = error.respond_to?(:code) ? error.code : nil
|
|
37
|
+
@batch.mark_failed(@notification, code, error.to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def mark_batch_failed(error)
|
|
41
|
+
code = error.respond_to?(:code) ? error.code : nil
|
|
42
|
+
@batch.mark_all_failed(code, error.to_s)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
class DeliveryError < StandardError
|
|
3
|
+
attr_reader :code, :notification_id
|
|
4
|
+
|
|
5
|
+
def initialize(code, notification_id, description)
|
|
6
|
+
@code = code
|
|
7
|
+
@notification_id = notification_id
|
|
8
|
+
@description = description
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
message
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
error_str = [@code, "(#{@description})"].compact.join(' ')
|
|
17
|
+
"Unable to deliver notification #{@notification_id}, received error #{error_str}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ==(other)
|
|
21
|
+
other.is_a?(DeliveryError) && \
|
|
22
|
+
other.code == code && \
|
|
23
|
+
other.notification_id == notification_id && \
|
|
24
|
+
other.to_s == to_s
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|