spire_io 1.0.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/spire/api.rb CHANGED
@@ -10,7 +10,10 @@ require "spire/api/session"
10
10
  require "spire/api/account"
11
11
  require "spire/api/channel"
12
12
  require "spire/api/subscription"
13
- require "spire/api/message"
13
+ require "spire/api/event"
14
+ require "spire/api/application"
15
+ require "spire/api/member"
16
+ require "spire/api/notification"
14
17
 
15
18
  class Spire
16
19
 
@@ -28,10 +31,6 @@ class Spire
28
31
  @url = url
29
32
  end
30
33
 
31
- def inspect
32
- "#<Spire::API:0x#{object_id.to_s(16)} @url=#{@url.dump}>"
33
- end
34
-
35
34
  define_request(:discover) do
36
35
  {
37
36
  :method => :get,
@@ -71,7 +70,8 @@ class Spire
71
70
  :body => {
72
71
  :email => info[:email],
73
72
  :password => info[:password],
74
- :password_confirmation => info[:password_confirmation]
73
+ :password_confirmation => info[:password_confirmation],
74
+ :email_opt_in => info[:email_opt_in]
75
75
  }.to_json,
76
76
  :headers => {
77
77
  "Accept" => mediaType("session"),
@@ -99,6 +99,17 @@ class Spire
99
99
  }
100
100
  end
101
101
 
102
+ define_request(:get_application) do |app_key|
103
+ {
104
+ :method => :get,
105
+ :url => @description["resources"]["applications"]["url"],
106
+ :query => {:application_key => app_key},
107
+ :headers => {
108
+ "Accept" => mediaType("applications"),
109
+ "Content-Type" => mediaType("applications")
110
+ }
111
+ }
112
+ end
102
113
 
103
114
  def discover
104
115
  response = request(:discover)
@@ -146,6 +157,16 @@ class Spire
146
157
  response
147
158
  end
148
159
 
160
+ # Gets an application resource from a key without requiring any authentication
161
+ # @param [String] application_key The application key
162
+ def get_application(application_key)
163
+ response = request(:get_application, application_key)
164
+ if response.status != 200
165
+ raise "Error attempting to retrieve application (#{response.status}) #{response.body}"
166
+ end
167
+ API::Application.new(self, response.data)
168
+ end
169
+
149
170
  # Returns a billing object than contains a list of all the plans available
150
171
  # @param [String] info optional object description
151
172
  # @return [Billing]
@@ -0,0 +1,325 @@
1
+ require 'base64'
2
+ class Spire
3
+ class API
4
+
5
+ class Application < Resource
6
+ attr_reader :resources
7
+
8
+ def resource_name
9
+ "application"
10
+ end
11
+
12
+ def initialize(spire, data)
13
+ super
14
+ @resources = data["resources"]
15
+ end
16
+
17
+ #Channels
18
+ define_request(:create_channel) do |name, message_limit, message_ttl|
19
+ collection = @resources["channels"]
20
+ capability = collection["capabilities"]["create"]
21
+ url = collection["url"]
22
+
23
+ body = {
24
+ :name => name,
25
+ :message_limit => message_limit,
26
+ :message_ttl => message_ttl
27
+ }.to_json
28
+ {
29
+ :method => :post,
30
+ :url => url,
31
+ :body => body,
32
+ :headers => {
33
+ "Authorization" => "Capability #{capability}",
34
+ "Accept" => @spire.mediaType("channel"),
35
+ "Content-Type" => @spire.mediaType("channel")
36
+ }
37
+ }
38
+ end
39
+
40
+ define_request(:channels) do
41
+ collection = @resources["channels"]
42
+ capability = collection["capabilities"]["all"]
43
+ url = collection["url"]
44
+ {
45
+ :method => :get,
46
+ :url => url,
47
+ :headers => {
48
+ "Authorization" => "Capability #{capability}",
49
+ "Accept" => @spire.mediaType("channels"),
50
+ }
51
+ }
52
+ end
53
+
54
+ define_request(:channel_by_name) do |name|
55
+ collection = @resources["channels"]
56
+ capability = collection["capabilities"]["get_by_name"]
57
+ url = collection["url"]
58
+ request = {
59
+ :method => :get,
60
+ :url => url,
61
+ :query => {:name => name},
62
+ :headers => {
63
+ "Authorization" => "Capability #{capability}",
64
+ "Accept" => @spire.mediaType("channels"),
65
+ }
66
+ }
67
+ end
68
+
69
+ #Subscriptions
70
+ define_request(:subscriptions) do
71
+ collection = @resources["subscriptions"]
72
+ capability = collection["capabilities"]["all"]
73
+ url = collection["url"]
74
+ {
75
+ :method => :get,
76
+ :url => url,
77
+ :headers => {
78
+ "Authorization" => "Capability #{capability}",
79
+ "Accept" => @spire.mediaType("subscriptions"),
80
+ }
81
+ }
82
+ end
83
+
84
+ define_request(:create_subscription) do |subscription_name, channel_urls|
85
+ collection = @resources["subscriptions"]
86
+ capability = collection["capabilities"]["create"]
87
+ url = collection["url"]
88
+ {
89
+ :method => :post,
90
+ :url => url,
91
+ :body => {
92
+ :channels => channel_urls,
93
+ :name => subscription_name
94
+ }.to_json,
95
+ :headers => {
96
+ "Authorization" => "Capability #{capability}",
97
+ "Accept" => @spire.mediaType("subscription"),
98
+ "Content-Type" => @spire.mediaType("subscription")
99
+ }
100
+ }
101
+ end
102
+
103
+ define_request(:subscription_by_name) do |name|
104
+ collection = @resources["subscriptions"]
105
+ capability = collection["capabilities"]["get_by_name"]
106
+ url = collection["url"]
107
+ request = {
108
+ :method => :get,
109
+ :url => url,
110
+ :query => {:name => name},
111
+ :headers => {
112
+ "Authorization" => "Capability #{capability}",
113
+ "Accept" => @spire.mediaType("subscriptions"),
114
+ }
115
+ }
116
+ end
117
+
118
+ #Members
119
+ define_request(:create_member) do |data|
120
+ collection = @resources["members"]
121
+ capability = collection["capabilities"]["create"]
122
+ url = collection["url"]
123
+ {
124
+ :method => :post,
125
+ :url => url,
126
+ :body => data.to_json,
127
+ :headers => {
128
+ "Authorization" => "Capability #{capability}",
129
+ "Accept" => @spire.mediaType("member"),
130
+ "Content-Type" => @spire.mediaType("member")
131
+ }
132
+ }
133
+ end
134
+
135
+ define_request(:members) do
136
+ collection = @resources["members"]
137
+ capability = collection["capabilities"]["all"]
138
+ url = collection["url"]
139
+ {
140
+ :method => :get,
141
+ :url => url,
142
+ :headers => {
143
+ "Authorization" => "Capability #{capability}",
144
+ "Accept" => @spire.mediaType("members"),
145
+ }
146
+ }
147
+ end
148
+
149
+ define_request(:member_by_login) do |login|
150
+ collection = @resources["members"]
151
+ capability = collection["capabilities"]["get_by_login"]
152
+ url = collection["url"]
153
+ request = {
154
+ :method => :get,
155
+ :url => url,
156
+ :query => {:login => login},
157
+ :headers => {
158
+ "Authorization" => "Capability #{capability}",
159
+ "Accept" => @spire.mediaType("member"),
160
+ }
161
+ }
162
+ end
163
+
164
+ define_request(:authenticate_with_post) do |data|
165
+ collection = @resources["authentication"]
166
+ url = collection["url"]
167
+ request = {
168
+ :method => :post,
169
+ :url => url,
170
+ :body => data.to_json,
171
+ :headers => {
172
+ "Accept" => @spire.mediaType("member"),
173
+ }
174
+ }
175
+ end
176
+
177
+ define_request(:authenticate) do |data|
178
+ collection = @resources["members"]
179
+ url = "#{collection["url"]}?login=#{data[:login]}"
180
+ auth = Base64.encode64("#{data[:login]}:#{data[:password]}").gsub("\n", '')
181
+ request = {
182
+ :method => :get,
183
+ :url => url,
184
+ :headers => {
185
+ "Accept" => @spire.mediaType("member"),
186
+ "Authorization" => "Basic #{auth}"
187
+ }
188
+ }
189
+ end
190
+
191
+ #Authenticates with the application using basic auth
192
+ def authenticate(login, password)
193
+ response = request(:authenticate, {:login => login, :password => password})
194
+ unless response.status == 200
195
+ raise "Error authenticating for application #{self.name}: (#{response.status}) #{response.body}"
196
+ end
197
+ API::Member.new(@spire, response.data)
198
+ end
199
+
200
+ #Alternative application authentication, without using basic auth
201
+ def authenticate_with_post(login, password)
202
+ response = request(:authenticate_with_post, {:login => login, :password => password})
203
+ unless response.status == 201
204
+ raise "Error authenticating for application #{self.name}: (#{response.status}) #{response.body}"
205
+ end
206
+ API::Member.new(@spire, response.data)
207
+ end
208
+
209
+ def create_member(member_data)
210
+ response = request(:create_member, member_data)
211
+ unless response.status == 201
212
+ raise "Error creating member for application #{self.name}: (#{response.status}) #{response.body}"
213
+ end
214
+ API::Member.new(@spire, response.data)
215
+ end
216
+
217
+ def members
218
+ @members || members!
219
+ end
220
+
221
+ def members!
222
+ response = request(:members)
223
+ unless response.status == 200
224
+ raise "Error getting members for application #{self.name}: (#{response.status}) #{response.body}"
225
+ end
226
+ @members = {}
227
+ response.data.each do |login, properties|
228
+ @members[login] = API::Member.new(@spire, properties)
229
+ end
230
+ @members
231
+ end
232
+
233
+ def get_member(member_login)
234
+ response = request(:member_by_login, member_login)
235
+ unless response.status == 200
236
+ raise "Error finding member with login #{member_login}: (#{response.status}) #{response.body}"
237
+ end
238
+ properties = response.data[member_login]
239
+ member = API::Member.new(@spire, properties)
240
+ @members[member_login] = member if @members.is_a?(Hash)
241
+ member
242
+ end
243
+
244
+ def create_channel(name, options={})
245
+ message_limit = options[:message_limit]
246
+ message_ttl = options[:message_ttl]
247
+ response = request(:create_channel, name, message_limit, message_ttl)
248
+ unless response.status == 201
249
+ raise "Error creating Channel: (#{response.status}) #{response.body}"
250
+ end
251
+ properties = response.data
252
+ channels[name] = API::Channel.new(@spire, properties)
253
+ end
254
+
255
+ def create_subscription(subscription_name, channel_names)
256
+ channel_urls = channel_names.flatten.map { |name| self.channels[name].url rescue nil }.compact
257
+ response = request(:create_subscription, subscription_name, channel_urls)
258
+ unless response.status == 201
259
+ raise "Error creating Subscription: (#{response.status}) #{response.body}"
260
+ end
261
+ data = response.data
262
+ subscription = API::Subscription.new(@spire, data)
263
+ if subscription_name
264
+ subscriptions[data["name"]] = subscription
265
+ end
266
+ subscription
267
+ end
268
+
269
+ def channels!
270
+ response = request(:channels)
271
+ unless response.status == 200
272
+ raise "Error retrieving Channels: (#{response.status}) #{response.body}"
273
+ end
274
+ channels_data = response.data
275
+ @channels = {}
276
+ channels_data.each do |name, properties|
277
+ @channels[name] = API::Channel.new(@spire, properties)
278
+ end
279
+ @channels
280
+ end
281
+
282
+ def channels
283
+ @channels ||= channels!
284
+ end
285
+
286
+ def subscriptions
287
+ @subscriptions ||= subscriptions!
288
+ end
289
+
290
+ def subscriptions!
291
+ response = request(:subscriptions)
292
+ unless response.status == 200
293
+ raise "Error retrieving Subscriptions: (#{response.status}) #{response.body}"
294
+ end
295
+ @subscriptions = {}
296
+ response.data.each do |name, properties|
297
+ @subscriptions[name] = API::Subscription.new(@spire, properties)
298
+ end
299
+ @subscriptions
300
+ end
301
+
302
+ def get_channel(name)
303
+ response = request(:channel_by_name, name)
304
+ unless response.status == 200
305
+ raise "Error finding channel with name #{name}: (#{response.status}) #{response.body}"
306
+ end
307
+ properties = response.data[name]
308
+ channel = API::Channel.new(@spire, properties)
309
+ @channels[name] = channel if @channels.is_a?(Hash)
310
+ channel
311
+ end
312
+
313
+ def get_subscription(name)
314
+ response = request(:subscription_by_name, name)
315
+ unless response.status == 200
316
+ raise "Error finding subscription with name #{name}: (#{response.status}) #{response.body}"
317
+ end
318
+ properties = response.data[name]
319
+ sub = API::Subscription.new(@spire, properties)
320
+ @subscriptions[name] = sub if @subscriptions.is_a?(Hash)
321
+ sub
322
+ end
323
+ end
324
+ end
325
+ end
@@ -2,10 +2,32 @@ class Spire
2
2
  class API
3
3
 
4
4
  class Channel < Resource
5
+
6
+ attr_reader :resources
7
+
8
+ def initialize(spire, data)
9
+ super
10
+ @resources = data["resources"]
11
+ end
12
+
5
13
  def resource_name
6
14
  "channel"
7
15
  end
8
16
 
17
+ define_request(:subscriptions) do
18
+ collection = @resources["subscriptions"]
19
+ capability = collection["capabilities"]["get_subscriptions"]
20
+ url = collection["url"]
21
+ {
22
+ :method => :get,
23
+ :url => url,
24
+ :headers => {
25
+ "Authorization" => "Capability #{capability}",
26
+ "Accept" => @spire.mediaType("subscriptions"),
27
+ }
28
+ }
29
+ end
30
+
9
31
  define_request(:publish) do |string|
10
32
  {
11
33
  :method => :post,
@@ -19,15 +41,54 @@ class Spire
19
41
  }
20
42
  end
21
43
 
44
+ define_request(:subscribe) do |name|
45
+ collection = @resources["subscriptions"]
46
+ capability = collection["capabilities"]["create"]
47
+ url = collection["url"]
48
+ {
49
+ :method => :post,
50
+ :url => url,
51
+ :body => {:name => name}.to_json,
52
+ :headers => {
53
+ "Authorization" => "Capability #{capability}",
54
+ "Accept" => @spire.mediaType("subscription"),
55
+ "Content-Type" => @spire.mediaType("subscription")
56
+ }
57
+ }
58
+ end
59
+
60
+ def subscriptions
61
+ @subscriptions ||= subscriptions!
62
+ end
63
+
64
+ def subscriptions!
65
+ response = request(:subscriptions)
66
+ unless response.status == 200
67
+ raise "Error getting subscriptions to #{self.class.name}: (#{response.status}) #{response.body}"
68
+ end
69
+ @subscriptions = {}
70
+ response.data.each do |name, properties|
71
+ @subscriptions[name] = API::Subscription.new(@spire, properties)
72
+ end
73
+ @subscriptions
74
+ end
75
+
22
76
  def publish(content)
23
77
  body = {:content => content}.to_json
24
78
  response = request(:publish, body)
25
79
  unless response.status == 201
26
80
  raise "Error publishing to #{self.class.name}: (#{response.status}) #{response.body}"
27
81
  end
28
- message = response.data
82
+ API::Message.new(@spire, response.data)
29
83
  end
30
84
 
85
+ def subscribe(name = nil)
86
+ response = request(:subscribe, name)
87
+ unless response.status == 201
88
+ raise "Error creating subscription for #{self.name}: (#{response.status}) #{response.body}"
89
+ end
90
+ API::Subscription.new(@spire, response.data)
91
+ end
31
92
  end
32
93
 
33
94
  end
@@ -0,0 +1,27 @@
1
+ class Spire
2
+ class API
3
+ class Event < Resource
4
+ def resource_name
5
+ "event"
6
+ end
7
+ end
8
+
9
+ class Message < Event
10
+ def resource_name
11
+ "message"
12
+ end
13
+ end
14
+
15
+ class Join < Event
16
+ def resource_name
17
+ "join"
18
+ end
19
+ end
20
+
21
+ class Part < Event
22
+ def resource_name
23
+ "part"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,9 +1,13 @@
1
1
  class Spire
2
2
  class API
3
- class Message < Resource
3
+
4
+ class Member < Resource
4
5
  def resource_name
5
- "message"
6
+ "member"
6
7
  end
8
+
9
+
7
10
  end
11
+
8
12
  end
9
13
  end
@@ -0,0 +1,100 @@
1
+ class Spire
2
+ class API
3
+
4
+ class Notification < Resource
5
+ def resource_name
6
+ "notification"
7
+ end
8
+
9
+ define_request(:push) do |options|
10
+ {
11
+ :method => :post,
12
+ :url => @url,
13
+ :body => {
14
+ :device_tokens => options[:device_tokens],
15
+ :message => options[:message]
16
+ }.to_json,
17
+ :headers => {
18
+ "Authorization" => "Capability #{@capabilities["push"]}",
19
+ "Accept" => @spire.mediaType("notification"),
20
+ "Content-Type" => @spire.mediaType("notification")
21
+ }
22
+ }
23
+ end
24
+
25
+ define_request(:devices) do
26
+ devices = properties["resources"]["devices"]
27
+ capability = devices["capabilities"]["devices"]
28
+ {
29
+ :method => :get,
30
+ :url => devices["url"],
31
+ :headers => {
32
+ "Authorization" => "Capability #{capability}",
33
+ "Accept" => "application/json"
34
+ }
35
+ }
36
+ end
37
+
38
+ define_request(:register_device) do |data|
39
+ devices = properties["resources"]["devices"]
40
+ capability = devices["capabilities"]["register_device"]
41
+ {
42
+ :method => :put,
43
+ :url => devices["url"],
44
+ :body => data.to_json,
45
+ :headers => {
46
+ "Authorization" => "Capability #{capability}",
47
+ "Accept" => "application/json",
48
+ "Content-Type" => "application/json"
49
+ }
50
+ }
51
+ end
52
+
53
+ define_request(:remove_device) do |data|
54
+ devices = properties["resources"]["devices"]
55
+ capability = devices["capabilities"]["remove_device"]
56
+ {
57
+ :method => :delete,
58
+ :url => devices["url"],
59
+ :body => data.to_json,
60
+ :headers => {
61
+ "Authorization" => "Capability #{capability}",
62
+ "Accept" => "application/json",
63
+ "Content-Type" => "application/json"
64
+ }
65
+ }
66
+ end
67
+
68
+ def send_notification(options={})
69
+ response = request(:push, options)
70
+ unless response.status == 200
71
+ raise "Error sending push notification #{self.class.name}: (#{response.status}) #{response.body}"
72
+ end
73
+ response.data
74
+ end
75
+
76
+ def devices!
77
+ response = request(:devices)
78
+ unless response.status == 200
79
+ raise "Error getting device list #{self.class.name}: (#{response.status}) #{response.body}"
80
+ end
81
+ response.data["devices"]
82
+ end
83
+
84
+ def register_device(device_token)
85
+ response = request(:register_device, :token => device_token)
86
+ unless response.status == 200
87
+ raise "Error adding device #{self.class.name}: (#{response.status}) #{response.body}"
88
+ end
89
+ token = response.data["token"]
90
+ devices[token] = response.data
91
+ end
92
+
93
+ def devices
94
+ @devices ||= {}
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -65,10 +65,6 @@ class Spire
65
65
  properties[name]
66
66
  end
67
67
 
68
- def inspect
69
- "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
70
- end
71
-
72
68
  def get
73
69
  response = request(:get)
74
70
  unless response.status == 200
@@ -33,14 +33,20 @@ class Spire
33
33
  }
