spire_io 1.0.0 → 1.2

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.
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