urbanairship 2.4.1 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ require 'unirest'
2
+ require 'urbanairship/common'
3
+ require 'urbanairship/loggable'
4
+
5
+ module Urbanairship
6
+ class Client
7
+ attr_accessor :key, :secret
8
+ include Urbanairship::Common
9
+ include Urbanairship::Loggable
10
+
11
+ # set default client timeout to 5 seconds
12
+ Unirest.timeout(5)
13
+
14
+ # Initialize the Client
15
+ #
16
+ # @param [Object] key Application Key
17
+ # @param [Object] secret Application Secret
18
+ # @return [Object] Client
19
+ def initialize(key: required('key'), secret: required('secret'))
20
+ @key = key
21
+ @secret = secret
22
+ end
23
+
24
+ # Send a request to Urban Airship's API
25
+ #
26
+ # @param [Object] method HTTP Method
27
+ # @param [Object] body Request Body
28
+ # @param [Object] url Request URL
29
+ # @param [Object] content_type Content-Type
30
+ # @param [Object] version API Version
31
+ # @param [Object] params Parameters
32
+ # @return [Object] Push Response
33
+ def send_request(method: required('method'), body: required('body'), url: required('url'),
34
+ content_type: nil, version: nil, params: nil)
35
+ req_type = case method
36
+ when 'GET'
37
+ :get
38
+ when 'POST'
39
+ :post
40
+ when 'PUT'
41
+ :put
42
+ when 'DELETE'
43
+ :delete
44
+ else
45
+ fail 'Method was not "GET" "POST" "PUT" or "DELETE"'
46
+ end
47
+
48
+ logger.debug("Making #{method} request to #{url}. \n\tHeaders:\n\tcontent-type: #{content_type}\n\tversion=#{version.to_s}\nBody:\n\t#{body}")
49
+
50
+ response = Unirest.method(req_type).call(
51
+ url,
52
+ headers:{
53
+ "Content-type" => content_type,
54
+ "Accept" => "application/vnd.urbanairship+json; version=" + version.to_s
55
+ },
56
+ auth:{
57
+ :user=>@key,
58
+ :password=>@secret
59
+ },
60
+ parameters: body
61
+ )
62
+
63
+ logger.debug("Received #{response.code} response. Headers:\n\t#{response.headers}\nBody:\n\t#{response.body}")
64
+
65
+ Response.check_code(response.code, response)
66
+
67
+ {'body'=>response.body, 'code'=>response.code}
68
+ end
69
+
70
+ # Create a Push Object
71
+ #
72
+ # @return [Object] Push Object
73
+ def create_push
74
+ Push::Push.new(self)
75
+ end
76
+
77
+ # Create a Scheduled Push Object
78
+ #
79
+ # @return [Object] Scheduled Push Object
80
+ def create_scheduled_push
81
+ Push::ScheduledPush.new(self)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,85 @@
1
+ require 'urbanairship/loggable'
2
+
3
+ module Urbanairship
4
+ # Features mixed in to all classes
5
+ module Common
6
+ SERVER = 'go.urbanairship.com'
7
+ BASE_URL = 'https://go.urbanairship.com/api'
8
+ CHANNEL_URL = BASE_URL + '/channels/'
9
+ DEVICE_TOKEN_URL = BASE_URL + '/device_tokens/'
10
+ APID_URL = BASE_URL + '/apids/'
11
+ DEVICE_PIN_URL = BASE_URL + '/device_pins/'
12
+ PUSH_URL = BASE_URL + '/push/'
13
+ DT_FEEDBACK_URL = BASE_URL + '/device_tokens/feedback/'
14
+ APID_FEEDBACK_URL = BASE_URL + '/apids/feedback/'
15
+ SCHEDULES_URL = BASE_URL + '/schedules/'
16
+ TAGS_URL = BASE_URL + '/tags/'
17
+ SEGMENTS_URL = BASE_URL + '/segments/'
18
+
19
+ # Helper method for required keyword args in 2.0 that is compatible with 2.1+
20
+ # @example
21
+ # def say(greeting: required('greeting'))
22
+ # puts greeting
23
+ # end
24
+ #
25
+ # >> say
26
+ # >> test.rb:3:in `required': required parameter :greeting not passed to method say (ArgumentError)
27
+ # >> from test.rb:6:in `say'
28
+ # >> from test.rb:18:in `<main>'
29
+ # @param [Object] arg optional argument name
30
+ def required(arg=nil)
31
+ method = caller_locations(1,1)[0].label
32
+ raise ArgumentError.new("required parameter #{arg.to_sym.inspect + ' ' if arg}not passed to method #{method}")
33
+ end
34
+
35
+ class Unauthorized < StandardError
36
+ # raised when we get a 401 from server
37
+ end
38
+
39
+ class Forbidden < StandardError
40
+ # raised when we get a 403 from server
41
+ end
42
+
43
+ class AirshipFailure < StandardError
44
+ include Urbanairship::Loggable
45
+ # Raised when we get an error response from the server.
46
+ attr_accessor :error, :error_code, :details, :response
47
+
48
+ def initialize
49
+ @error = nil
50
+ @error_code = nil
51
+ @details = nil
52
+ @response = nil
53
+ end
54
+
55
+ # Instantiate a ValidationFailure from a Response object
56
+ def from_response(response)
57
+
58
+ payload = response.body
59
+ @error = payload['error']
60
+ @error_code = payload['error_code']
61
+ @details = payload['details']
62
+ @response = response
63
+
64
+ logger.error("Request failed with status #{response.code.to_s}: '#{@error_code} #{@error}': #{response.body}")
65
+
66
+ self
67
+ end
68
+
69
+ end
70
+
71
+ class Response
72
+ # Parse Response Codes and trigger appropriate actions.
73
+ def self.check_code(response_code, response)
74
+ if response_code == 401
75
+ raise Unauthorized, "Client is not authorized to make this request. The authorization credentials are incorrect or missing."
76
+ elsif response_code == 403
77
+ raise Forbidden, "Client is not forbidden from making this request. The application does not have the proper entitlement to access this feature."
78
+ elsif !((200...300).include?(response_code))
79
+ raise AirshipFailure.new.from_response(response)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+
3
+ module Urbanairship
4
+ module Loggable
5
+
6
+ def logger
7
+ @logger ||= create_logger
8
+ end
9
+
10
+ def create_logger
11
+ logger = Logger.new('urbanairship.log')
12
+ logger.datetime_format = '%Y-%m-%d %H:%M:%S'
13
+ logger.progname = 'Urbanairship'
14
+ logger
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,150 @@
1
+ require 'urbanairship/util'
2
+ require 'urbanairship/common'
3
+
4
+ module Urbanairship
5
+ module Push
6
+ module Audience
7
+ include Urbanairship::Common
8
+ UUID_PATTERN = /^\h{8}-\h{4}-\h{4}-\h{4}-\h{12}$/
9
+ DEVICE_TOKEN_PATTERN = /^\h{64}$/
10
+ DEVICE_PIN_PATTERN = /^\h{8}$/
11
+ DATE_TERMS = %i(minutes hours days weeks months years)
12
+
13
+
14
+ # Methods to select a single iOS Channel, Android Channel, Amazon Channel,
15
+ # Android APID, Windows 8 APID, or Windows Phone 8 APID respectively.
16
+ #
17
+ # @example
18
+ # ios_channel(<channel>) # ==>
19
+ # {:ios_channel=>"<channel>"}
20
+ %w(ios_channel android_channel amazon_channel apid wns mpns).each do |name|
21
+ define_method(name) do |uuid|
22
+ { name.to_sym => cleanup(uuid) }
23
+ end
24
+ end
25
+
26
+ # Select a single iOS device token
27
+ def device_token(token)
28
+ Util.validate(token, 'device_token', DEVICE_TOKEN_PATTERN)
29
+ { device_token: token.upcase.strip }
30
+ end
31
+
32
+ # Select a single BlackBerry PIN
33
+ def device_pin(pin)
34
+ Util.validate(pin, 'pin', DEVICE_PIN_PATTERN)
35
+ { device_pin: pin.downcase.strip }
36
+ end
37
+
38
+ # Select a single tag
39
+ def tag(tag)
40
+ { tag: tag }
41
+ end
42
+
43
+ # Select a single alias
44
+ def alias(an_alias)
45
+ { alias: an_alias }
46
+ end
47
+
48
+ # Select a single segment using segment_id
49
+ def segment(segment)
50
+ { segment: segment }
51
+ end
52
+
53
+ # Select devices that match at least one of the given selectors.
54
+ #
55
+ # @example
56
+ # or(tag('sports'), tag('business')) # ==>
57
+ # {or: [{tag: 'sports'}, {tag: 'business'}]}
58
+ def or(*children)
59
+ { or: children }
60
+ end
61
+
62
+ # Select devices that match all of the given selectors.
63
+ #
64
+ # @example
65
+ # and(tag('sports'), tag('business')) # ==>
66
+ # {and: [{tag: 'sports'}, {tag: 'business'}]}
67
+ def and(*children)
68
+ { and: children }
69
+ end
70
+
71
+ # Select devices that do not match the given selectors.
72
+ #
73
+ # @example
74
+ # not(and_(tag('sports'), tag('business'))) # ==>
75
+ # {not: {and: [{tag: 'sports'}, {tag: 'business'}]}}
76
+ def not(child)
77
+ { not: child }
78
+ end
79
+
80
+ # Select a recent date range for a location selector.
81
+ # Valid selectors are:
82
+ # :minutes :hours :days :weeks :months :years
83
+ #
84
+ # @example
85
+ # recent_date(months: 6) # => { recent: { months: 6 }}
86
+ # recent_date(weeks: 3) # => { recent: { weeks: 3 }}
87
+ def recent_date(**params)
88
+ fail ArgumentError, 'Only one range allowed' if params.size != 1
89
+ k, v = params.first
90
+ unless DATE_TERMS.include?(k)
91
+ fail ArgumentError, "#{k} not in #{DATE_TERMS}"
92
+ end
93
+ { recent: { k => v } }
94
+ end
95
+
96
+ # Select an absolute date range for a location selector.
97
+ #
98
+ # @param resolution [Symbol] Time resolution specifier, one of
99
+ # :minutes :hours :days :weeks :months :years
100
+ # @param start [String] UTC start time in ISO 8601 format.
101
+ # @param the_end [String] UTC end time in ISO 8601 format.
102
+ #
103
+ # @example
104
+ # absolute_date(resolution: :months, start: '2013-01', the_end: '2013-06')
105
+ # #=> {months: {end: '2013-06', start: '2013-01'}}
106
+ #
107
+ # absolute_date(resolution: :minutes, start: '2012-01-01 12:00',
108
+ # the_end: '2012-01-01 12:45')
109
+ # #=> {minutes: {end: '2012-01-01 12:45', start: '2012-01-01 12:00'}}
110
+ def absolute_date(resolution: required('resolution'), start: required('start'), the_end: required('the_end'))
111
+ unless DATE_TERMS.include?(resolution)
112
+ fail ArgumentError, "#{resolution} not in #{DATE_TERMS}"
113
+ end
114
+ { resolution => { start: start, end: the_end } }
115
+ end
116
+
117
+ # Select a location expression.
118
+ #
119
+ # Location selectors are made up of either an id or an alias and a date
120
+ # period specifier. Use a date specification function to generate the time
121
+ # period specifier.
122
+ #
123
+ # @example ID location
124
+ # location(id: '4oFkxX7RcUdirjtaenEQIV', date: recent_date(days: 4))
125
+ # #=> {location: {date: {recent: {days: 4}},
126
+ # id: '4oFkxX7RcUdirjtaenEQIV'}}
127
+ #
128
+ # @example Alias location
129
+ # location(us_zip: '94103', date: absolute_date(
130
+ # resolution: 'days', start: '2012-01-01', end: '2012-01-15'))
131
+ # #=> {location: {date: {days: {end: '2012-01-15',
132
+ # start: '2012-01-01'}}, us_zip: '94103'}}
133
+ def location(date: required('date'), **params)
134
+ unless params.size == 1
135
+ fail ArgumentError, 'One location specifier required'
136
+ end
137
+ params[:date] = date
138
+ { location: params }
139
+ end
140
+
141
+ private
142
+
143
+ # Clean up a UUID for use in the library
144
+ def cleanup(uuid)
145
+ Util.validate(uuid, 'UUID', UUID_PATTERN)
146
+ uuid.downcase.strip
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,144 @@
1
+ module Urbanairship
2
+ module Push
3
+ module Payload
4
+ require 'ext/hash'
5
+ include Urbanairship::Common
6
+
7
+ # Notification Object for a Push Payload
8
+ def notification(alert: nil, ios: nil, android: nil, amazon: nil,
9
+ blackberry: nil, wns: nil, mpns: nil, actions: nil,
10
+ interactive: nil)
11
+ payload = {
12
+ alert: alert,
13
+ actions: actions,
14
+ ios: ios,
15
+ android: android,
16
+ amazon: amazon,
17
+ blackberry: blackberry,
18
+ wns: wns,
19
+ mpns: mpns,
20
+ interactive: interactive
21
+ }.compact
22
+ fail ArgumentError, 'Notification body is empty' if payload.empty?
23
+ payload
24
+ end
25
+
26
+ # iOS specific portion of Push Notification Object
27
+ def ios(alert: nil, badge: nil, sound: nil, extra: nil, expiry: nil,
28
+ category: nil, interactive: nil, content_available: nil)
29
+ {
30
+ alert: alert,
31
+ badge: badge,
32
+ sound: sound,
33
+ extra: extra,
34
+ expiry: expiry,
35
+ category: category,
36
+ interactive: interactive,
37
+ 'content-available' => content_available
38
+ }.compact
39
+ end
40
+
41
+ # Amazon specific portion of Push Notification Object
42
+ def amazon(alert: nil, consolidation_key: nil, expires_after: nil,
43
+ extra: nil, title: nil, summary: nil, interactive: nil)
44
+ {
45
+ alert: alert,
46
+ consolidation_key: consolidation_key,
47
+ expires_after: expires_after,
48
+ extra: extra,
49
+ title: title,
50
+ summary: summary,
51
+ interactive: interactive
52
+ }.compact
53
+ end
54
+
55
+ # Android specific portion of Push Notification Object
56
+ def android(alert: nil, collapse_key: nil, time_to_live: nil,
57
+ extra: nil, delay_while_idle: nil, interactive: nil)
58
+ {
59
+ alert: alert,
60
+ collapse_key: collapse_key,
61
+ time_to_live: time_to_live,
62
+ extra: extra,
63
+ delay_while_idle: delay_while_idle,
64
+ interactive: interactive
65
+ }.compact
66
+ end
67
+
68
+ # BlackBerry specific portion of Push Notification Object
69
+ def blackberry(alert: nil, body: nil, content_type: 'text/plain')
70
+ { body: alert || body, content_type: content_type }
71
+ end
72
+
73
+ # WNS specific portion of Push Notification Object
74
+ def wns_payload(alert: nil, toast: nil, tile: nil, badge: nil)
75
+ payload = {
76
+ alert: alert,
77
+ toast: toast,
78
+ tile: tile,
79
+ badge: badge
80
+ }.compact
81
+ fail ArgumentError, 'Must specify one message type' if payload.size != 1
82
+ payload
83
+ end
84
+
85
+ # MPNS specific portion of Push Notification Object
86
+ def mpns_payload(alert: nil, toast: nil, tile: nil)
87
+ payload = {
88
+ alert: alert,
89
+ toast: toast,
90
+ tile: tile
91
+ }.compact
92
+ fail ArgumentError, 'Must specify one message type' if payload.size != 1
93
+ payload
94
+ end
95
+
96
+ # Rich Message specific portion of Push Notification Object
97
+ def message(title: required('title'), body: required('body'), content_type: nil, content_encoding: nil,
98
+ extra: nil, expiry: nil, icons: nil, options: nil)
99
+ {
100
+ title: title,
101
+ body: body,
102
+ content_type: content_type,
103
+ content_encoding: content_encoding,
104
+ extra: extra,
105
+ expiry: expiry,
106
+ icons: icons,
107
+ options: options
108
+ }.compact
109
+ end
110
+
111
+ # Interactive Notification portion of Push Notification Object
112
+ def interactive(type: required('type'), button_actions: nil)
113
+ fail ArgumentError, 'type must not be nil' if type.nil?
114
+ { type: type, button_actions: button_actions }.compact
115
+ end
116
+
117
+ def all
118
+ 'all'
119
+ end
120
+
121
+ # Target specified device types
122
+ def device_types(types)
123
+ types
124
+ end
125
+
126
+ # Expiry for a Rich Message
127
+ def options(expiry: required('expiry'))
128
+ { expiry: expiry }
129
+ end
130
+
131
+ # Actions for a Push Notification Object
132
+ def actions(add_tag: nil, remove_tag: nil, open_: nil, share: nil,
133
+ app_defined: nil)
134
+ {
135
+ add_tag: add_tag,
136
+ remove_tag: remove_tag,
137
+ open: open_,
138
+ share: share,
139
+ app_defined: app_defined
140
+ }.compact
141
+ end
142
+ end
143
+ end
144
+ end