34
34
  end
35
35
 
36
- define_request(:create_channel) do |name|
36
+ define_request(:create_channel) do |name, limit, ttl|
37
37
  collection = @resources["channels"]
38
38
  capability = collection["capabilities"]["create"]
39
39
  url = collection["url"]
40
+
41
+ body = {
42
+ :name => name,
43
+ :limit => limit,
44
+ :ttl => ttl
45
+ }.to_json
40
46
  {
41
47
  :method => :post,
42
48
  :url => url,
43
- :body => { :name => name }.to_json,
49
+ :body => body,
44
50
  :headers => {
45
51
  "Authorization" => "Capability #{capability}",
46
52
  "Accept" => @spire.mediaType("channel"),
@@ -63,7 +69,13 @@ class Spire
63
69
  }
64
70
  end
65
71
 
66
- define_request(:create_subscription) do |subscription_name, channel_urls|
72
+ define_request(:create_subscription) do |options|
73
+ name = options[:name]
74
+ channel_urls = options[:channel_urls]
75
+ expiration = options[:expiration]
76
+ device_token = options[:device_token]
77
+ notification_name = options[:notification_name]
78
+
67
79
  collection = @resources["subscriptions"]
68
80
  capability = collection["capabilities"]["create"]
69
81
  url = collection["url"]
@@ -72,7 +84,10 @@ class Spire
72
84
  :url => url,
73
85
  :body => {
74
86
  :channels => channel_urls,
75
- :name => subscription_name
87
+ :name => name,
88
+ :expiration => expiration,
89
+ :device_token => device_token,
90
+ :notification_name => notification_name
76
91
  }.to_json,
77
92
  :headers => {
78
93
  "Authorization" => "Capability #{capability}",
@@ -96,6 +111,81 @@ class Spire
96
111
  }
97
112
  }
