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 +27 -6
- data/lib/spire/api/application.rb +325 -0
- data/lib/spire/api/channel.rb +62 -1
- data/lib/spire/api/event.rb +27 -0
- data/lib/spire/api/{message.rb → member.rb} +6 -2
- data/lib/spire/api/notification.rb +100 -0
- data/lib/spire/api/resource.rb +0 -4
- data/lib/spire/api/session.rb +184 -10
- data/lib/spire/api/subscription.rb +45 -20
- data/lib/spire_io.rb +79 -3
- metadata +8 -6
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/
|
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
|
data/lib/spire/api/channel.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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
|
data/lib/spire/api/resource.rb
CHANGED
data/lib/spire/api/session.rb
CHANGED
@@ -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 =>
|
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 |
|
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 =>
|
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
|
-
|
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 }
|
130
|
-
|
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(:
|
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
|
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["
|
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
|
-
|
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
|
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(:
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
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
|
-
|
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)
|
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:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
|
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-
|
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/
|
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
|