rpush_extended 3.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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,178 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Wns
|
4
|
+
# https://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx
|
5
|
+
class Delivery < Rpush::Daemon::Delivery
|
6
|
+
# Oauth2.0 token endpoint. This endpoint is used to request authorization tokens.
|
7
|
+
WPN_TOKEN_URI = URI.parse('https://login.live.com/accesstoken.srf')
|
8
|
+
|
9
|
+
# Data used to request authorization tokens.
|
10
|
+
ACCESS_TOKEN_REQUEST_DATA = { "grant_type" => "client_credentials", "scope" => "notify.windows.com" }
|
11
|
+
|
12
|
+
MAX_RETRIES = 14
|
13
|
+
|
14
|
+
FAILURE_MESSAGES = {
|
15
|
+
400 => 'One or more headers were specified incorrectly or conflict with another header.',
|
16
|
+
401 => 'The cloud service did not present a valid authentication ticket. The OAuth ticket may be invalid.',
|
17
|
+
403 => 'The cloud service is not authorized to send a notification to this URI even though they are authenticated.',
|
18
|
+
404 => 'The channel URI is not valid or is not recognized by WNS.',
|
19
|
+
405 => 'Invalid method (GET, CREATE); only POST (Windows or Windows Phone) or DELETE (Windows Phone only) is allowed.',
|
20
|
+
406 => 'The cloud service exceeded its throttle limit.',
|
21
|
+
410 => 'The channel expired.',
|
22
|
+
413 => 'The notification payload exceeds the 5000 byte size limit.',
|
23
|
+
500 => 'An internal failure caused notification delivery to fail.',
|
24
|
+
503 => 'The server is currently unavailable.'
|
25
|
+
}
|
26
|
+
|
27
|
+
def initialize(app, http, notification, batch)
|
28
|
+
@app = app
|
29
|
+
@http = http
|
30
|
+
@notification = notification
|
31
|
+
@batch = batch
|
32
|
+
end
|
33
|
+
|
34
|
+
def perform
|
35
|
+
handle_response(do_post)
|
36
|
+
rescue SocketError => error
|
37
|
+
mark_retryable(@notification, Time.now + 10.seconds, error)
|
38
|
+
raise
|
39
|
+
rescue StandardError => error
|
40
|
+
mark_failed(error)
|
41
|
+
raise
|
42
|
+
ensure
|
43
|
+
@batch.notification_processed
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def handle_response(response)
|
49
|
+
code = response.code.to_i
|
50
|
+
case code
|
51
|
+
when 200
|
52
|
+
ok(response)
|
53
|
+
when 401
|
54
|
+
unauthorized
|
55
|
+
when 404
|
56
|
+
invalid_channel(code)
|
57
|
+
when 406
|
58
|
+
not_acceptable
|
59
|
+
when 410
|
60
|
+
invalid_channel(code)
|
61
|
+
when 412
|
62
|
+
precondition_failed
|
63
|
+
when 503
|
64
|
+
service_unavailable
|
65
|
+
else
|
66
|
+
handle_failure(code)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_failure(code, msg = nil)
|
71
|
+
unless msg
|
72
|
+
msg = FAILURE_MESSAGES.key?(code) ? FAILURE_MESSAGES[code] : Rpush::Daemon::HTTP_STATUS_CODES[code]
|
73
|
+
end
|
74
|
+
fail Rpush::DeliveryError.new(code, @notification.id, msg)
|
75
|
+
end
|
76
|
+
|
77
|
+
def ok(response)
|
78
|
+
status = status_from_response(response)
|
79
|
+
case status[:notification]
|
80
|
+
when ["received"]
|
81
|
+
mark_delivered
|
82
|
+
log_info("#{@notification.id} sent successfully")
|
83
|
+
when ["channelthrottled"]
|
84
|
+
mark_retryable(@notification, Time.now + (60 * 10))
|
85
|
+
log_warn("#{@notification.id} cannot be sent. The Queue is full.")
|
86
|
+
when ["dropped"]
|
87
|
+
log_error("#{@notification.id} was dropped. Headers: #{status}")
|
88
|
+
handle_failure(200, "Notification was received but suppressed by the service (#{status[:error_description]}).")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def unauthorized
|
93
|
+
@notification.app.access_token = nil
|
94
|
+
Rpush::Daemon.store.update_app(@notification.app)
|
95
|
+
if @notification.retries < MAX_RETRIES
|
96
|
+
retry_notification("Token invalid.")
|
97
|
+
else
|
98
|
+
msg = "Notification failed to be delivered in #{MAX_RETRIES} retries."
|
99
|
+
mark_failed(Rpush::DeliveryError.new(nil, @notification.id, msg))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def invalid_channel(code, msg = nil)
|
104
|
+
unless msg
|
105
|
+
msg = FAILURE_MESSAGES.key?(code) ? FAILURE_MESSAGES[code] : Rpush::Daemon::HTTP_STATUS_CODES[code]
|
106
|
+
end
|
107
|
+
reflect(:wns_invalid_channel, @notification, @notification.uri, "#{code}. #{msg}")
|
108
|
+
handle_failure(code, msg)
|
109
|
+
end
|
110
|
+
|
111
|
+
def not_acceptable
|
112
|
+
retry_notification("Per-day throttling limit reached.")
|
113
|
+
end
|
114
|
+
|
115
|
+
def precondition_failed
|
116
|
+
retry_notification("Device unreachable.")
|
117
|
+
end
|
118
|
+
|
119
|
+
def service_unavailable
|
120
|
+
mark_retryable_exponential(@notification)
|
121
|
+
log_warn("Service Unavailable. " + retry_message)
|
122
|
+
end
|
123
|
+
|
124
|
+
def retry_message
|
125
|
+
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
|
126
|
+
end
|
127
|
+
|
128
|
+
def retry_notification(reason)
|
129
|
+
deliver_after = Time.now + (60 * 60)
|
130
|
+
mark_retryable(@notification, deliver_after)
|
131
|
+
log_warn("#{reason} " + retry_message)
|
132
|
+
end
|
133
|
+
|
134
|
+
def do_post
|
135
|
+
post = PostRequest.create(@notification, access_token)
|
136
|
+
@http.request(URI.parse(@notification.uri), post)
|
137
|
+
end
|
138
|
+
|
139
|
+
def status_from_response(response)
|
140
|
+
headers = response.to_hash.each_with_object({}) { |e, a| a[e[0].downcase] = e[1] }
|
141
|
+
{
|
142
|
+
notification: headers["x-wns-status"],
|
143
|
+
device_connection: headers["x-wns-deviceconnectionstatus"],
|
144
|
+
msg_id: headers["x-wns-msg-id"],
|
145
|
+
error_description: headers["x-wns-error-description"],
|
146
|
+
debug_trace: headers["x-wns-debug-trace"]
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def access_token
|
151
|
+
if @notification.app.access_token.nil? || @notification.app.access_token_expired?
|
152
|
+
post = Net::HTTP::Post.new(WPN_TOKEN_URI.path, 'Content-Type' => 'application/x-www-form-urlencoded')
|
153
|
+
post.set_form_data(ACCESS_TOKEN_REQUEST_DATA.merge('client_id' => @notification.app.client_id, 'client_secret' => @notification.app.client_secret))
|
154
|
+
|
155
|
+
handle_access_token(@http.request(WPN_TOKEN_URI, post))
|
156
|
+
end
|
157
|
+
|
158
|
+
@notification.app.access_token
|
159
|
+
end
|
160
|
+
|
161
|
+
def handle_access_token(response)
|
162
|
+
if response.code.to_i == 200
|
163
|
+
update_access_token(JSON.parse(response.body))
|
164
|
+
Rpush::Daemon.store.update_app(@notification.app)
|
165
|
+
log_info("WNS access token updated: token = #{@notification.app.access_token}, expires = #{@notification.app.access_token_expiration}")
|
166
|
+
else
|
167
|
+
log_warn("Could not retrieve access token from WNS: #{response.body}")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def update_access_token(data)
|
172
|
+
@notification.app.access_token = data['access_token']
|
173
|
+
@notification.app.access_token_expiration = Time.now + data['expires_in'].to_i
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Wns
|
4
|
+
class PostRequest
|
5
|
+
def self.create(notification, access_token)
|
6
|
+
stringify_keys(notification.data) unless notification.data.nil?
|
7
|
+
|
8
|
+
if raw_notification?(notification)
|
9
|
+
RawRequest.create(notification, access_token)
|
10
|
+
elsif badge_notification?(notification)
|
11
|
+
BadgeRequest.create(notification, access_token)
|
12
|
+
else
|
13
|
+
ToastRequest.create(notification, access_token)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private_class_method
|
18
|
+
|
19
|
+
def self.raw_notification?(notification)
|
20
|
+
notification.class.name.match(/RawNotification/)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.badge_notification?(notification)
|
24
|
+
notification.class.name.match(/BadgeNotification/)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.stringify_keys(data)
|
28
|
+
data.keys.each { |key| data[key.to_s || key] = data.delete(key) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Wns
|
4
|
+
class RawRequest
|
5
|
+
def self.create(notification, access_token)
|
6
|
+
body = notification.data.to_json
|
7
|
+
uri = URI.parse(notification.uri)
|
8
|
+
post = Net::HTTP::Post.new(
|
9
|
+
uri.request_uri,
|
10
|
+
"Content-Length" => body.length.to_s,
|
11
|
+
"Content-Type" => "application/octet-stream",
|
12
|
+
"X-WNS-Type" => "wns/raw",
|
13
|
+
"X-WNS-RequestForStatus" => "true",
|
14
|
+
"Authorization" => "Bearer #{access_token}"
|
15
|
+
)
|
16
|
+
post.body = body
|
17
|
+
post
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Wns
|
4
|
+
class ToastRequest
|
5
|
+
def self.create(notification, access_token)
|
6
|
+
body = ToastRequestPayload.new(notification).to_xml
|
7
|
+
uri = URI.parse(notification.uri)
|
8
|
+
post = Net::HTTP::Post.new(
|
9
|
+
uri.request_uri,
|
10
|
+
"Content-Length" => body.length.to_s,
|
11
|
+
"Content-Type" => "text/xml",
|
12
|
+
"X-WNS-Type" => "wns/toast",
|
13
|
+
"X-WNS-RequestForStatus" => "true",
|
14
|
+
"Authorization" => "Bearer #{access_token}"
|
15
|
+
)
|
16
|
+
post.body = body
|
17
|
+
post
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ToastRequestPayload
|
22
|
+
def initialize(notification)
|
23
|
+
@title = notification.data['title'] || ''
|
24
|
+
@body = notification.data['body'] || ''
|
25
|
+
@launch = notification.data['launch']
|
26
|
+
@sound = notification.sound unless notification.sound.eql?("default".freeze)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_xml
|
30
|
+
launch_string = "" unless @launch
|
31
|
+
launch_string = " launch='#{CleanParamString.clean(@launch)}'" if @launch
|
32
|
+
audio_string = "" unless @sound
|
33
|
+
audio_string = "<audio src='#{CleanParamString.clean(@sound)}'/>" if @sound
|
34
|
+
"<toast#{launch_string}>
|
35
|
+
<visual version='1' lang='en-US'>
|
36
|
+
<binding template='ToastText02'>
|
37
|
+
<text id='1'>#{CleanParamString.clean(@title)}</text>
|
38
|
+
<text id='2'>#{CleanParamString.clean(@body)}</text>
|
39
|
+
</binding>
|
40
|
+
</visual>
|
41
|
+
#{audio_string}
|
42
|
+
</toast>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class CleanParamString
|
47
|
+
def self.clean(string)
|
48
|
+
string.gsub(/&/, "&").gsub(/</, "<") \
|
49
|
+
.gsub(/>/, ">").gsub(/'/, "'").gsub(/"/, """)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Wpns
|
4
|
+
# http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff941100%28v=vs.105%29.aspx
|
5
|
+
class Delivery < Rpush::Daemon::Delivery
|
6
|
+
FAILURE_MESSAGES = {
|
7
|
+
400 => 'Bad XML or malformed notification URI.',
|
8
|
+
401 => 'Unauthorized to send a notification to this app.'
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(app, http, notification, batch)
|
12
|
+
@app = app
|
13
|
+
@http = http
|
14
|
+
@notification = notification
|
15
|
+
@batch = batch
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform
|
19
|
+
handle_response(do_post)
|
20
|
+
rescue SocketError => error
|
21
|
+
mark_retryable(@notification, Time.now + 10.seconds, error)
|
22
|
+
raise
|
23
|
+
rescue StandardError => error
|
24
|
+
mark_failed(error)
|
25
|
+
raise
|
26
|
+
ensure
|
27
|
+
@batch.notification_processed
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def handle_response(response)
|
33
|
+
code = response.code.to_i
|
34
|
+
case code
|
35
|
+
when 200
|
36
|
+
ok(response)
|
37
|
+
when 406
|
38
|
+
not_acceptable
|
39
|
+
when 412
|
40
|
+
precondition_failed
|
41
|
+
when 503
|
42
|
+
service_unavailable
|
43
|
+
else
|
44
|
+
handle_failure(code)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_failure(code, msg = nil)
|
49
|
+
unless msg
|
50
|
+
msg = FAILURE_MESSAGES.key?(code) ? FAILURE_MESSAGES[code] : Rpush::Daemon::HTTP_STATUS_CODES[code]
|
51
|
+
end
|
52
|
+
fail Rpush::DeliveryError.new(code, @notification.id, msg)
|
53
|
+
end
|
54
|
+
|
55
|
+
def ok(response)
|
56
|
+
status = status_from_response(response)
|
57
|
+
case status[:notification]
|
58
|
+
when ["Received"]
|
59
|
+
mark_delivered
|
60
|
+
log_info("#{@notification.id} sent successfully")
|
61
|
+
when ["QueueFull"]
|
62
|
+
mark_retryable(@notification, Time.now + (60 * 10))
|
63
|
+
log_warn("#{@notification.id} cannot be sent. The Queue is full.")
|
64
|
+
when ["Suppressed"]
|
65
|
+
handle_failure(200, "Notification was received but suppressed by the service.")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def not_acceptable
|
70
|
+
retry_notification("Per-day throttling limit reached.")
|
71
|
+
end
|
72
|
+
|
73
|
+
def precondition_failed
|
74
|
+
retry_notification("Device unreachable.")
|
75
|
+
end
|
76
|
+
|
77
|
+
def service_unavailable
|
78
|
+
mark_retryable_exponential(@notification)
|
79
|
+
log_warn("Service Unavailable. " + retry_message)
|
80
|
+
end
|
81
|
+
|
82
|
+
def retry_message
|
83
|
+
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
|
84
|
+
end
|
85
|
+
|
86
|
+
def retry_notification(reason)
|
87
|
+
deliver_after = Time.now + (60 * 60)
|
88
|
+
mark_retryable(@notification, deliver_after)
|
89
|
+
log_warn("#{reason} " + retry_message)
|
90
|
+
end
|
91
|
+
|
92
|
+
def do_post
|
93
|
+
body = notification_to_xml
|
94
|
+
post = Net::HTTP::Post.new(URI.parse(@notification.uri).path, "Content-Length" => body.length.to_s,
|
95
|
+
"Content-Type" => "text/xml",
|
96
|
+
"X-WindowsPhone-Target" => "toast",
|
97
|
+
"X-NotificationClass" => '2')
|
98
|
+
post.body = body
|
99
|
+
@http.request(URI.parse(@notification.uri), post)
|
100
|
+
end
|
101
|
+
|
102
|
+
def status_from_response(response)
|
103
|
+
headers = response.to_hash
|
104
|
+
{
|
105
|
+
notification: headers["x-notificationstatus"],
|
106
|
+
notification_channel: headers["x-subscriptionstatus"],
|
107
|
+
device_connection: headers["x-deviceconnectionstatus"]
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def notification_to_xml
|
112
|
+
title = clean_param_string(@notification.data['title']) if @notification.data['title'].present?
|
113
|
+
body = clean_param_string(@notification.data['body']) if @notification.data['body'].present?
|
114
|
+
param = clean_param_string(@notification.data['param']) if @notification.data['param'].present?
|
115
|
+
"<?xml version=\"1.0\" encoding=\"utf-8\"?>
|
116
|
+
<wp:Notification xmlns:wp=\"WPNotification\">
|
117
|
+
<wp:Toast>
|
118
|
+
<wp:Text1>#{title}</wp:Text1>
|
119
|
+
<wp:Text2>#{body}</wp:Text2>
|
120
|
+
<wp:Param>#{param}</wp:Param>
|
121
|
+
</wp:Toast>
|
122
|
+
</wp:Notification>"
|
123
|
+
end
|
124
|
+
|
125
|
+
def clean_param_string(string)
|
126
|
+
string.gsub(/&/, "&").gsub(/</, "<") \
|
127
|
+
.gsub(/>/, ">").gsub(/'/, "'").gsub(/"/, """)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/rpush/daemon.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'socket'
|
5
|
+
require 'pathname'
|
6
|
+
require 'openssl'
|
7
|
+
require 'net/http/persistent'
|
8
|
+
|
9
|
+
require 'rpush/daemon/errors'
|
10
|
+
require 'rpush/daemon/constants'
|
11
|
+
require 'rpush/daemon/loggable'
|
12
|
+
require 'rpush/daemon/string_helpers'
|
13
|
+
require 'rpush/daemon/interruptible_sleep'
|
14
|
+
require 'rpush/daemon/delivery_error'
|
15
|
+
require 'rpush/daemon/retryable_error'
|
16
|
+
require 'rpush/daemon/delivery'
|
17
|
+
require 'rpush/daemon/feeder'
|
18
|
+
require 'rpush/daemon/batch'
|
19
|
+
require 'rpush/daemon/queue_payload'
|
20
|
+
require 'rpush/daemon/synchronizer'
|
21
|
+
require 'rpush/daemon/app_runner'
|
22
|
+
require 'rpush/daemon/tcp_connection'
|
23
|
+
require 'rpush/daemon/dispatcher_loop'
|
24
|
+
require 'rpush/daemon/dispatcher/http'
|
25
|
+
require 'rpush/daemon/dispatcher/tcp'
|
26
|
+
require 'rpush/daemon/dispatcher/apns_tcp'
|
27
|
+
require 'rpush/daemon/dispatcher/apns_http2'
|
28
|
+
require 'rpush/daemon/dispatcher/apnsp8_http2'
|
29
|
+
require 'rpush/daemon/service_config_methods'
|
30
|
+
require 'rpush/daemon/retry_header_parser'
|
31
|
+
require 'rpush/daemon/ring_buffer'
|
32
|
+
require 'rpush/daemon/signal_handler'
|
33
|
+
require 'rpush/daemon/proc_title'
|
34
|
+
|
35
|
+
require 'rpush/daemon/rpc'
|
36
|
+
require 'rpush/daemon/rpc/server'
|
37
|
+
require 'rpush/daemon/rpc/client'
|
38
|
+
|
39
|
+
require 'rpush/daemon/store/interface'
|
40
|
+
|
41
|
+
require 'rpush/daemon/apns/delivery'
|
42
|
+
require 'rpush/daemon/apns/feedback_receiver'
|
43
|
+
require 'rpush/daemon/apns'
|
44
|
+
|
45
|
+
require 'rpush/daemon/apns2/delivery'
|
46
|
+
require 'rpush/daemon/apns2'
|
47
|
+
|
48
|
+
require 'rpush/daemon/apnsp8/delivery'
|
49
|
+
require 'rpush/daemon/apnsp8/token'
|
50
|
+
require 'rpush/daemon/apnsp8'
|
51
|
+
|
52
|
+
require 'rpush/daemon/gcm/delivery'
|
53
|
+
require 'rpush/daemon/gcm'
|
54
|
+
|
55
|
+
require 'rpush/daemon/wpns/delivery'
|
56
|
+
require 'rpush/daemon/wpns'
|
57
|
+
|
58
|
+
require 'rpush/daemon/wns/post_request'
|
59
|
+
require 'rpush/daemon/wns/raw_request'
|
60
|
+
require 'rpush/daemon/wns/toast_request'
|
61
|
+
require 'rpush/daemon/wns/badge_request'
|
62
|
+
require 'rpush/daemon/wns/delivery'
|
63
|
+
require 'rpush/daemon/wns'
|
64
|
+
|
65
|
+
require 'rpush/daemon/adm/delivery'
|
66
|
+
require 'rpush/daemon/adm'
|
67
|
+
|
68
|
+
require 'rpush/daemon/pushy'
|
69
|
+
require 'rpush/daemon/pushy/delivery'
|
70
|
+
|
71
|
+
module Rpush
|
72
|
+
module Daemon
|
73
|
+
class << self
|
74
|
+
attr_accessor :store
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.start
|
78
|
+
Process.daemon if daemonize?
|
79
|
+
write_pid_file
|
80
|
+
SignalHandler.start
|
81
|
+
common_init
|
82
|
+
Synchronizer.sync
|
83
|
+
Rpc::Server.start
|
84
|
+
|
85
|
+
# No further store connections will be made from this thread.
|
86
|
+
store.release_connection
|
87
|
+
|
88
|
+
Rpush.logger.info('Rpush operational.')
|
89
|
+
show_welcome_if_needed
|
90
|
+
|
91
|
+
# Blocking call, returns after Feeder.stop is called from another thread.
|
92
|
+
Feeder.start
|
93
|
+
|
94
|
+
# Wait for shutdown to complete.
|
95
|
+
shutdown_lock.synchronize { true }
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.shutdown
|
99
|
+
if Rpush.config.foreground
|
100
|
+
# Eat the '^C'
|
101
|
+
STDOUT.write("\b\b")
|
102
|
+
STDOUT.flush
|
103
|
+
end
|
104
|
+
|
105
|
+
Rpush.logger.info('Shutting down... ', true)
|
106
|
+
|
107
|
+
shutdown_lock.synchronize do
|
108
|
+
Rpc::Server.stop
|
109
|
+
Feeder.stop
|
110
|
+
AppRunner.stop
|
111
|
+
delete_pid_file
|
112
|
+
puts ANSI.green { '✔' } if Rpush.config.foreground
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.shutdown_lock
|
117
|
+
@shutdown_lock ||= Mutex.new
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.common_init
|
121
|
+
init_store
|
122
|
+
init_plugins
|
123
|
+
end
|
124
|
+
|
125
|
+
protected
|
126
|
+
|
127
|
+
def self.init_store
|
128
|
+
return if store
|
129
|
+
begin
|
130
|
+
name = Rpush.config.client.to_s
|
131
|
+
require "rpush/daemon/store/#{name}"
|
132
|
+
self.store = Rpush::Daemon::Store.const_get(name.camelcase).new
|
133
|
+
rescue StandardError, LoadError => e
|
134
|
+
Rpush.logger.error("Failed to load '#{Rpush.config.client}' storage backend.")
|
135
|
+
Rpush.logger.error(e)
|
136
|
+
exit 1
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.init_plugins
|
141
|
+
Rpush.plugins.each do |name, plugin|
|
142
|
+
plugin.init_block.call
|
143
|
+
Rpush.logger.info("[plugin:#{name}] Loaded.")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.daemonize?
|
148
|
+
!(Rpush.config.push || Rpush.config.foreground || Rpush.config.embedded || Rpush.jruby?)
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.write_pid_file
|
152
|
+
unless Rpush.config.pid_file.blank?
|
153
|
+
begin
|
154
|
+
FileUtils.mkdir_p(File.dirname(Rpush.config.pid_file))
|
155
|
+
File.open(Rpush.config.pid_file, 'w') { |f| f.puts Process.pid }
|
156
|
+
rescue SystemCallError => e
|
157
|
+
Rpush.logger.error("Failed to write PID to '#{Rpush.config.pid_file}': #{e.inspect}")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.delete_pid_file
|
163
|
+
pid_file = Rpush.config.pid_file
|
164
|
+
File.delete(pid_file) if !pid_file.blank? && File.exist?(pid_file)
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.show_welcome_if_needed
|
168
|
+
if Rpush::Daemon::AppRunner.app_ids.count == 0
|
169
|
+
puts <<-EOS
|
170
|
+
|
171
|
+
* #{ANSI.green { 'Is this your first time using Rpush?' }}
|
172
|
+
You need to create an App before you can start using Rpush.
|
173
|
+
Please refer to the documentation at https://github.com/rpush/rpush
|
174
|
+
|
175
|
+
EOS
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Deprecatable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def deprecated(method_name, version, msg = nil)
|
9
|
+
method_name_as_var = method_name.to_s.tr('=', '_setter_')
|
10
|
+
instance_eval do
|
11
|
+
alias_method "#{method_name_as_var}_without_warning", method_name
|
12
|
+
end
|
13
|
+
warning = "#{method_name} is deprecated and will be removed from Rpush #{version}."
|
14
|
+
warning << " #{msg}" if msg
|
15
|
+
class_eval(<<-RUBY, __FILE__, __LINE__)
|
16
|
+
def #{method_name}(*args, &blk)
|
17
|
+
Rpush::Deprecation.warn_with_backtrace(#{warning.inspect})
|
18
|
+
#{method_name_as_var}_without_warning(*args, &blk)
|
19
|
+
end
|
20
|
+
RUBY
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rpush
|
2
|
+
class Deprecation
|
3
|
+
def self.muted
|
4
|
+
orig_val = Thread.current[:rpush_mute_deprecations]
|
5
|
+
Thread.current[:rpush_mute_deprecations] = true
|
6
|
+
yield
|
7
|
+
ensure
|
8
|
+
Thread.current[:rpush_mute_deprecations] = orig_val
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.muted?
|
12
|
+
Thread.current[:rpush_mute_deprecations] == true
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.warn(msg)
|
16
|
+
return if Rpush::Deprecation.muted?
|
17
|
+
STDERR.puts "DEPRECATION WARNING: #{msg}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.warn_with_backtrace(msg)
|
21
|
+
return if Rpush::Deprecation.muted?
|
22
|
+
trace = "\n\nCALLED FROM:\n" + caller.join("\n")
|
23
|
+
warn(msg + trace)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|