98
113
  end
114
+
115
+ define_request(:create_notification) do |options|
116
+ collection = @resources["notifications"]
117
+ capability = collection["capabilities"]["create"]
118
+ url = collection["url"]
119
+ {
120
+ :method => :post,
121
+ :url => url,
122
+ :body => {
123
+ :name => options[:name],
124
+ :mode => options[:mode]
125
+ }.to_json,
126
+ :headers => {
127
+ "Authorization" => "Capability #{capability}",
128
+ "Accept" => @spire.mediaType("notification"),
129
+ "Content-Type" => @spire.mediaType("notification")
130
+ }
131
+ }
132
+ end
133
+
134
+ define_request(:notifications) do
135
+ collection = @resources["notifications"]
136
+ capability = collection["capabilities"]["all"]
137
+ url = collection["url"]
138
+ {
139
+ :method => :get,
140
+ :url => url,
141
+ :headers => {
142
+ "Authorization" => "Capability #{capability}",
143
+ "Accept" => @spire.mediaType("notifications"),
144
+ }
145
+ }
146
+ end
147
+
148
+ define_request(:applications) do
149
+ collection = @resources["applications"]
150
+ capability = collection["capabilities"]["all"]
151
+ request = {
152
+ :method => :get,
153
+ :url => collection["url"],
154
+ :headers => {
155
+ "Authorization" => "Capability #{capability}",
156
+ "Accept" => @spire.mediaType("applications"),
157
+ }
158
+ }
159
+ end
160
+
161
+ define_request(:application_by_name) do |name|
162
+ collection = @resources["applications"]
163
+ capability = collection["capabilities"]["get_by_name"]
164
+ url = collection["url"]
165
+ request = {
166
+ :method => :get,
167
+ :url => url,
168
+ :query => {:name => name},
169
+ :headers => {
170
+ "Authorization" => "Capability #{capability}",
171
+ "Accept" => @spire.mediaType("applications"),
172
+ }
173
+ }
174
+ end
175
+
176
+ define_request(:create_application) do |data|
177
+ collection = @resources["applications"]
178
+ {
179
+ :method => :post,
180
+ :url => collection["url"],
181
+ :body => data.to_json,
182
+ :headers => {
183
+ "Authorization" => "Capability #{collection["capabilities"]["create"]}",
184
+ "Accept" => @spire.mediaType("application"),
185
+ "Content-Type" => @spire.mediaType("application")
186
+ }
187
+ }
188
+ end
99
189
 
