lib/grocer/notification.rb
require 'json'
module Grocer
# Public: An object used to send notifications to APNS.
class Notification
MAX_PAYLOAD_SIZE = 2048
CONTENT_AVAILABLE_INDICATOR = 1
MUTABLE_CONTENT_INDICATOR = 1
attr_accessor :identifier, :expiry, :device_token
attr_reader :alert, :badge, :custom, :sound, :content_available, :mutable_content, :category
# Public: Initialize a new Grocer::Notification. You must specify at least an `alert` or `badge`.
#
# payload - The Hash of notification parameters and payload to be sent to APNS.:
# :device_token - The String representing to device token sent to APNS.
# :alert - The String or Hash to be sent as the alert portion of the payload. (optional)
# :badge - The Integer to be sent as the badge portion of the payload. (optional)
# :sound - The String representing the sound portion of the payload. (optional)
# :expiry - The Integer representing UNIX epoch date sent to APNS as the notification expiry. (default: 0)
# :identifier - The arbitrary Integer sent to APNS to uniquely this notification. (default: 0)
# :content_available - The truthy or falsy value indicating the availability of new content for background fetch. (optional)
# :mutable_content - The truthy or falsy value indicating whether to have this notification be processed by a Notification Service Extension (since iOS 10) (optional)
# :category - The String to be sent as the category portion of the payload. (optional)
def initialize(payload = {})
@identifier = 0
payload.each do |key, val|
send("#{key}=", val)
end
end
def to_bytes
validate_payload
[
1,
identifier,
expiry_epoch_time,
device_token_length,
sanitized_device_token,
encoded_payload.bytesize,
encoded_payload
].pack('CNNnH64nA*')
end
def alert=(alert)
@alert = alert
@encoded_payload = nil
end
def badge=(badge)
@badge = badge
@encoded_payload = nil
end
def custom=(custom)
@custom = custom
@encoded_payload = nil
end
def sound=(sound)
@sound = sound
@encoded_payload = nil
end
def category=(category)
@category = category
@encoded_payload = nil
end
def content_available=(content_available)
@content_available = CONTENT_AVAILABLE_INDICATOR if content_available
@encoded_payload = nil
end
def content_available?
!!content_available
end
def mutable_content=(mutable_content)
@mutable_content = MUTABLE_CONTENT_INDICATOR if mutable_content
@encoded_payload = nil
end
def mutable_content?
!!mutable_content
end
def validate_payload
fail NoPayloadError unless alert || badge || custom
fail PayloadTooLargeError if payload_too_large?
true
end
def valid?
validate_payload rescue false
end
private
def encoded_payload
@encoded_payload ||= JSON.dump(payload_hash)
end
def payload_hash
aps_hash = { }
aps_hash[:alert] = alert if alert
aps_hash[:badge] = badge if badge
aps_hash[:sound] = sound if sound
aps_hash[:'content-available'] = content_available if content_available?
aps_hash[:'mutable-content'] = mutable_content if mutable_content?
aps_hash[:category] = category if category
{ aps: aps_hash }.merge(custom || { })
end
def payload_too_large?
encoded_payload.bytesize > MAX_PAYLOAD_SIZE
end
def expiry_epoch_time
expiry.to_i
end
def sanitized_device_token
device_token.tr(' ', '') if device_token
end
def device_token_length
32
end
end
end