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