100
190
  attr_reader :url, :resources, :schema, :capabilities, :capability
101
191
 
@@ -116,8 +206,10 @@ class Spire
116
206
  @account = API::Account.new(@spire, @resources["account"]).get
117
207
  end
118
208
 
119
- def create_channel(name)
120
- response = request(:create_channel, name)
209
+ def create_channel(name, options={})
210
+ limit = options[:limit]
211
+ ttl = options[:ttl]
212
+ response = request(:create_channel, name, limit, ttl)
121
213
  unless response.status == 201
122
214
  raise "Error creating Channel: (#{response.status}) #{response.body}"
123
215
  end
@@ -125,20 +217,86 @@ class Spire
125
217
  channels[name] = API::Channel.new(@spire, properties)
126
218
  end
127
219
 
128
- def create_subscription(subscription_name, channel_names)
129
- channel_urls = channel_names.flatten.map { |name| self.channels[name].url rescue nil }.compact
130
- response = request(:create_subscription, subscription_name, channel_urls)
220
+ def create_subscription(subscription_name, channel_names, expiration=nil, device_token=nil, notification_name=nil, second_try=false)
221
+ channel_urls = channel_names.flatten.map { |name| self.channels[name].url rescue nil }
222
+ if channel_urls.size != channel_urls.compact.size
223
+ if !second_try
224
+ self.channels!
225
+ return create_subscription(subscription_name, channel_names, expiration, device_token, notification_name, true)
226
+ else
227
+ channel_urls = channel_urls.compact
228
+ end
229
+ end
230
+ response = request(:create_subscription, {
231
+ :name => subscription_name,
232
+ :channel_urls => channel_urls,
233
+ :expiration => expiration,
234
+ :device_token => device_token,
235
+ :notification_name => notification_name
236
+ })
237
+
131
238
  unless response.status == 201
132
239
  raise "Error creating Subscription: (#{response.status}) #{response.body}"
133
240
  end
134
241
  data = response.data
135
- subscription = API::Subscription.new(@spire, data)
242
+ subscription = API::Subscription.new(@spire, data)
136
243
  if subscription_name
137
244
  subscriptions[data["name"]] = subscription
138
245
  end
139
246
  subscription
140
247
  end
141
248
 
249
+ def get_application(name)
250
+ response = request(:application_by_name, name)
251
+ unless response.status == 200
252
+ raise "Error finding application with name #{name}: (#{response.status}) #{response.body}"
253
+ end
254
+ properties = response.data[name]
255
+ app = API::Application.new(@spire, properties)
256
+ @applications[name] = app if @applications.is_a?(Hash)
257
+ app
258
+ end
259
+
260
+ def create_application(name, data = {})
261
+ data[:name] = name
262
+ response = request(:create_application, data)
263
+ unless response.status == 201
264
+ raise "Error creating Application (#{response.status}) #{response.body}"
265
+ end
266
+ properties = response.data
267
+ applications[name] = API::Application.new(@spire, properties)
268
+ end
269
+
270
+ def applications!
271
+ response = request(:applications)
272
+ unless response.status == 200
273
+ raise "Error retrieving Applications (#{response.status}) #{response.body}"
274
+ end
275
+ applications_data = response.data
276
+ @applications = {}
277
+ applications_data.each do |name, properties|
278
+ @applications[name] = API::Application.new(@spire, properties)
279
+ end
280
+ @applications
281
+ end
282
+
283
+ def applications
284
+ @applications ||= applications!
285
+ end
286
+
287
+ def create_notification(options={})
288
+ response = request(:create_notification, options)
289
+ unless response.status == 201
290
+ raise "Error creating Notification: (#{response.status}) #{response.body}"
291
+ end
292
+ data = response.data
293
+ notification = API::Notification.new(@spire, data)
294
+ if options[:name]
295
+ notifications[data["name"]] = notification
296
+ end
297
+ notification
298
+ end
299
+
142
300
  def channels!
143
301
  response = request(:channels)
144
302
  unless response.status == 200
@@ -171,6 +329,22 @@ class Spire
171
329
  end
172
330
  @subscriptions
173
331
  end
332
+
333
+ def notifications
334
+ @notifications ||= notifications!
335
+ end
336
+
337
+ def notifications!
338
+ response = request(:notifications)
339
+ unless response.status == 200
340
+ raise "Error retrieving Notifications: (#{response.status}) #{response.body}"
341
+ end
342
+ @notifications = {}
343
+ response.data.each do |name, properties|
344
+ @notifications[name] = API::Notification.new(@spire, properties)
345
+ end
346
+ @notifications
347
+ end
174
348
 
175
349
  end
176
350
 
@@ -8,65 +8,91 @@ class Spire
8
8
  "subscription"
9
9
  end
10
10
 
11
- define_request(:messages) do |options|
11
+ define_request(:events) do |options|
12
12
  {
13
13
  :method => :get,
14
14
  :url => @url,
15
15
  :query => {
16
16
  "timeout" => options[:timeout],
17
- "last-message" => options[:last],
17
+ "last" => options[:last],
18
18
  "order-by" => options[:order_by],
19
19
  "delay" => options[:delay]
20
20
  },
21
21
  :headers => {
22
- "Authorization" => "Capability #{@capabilities["messages"]}",
22
+ "Authorization" => "Capability #{@capabilities["events"]}",
23
23
  "Accept" => @spire.mediaType("events")
24
24
  }
25
25
  }
26
26
  end
27
27
 
28
+ EVENT_TYPES = ["message", "join", "part"]
29
+
28
30
  def listeners
29
- @listeners ||= []
31
+ if @listeners
32
+ @listeners
33
+ else
34
+ @listeners = { }
35
+ EVENT_TYPES.each do |type|
36
+ @listeners[type] = []
37
+ end
38
+ @listeners
39
+ end
30
40
  end
31
41
 
32
- def add_listener(&block)
33
- listeners << block
42
+ def add_listener(type="message", &block)
43
+ type.downcase!
44
+ if !EVENT_TYPES.include?(type)
45
+ throw "Listener type must be one of #{EVENT_TYPES}"
46
+ end
47
+
48
+ listeners[type] << block
34
49
  block
35
50
  end
36
51
 
37
- def retrieve_messages(options={})
52
+ def retrieve_events(options={})
38
53
  options[:last] ||= "0"
39
54
  options[:delay] ||= 0
40
55
  options[:order_by] ||= "desc"
41
56
 
42
- response = request(:messages, options)
57
+ response = request(:events, options)
43
58
  unless response.status == 200
44
59
  raise "Error retrieving messages from #{self.class.name}: (#{response.status}) #{response.body}"
45
60
  end
46
- messages = response.data["messages"].map do |message|
47
- API::Message.new(@spire, message)
48
- end
49
- @last = messages.last.timestamp unless messages.empty?
50
- messages.each do |message|
51
- listeners.each do |listener|
52
- listener.call(message)
61
+ @last = response.data["last"] if response.data and response.data["last"]
62
+
63
+ event_hash = {
64
+ :first => response.data["first"],
65
+ :last => response.data["last"]
66
+ }
67
+
68
+ EVENT_TYPES.each do |type|
69
+ type_pl = "#{type}s"
70
+ event_hash[type_pl.to_sym] = []
71
+ response.data[type_pl].each do |event|
72
+ klass_name = type.capitalize
73
+ klass = API.const_get(klass_name)
74
+ event_obj = klass.new(@spire, event)
75
+ event_hash[type_pl.to_sym].push(event_obj)
76
+
77
+ listeners[type].each do |listener|
78
+ listener.call(event_obj)
79
+ end
53
80
  end
54
81
  end
55
- messages
82
+ event_hash
56
83
  end
57
84
 
58
85
  def poll(options={})
59
86
  # timeout option of 0 means no long poll,
60
87
  # so we force it here.
61
88
  options[:timeout] = 0
62
- options[:last] = @last
63
- retrieve_messages(options)
89
+ long_poll(options)
64
90
  end
65
91
 
66
92
  def long_poll(options={})
67
93
  options[:timeout] ||= 30
68
94
  options[:last] = @last
69
- retrieve_messages(options)
95
+ retrieve_events(options)
70
96
  end
71
97
 
72
98
  def listen(options={})
@@ -76,6 +102,5 @@ class Spire
76
102
  end
77
103
 
78
104
  end
79
-
80
105
  end
81
106
  end
data/lib/spire_io.rb CHANGED
@@ -14,7 +14,9 @@ class Spire
14
14
  @api = Spire::API.new(url)
15
15
  @url = url
16
16
  @channel_error_counts = {}
17
+ @application_error_counts = {}
17
18
  @subscription_error_counts = {}
19
+ @notification_error_counts = {}
18
20
  discover
19
21
  end
20
22
 
@@ -143,6 +145,68 @@ class Spire
143
145
  def subscriptions
144
146
  @session.subscriptions.values
145
147
  end
148
+
149
+
150
+ def notification(name, mode="development")
151
+ Notification.new(
152
+ @session.notifications[name] || find_or_create_notification(name, ssl_cert)
153
+ )
154
+ end
155
+
156
+ def find_or_create_notification(notification_name, mode)
157
+ @notification_error_counts[notification_name] ||= 0
158
+ begin
159
+ return @session.create_notification(
160
+ :name => notification_name,
161
+ :mode => mode
162
+ )
163
+ rescue => error
164
+ if error.message =~ /409/
165
+
166
+ if notification = @session.notification![notification_name]
167
+ return notification
168
+ else
169
+ @notification_error_counts[notification_name] += 1
170
+ retry unless @notification_error_counts >= RETRY_CREATION_LIMIT
171
+ end
172
+
173
+ else
174
+ raise error
175
+ end
176
+ end
177
+ end
178
+
179
+
180
+ def applications
181
+ @session.applications
182
+ end
183
+
184
+ def applications!
185
+ @session.applications!
186
+ end
187
+
188
+ # Creates an application on spire. Returns an Application object. Will retry on a 409.
189
+ # @param [String] Name of the application to find/create
190
+ def find_or_create_application(name)
191
+ @application_error_counts[name] ||= 0
192
+ begin
193
+ return @session.create_application(name)
194
+ # TODO custom error class for Conflict, which we can
195
+ # then match here, instead of testing for error message
196
+ rescue => error
197
+ if error.message =~ /409/
198
+ # Dear retry, I love you. Affectionately, Matthew.
199
+ if application = @session.applications![name]
200
+ return application
201
+ else
202
+ @application_error_counts[name] += 1
203
+ retry unless @application_error_counts[name] >= RETRY_CREATION_LIMIT
204
+ end
205
+ else
206
+ raise error
207
+ end
208
+ end
209
+ end
146
210
 
147
211
  # Returns a billing object than contains a list of all the plans available
148
212
  # @param [String] info optional object description
@@ -210,7 +274,7 @@ class Spire
210
274
  # misnamed method, here for backompat. Should be something like #get_messages,
211
275
  # because it only makes one request.
212
276
  def listen(options={})
213
- long_poll(options).map {|message| message["content"] }
277
+ long_poll(options)
214
278
  end
215
279
 
216
280
  # wraps the underlying Subscription#add_listener to
@@ -221,7 +285,7 @@ class Spire
221
285
  listener_name ||= generate_listener_name
222
286
  listener = wrap_listener(&block)
223
287
  listeners[listener_name] = listener
224
- __getobj__.add_listener(&listener)
288
+ __getobj__.add_listener("message", &listener)
225
289
  end
226
290
 
227
291
  def remove_listener(arg)
@@ -233,7 +297,7 @@ class Spire
233
297
  end
234
298
 
235
299
  if listener
236
- __getobj__.listeners.delete(listener)
300
+ __getobj__.listeners["message"].delete(listener)
237
301
  end
238
302
  end
239
303
 
@@ -273,4 +337,16 @@ class Spire
273
337
 
274
338
  end
275
339
 
340
+ class Notification < SimpleDelegator
341
+
342
+ # this is required because Delegator's method_missing relies
343
+ # on the object having a method defined, but in this case
344
+ # the API::Subscription is also using method_missing
345
+ def name
346
+ __getobj__.name
347
+ end
348
+
349
+
350
+ end
351
+
276
352
  end
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spire_io
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 0
9
- - 0
10
- version: 1.0.0
8
+ - 2
9
+ version: "1.2"
11
10
  platform: ruby
12
11
  authors:
13
12
  - Dan Yoder
@@ -17,7 +16,7 @@ autorequire:
17
16
  bindir: bin
18
17
  cert_chain: []
19
18
 
20
- date: 2012-03-08 00:00:00 -08:00
19
+ date: 2012-05-07 00:00:00 -07:00
21
20
  default_executable:
22
21
  dependencies:
23
22
  - !ruby/object:Gem::Dependency
@@ -142,8 +141,11 @@ extra_rdoc_files: []
142
141
 
143
142
  files:
144
143
  - lib/spire/api/account.rb
144
+ - lib/spire/api/application.rb
145
145
  - lib/spire/api/channel.rb
146
- - lib/spire/api/message.rb
146
+ - lib/spire/api/event.rb
147
+ - lib/spire/api/member.rb
148
+ - lib/spire/api/notification.rb
147
149
  - lib/spire/api/requestable.rb
148
150
  - lib/spire/api/resource.rb
149
151
  - lib/spire/api/session.rb