nylas 2.0.1 → 3.0.0
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.
- checksums.yaml +4 -4
- data/lib/account.rb +2 -2
- data/lib/api_account.rb +1 -1
- data/lib/api_thread.rb +1 -1
- data/lib/calendar.rb +1 -1
- data/lib/contact.rb +2 -2
- data/lib/draft.rb +9 -16
- data/lib/event.rb +2 -2
- data/lib/file.rb +3 -3
- data/lib/folder.rb +1 -1
- data/lib/label.rb +1 -1
- data/lib/message.rb +5 -6
- data/lib/mixins.rb +1 -1
- data/lib/nylas.rb +56 -46
- data/lib/parameters.rb +1 -1
- data/lib/restful_model.rb +7 -7
- data/lib/restful_model_collection.rb +5 -5
- data/lib/time_attr_accessor.rb +2 -2
- data/lib/version.rb +2 -2
- metadata +102 -58
- data/LICENSE.txt +0 -22
- data/README.md +0 -599
- data/lib/inbox.rb +0 -375
data/lib/inbox.rb
DELETED
@@ -1,375 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'rest-client'
|
3
|
-
require 'yajl'
|
4
|
-
require 'em-http'
|
5
|
-
require 'ostruct'
|
6
|
-
require 'active_support/core_ext/hash'
|
7
|
-
|
8
|
-
require 'account'
|
9
|
-
require 'api_account'
|
10
|
-
require 'api_thread'
|
11
|
-
require 'calendar'
|
12
|
-
require 'account'
|
13
|
-
require 'message'
|
14
|
-
require 'draft'
|
15
|
-
require 'contact'
|
16
|
-
require 'file'
|
17
|
-
require 'calendar'
|
18
|
-
require 'event'
|
19
|
-
require 'folder'
|
20
|
-
require 'restful_model'
|
21
|
-
require 'restful_model_collection'
|
22
|
-
require 'version'
|
23
|
-
|
24
|
-
module Inbox
|
25
|
-
Error = Class.new(::StandardError)
|
26
|
-
AccessDenied = Class.new(Error)
|
27
|
-
ResourceNotFound = Class.new(Error)
|
28
|
-
NoAuthToken = Class.new(Error)
|
29
|
-
UnexpectedAccountAction = Class.new(Error)
|
30
|
-
UnexpectedResponse = Class.new(Error)
|
31
|
-
class APIError < Error
|
32
|
-
attr_accessor :error_type
|
33
|
-
attr_accessor :server_error
|
34
|
-
|
35
|
-
def initialize(type, error, server_error = nil)
|
36
|
-
super(error)
|
37
|
-
self.error_type = type
|
38
|
-
self.server_error = server_error
|
39
|
-
end
|
40
|
-
end
|
41
|
-
InvalidRequest = Class.new(APIError)
|
42
|
-
MessageRejected = Class.new(APIError)
|
43
|
-
SendingQuotaExceeded = Class.new(APIError)
|
44
|
-
ServiceUnavailable = Class.new(APIError)
|
45
|
-
|
46
|
-
def self.interpret_http_status(result)
|
47
|
-
# Handle HTTP errors and RestClient errors
|
48
|
-
raise ResourceNotFound.new if result.code.to_i == 404
|
49
|
-
raise AccessDenied.new if result.code.to_i == 403
|
50
|
-
end
|
51
|
-
|
52
|
-
HTTP_CODE_TO_EXCEPTIONS = {
|
53
|
-
400 => InvalidRequest,
|
54
|
-
402 => MessageRejected,
|
55
|
-
403 => AccessDenied,
|
56
|
-
404 => ResourceNotFound,
|
57
|
-
429 => SendingQuotaExceeded,
|
58
|
-
503 => ServiceUnavailable
|
59
|
-
}.freeze
|
60
|
-
|
61
|
-
def self.http_code_to_exception(http_code)
|
62
|
-
HTTP_CODE_TO_EXCEPTIONS.fetch(http_code, APIError)
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.interpret_response(result, result_content, options = {})
|
66
|
-
# Handle HTTP errors
|
67
|
-
self.interpret_http_status(result)
|
68
|
-
|
69
|
-
# Handle content expectation errors
|
70
|
-
raise UnexpectedResponse.new if options[:expected_class] && result_content.empty?
|
71
|
-
json = options[:result_parsed]? result_content : JSON.parse(result_content)
|
72
|
-
if json.is_a?(Hash) && (json['type'] == 'api_error' or json['type'] == 'invalid_request_error')
|
73
|
-
exc = Inbox.http_code_to_exception(result.code.to_i)
|
74
|
-
raise exc.new(json['type'], json['message'])
|
75
|
-
end
|
76
|
-
raise UnexpectedResponse.new(result.msg) if result.is_a?(Net::HTTPClientError)
|
77
|
-
raise UnexpectedResponse.new if options[:expected_class] && !json.is_a?(options[:expected_class])
|
78
|
-
json
|
79
|
-
|
80
|
-
rescue JSON::ParserError => e
|
81
|
-
# Handle parsing errors
|
82
|
-
raise UnexpectedResponse.new(e.message)
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
class API
|
87
|
-
attr_accessor :api_server
|
88
|
-
attr_reader :access_token
|
89
|
-
attr_reader :app_id
|
90
|
-
attr_reader :app_secret
|
91
|
-
|
92
|
-
def initialize(app_id, app_secret, access_token = nil, api_server = 'https://api.nylas.com',
|
93
|
-
service_domain = 'api.nylas.com')
|
94
|
-
raise "When overriding the Inbox API server address, you must include https://" unless api_server.include?('://')
|
95
|
-
@api_server = api_server
|
96
|
-
@access_token = access_token
|
97
|
-
@app_secret = app_secret
|
98
|
-
@app_id = app_id
|
99
|
-
@service_domain = service_domain
|
100
|
-
@version = Inbox::VERSION
|
101
|
-
|
102
|
-
if ::RestClient.before_execution_procs.empty?
|
103
|
-
::RestClient.add_before_execution_proc do |req, params|
|
104
|
-
req.add_field('X-Inbox-API-Wrapper', 'ruby')
|
105
|
-
req['User-Agent'] = "Nylas Ruby SDK #{@version} - #{RUBY_VERSION}"
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def url_for_path(path)
|
111
|
-
raise NoAuthToken.new if @access_token == nil and (@app_secret != nil or @app_id != nil)
|
112
|
-
protocol, domain = @api_server.split('//')
|
113
|
-
"#{protocol}//#{@access_token}:@#{domain}#{path}"
|
114
|
-
end
|
115
|
-
|
116
|
-
def url_for_authentication(redirect_uri, login_hint = '', options = {})
|
117
|
-
params = {
|
118
|
-
:client_id => @app_id,
|
119
|
-
:trial => options.fetch(:trial, false),
|
120
|
-
:response_type => 'code',
|
121
|
-
:scope => 'email',
|
122
|
-
:login_hint => login_hint,
|
123
|
-
:redirect_uri => redirect_uri,
|
124
|
-
}
|
125
|
-
|
126
|
-
if options.has_key?(:state) then
|
127
|
-
params[:state] = options[:state]
|
128
|
-
end
|
129
|
-
|
130
|
-
"https://#{@service_domain}/oauth/authorize?" + params.to_query
|
131
|
-
end
|
132
|
-
|
133
|
-
def url_for_management
|
134
|
-
protocol, domain = @api_server.split('//')
|
135
|
-
accounts_path = "#{protocol}//#{@app_secret}:@#{domain}/a/#{@app_id}/accounts"
|
136
|
-
end
|
137
|
-
|
138
|
-
def set_access_token(token)
|
139
|
-
@access_token = token
|
140
|
-
end
|
141
|
-
|
142
|
-
def token_for_code(code)
|
143
|
-
data = {
|
144
|
-
'client_id' => app_id,
|
145
|
-
'client_secret' => app_secret,
|
146
|
-
'grant_type' => 'authorization_code',
|
147
|
-
'code' => code
|
148
|
-
}
|
149
|
-
|
150
|
-
::RestClient.post("https://#{@service_domain}/oauth/token", data) do |response, request, result|
|
151
|
-
json = Inbox.interpret_response(result, response, :expected_class => Object)
|
152
|
-
return json['access_token']
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# API Methods
|
157
|
-
def threads
|
158
|
-
@threads ||= RestfulModelCollection.new(Thread, self)
|
159
|
-
end
|
160
|
-
|
161
|
-
def messages
|
162
|
-
@messages ||= RestfulModelCollection.new(Message, self)
|
163
|
-
end
|
164
|
-
|
165
|
-
def files
|
166
|
-
@files ||= RestfulModelCollection.new(File, self)
|
167
|
-
end
|
168
|
-
|
169
|
-
def drafts
|
170
|
-
@drafts ||= RestfulModelCollection.new(Draft, self)
|
171
|
-
end
|
172
|
-
|
173
|
-
def contacts
|
174
|
-
@contacts ||= RestfulModelCollection.new(Contact, self)
|
175
|
-
end
|
176
|
-
|
177
|
-
def calendars
|
178
|
-
@calendars ||= RestfulModelCollection.new(Calendar, self)
|
179
|
-
end
|
180
|
-
|
181
|
-
def events
|
182
|
-
@events ||= RestfulModelCollection.new(Event, self)
|
183
|
-
end
|
184
|
-
|
185
|
-
def folders
|
186
|
-
@folders ||= RestfulModelCollection.new(Folder, self)
|
187
|
-
end
|
188
|
-
|
189
|
-
def labels
|
190
|
-
@labels ||= RestfulModelCollection.new(Label, self)
|
191
|
-
end
|
192
|
-
|
193
|
-
def account
|
194
|
-
path = self.url_for_path("/account")
|
195
|
-
|
196
|
-
RestClient.get(path, {}) do |response,request,result|
|
197
|
-
json = Inbox.interpret_response(result, response, {:expected_class => Object})
|
198
|
-
model = APIAccount.new(self)
|
199
|
-
model.inflate(json)
|
200
|
-
model
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def using_hosted_api?
|
205
|
-
return !@app_id.nil?
|
206
|
-
end
|
207
|
-
|
208
|
-
def accounts
|
209
|
-
if self.using_hosted_api?
|
210
|
-
@accounts ||= ManagementModelCollection.new(Account, self)
|
211
|
-
else
|
212
|
-
@accounts ||= RestfulModelCollection.new(APIAccount, self)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def latest_cursor
|
217
|
-
# Get the cursor corresponding to a specific timestamp.
|
218
|
-
path = self.url_for_path("/delta/latest_cursor")
|
219
|
-
|
220
|
-
cursor = nil
|
221
|
-
|
222
|
-
RestClient.post(path, :content_type => :json) do |response,request,result|
|
223
|
-
json = Inbox.interpret_response(result, response, {:expected_class => Object})
|
224
|
-
cursor = json["cursor"]
|
225
|
-
end
|
226
|
-
|
227
|
-
cursor
|
228
|
-
end
|
229
|
-
|
230
|
-
OBJECTS_TABLE = {
|
231
|
-
"account" => Inbox::Account,
|
232
|
-
"calendar" => Inbox::Calendar,
|
233
|
-
"draft" => Inbox::Draft,
|
234
|
-
"thread" => Inbox::Thread,
|
235
|
-
"contact" => Inbox::Contact,
|
236
|
-
"event" => Inbox::Event,
|
237
|
-
"file" => Inbox::File,
|
238
|
-
"message" => Inbox::Message,
|
239
|
-
"folder" => Inbox::Folder,
|
240
|
-
"label" => Inbox::Label,
|
241
|
-
}
|
242
|
-
|
243
|
-
# It's possible to ask the API to expand objects.
|
244
|
-
# In this case, we do the right thing and return
|
245
|
-
# an expanded object.
|
246
|
-
EXPANDED_OBJECTS_TABLE = {
|
247
|
-
"message" => Inbox::ExpandedMessage,
|
248
|
-
}
|
249
|
-
|
250
|
-
def _build_exclude_types(exclude_types)
|
251
|
-
exclude_string = "&exclude_types="
|
252
|
-
|
253
|
-
exclude_types.each do |value|
|
254
|
-
count = 0
|
255
|
-
if OBJECTS_TABLE.has_value?(value)
|
256
|
-
param_name = OBJECTS_TABLE.key(value)
|
257
|
-
exclude_string += "#{param_name},"
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
exclude_string = exclude_string[0..-2]
|
262
|
-
end
|
263
|
-
|
264
|
-
def deltas(cursor, exclude_types=[], expanded_view=false)
|
265
|
-
return enum_for(:deltas, cursor, exclude_types, expanded_view) unless block_given?
|
266
|
-
|
267
|
-
exclude_string = ""
|
268
|
-
|
269
|
-
if exclude_types.any?
|
270
|
-
exclude_string = _build_exclude_types(exclude_types)
|
271
|
-
end
|
272
|
-
|
273
|
-
# loop and yield deltas until we've come to the end.
|
274
|
-
loop do
|
275
|
-
path = self.url_for_path("/delta?exclude_folders=false&cursor=#{cursor}#{exclude_string}")
|
276
|
-
if expanded_view
|
277
|
-
path += '&view=expanded'
|
278
|
-
end
|
279
|
-
|
280
|
-
json = nil
|
281
|
-
|
282
|
-
RestClient.get(path) do |response,request,result|
|
283
|
-
json = Inbox.interpret_response(result, response, {:expected_class => Object})
|
284
|
-
end
|
285
|
-
|
286
|
-
start_cursor = json["cursor_start"]
|
287
|
-
end_cursor = json["cursor_end"]
|
288
|
-
|
289
|
-
json["deltas"].each do |delta|
|
290
|
-
if not OBJECTS_TABLE.has_key?(delta['object'])
|
291
|
-
next
|
292
|
-
end
|
293
|
-
|
294
|
-
cls = OBJECTS_TABLE[delta['object']]
|
295
|
-
if EXPANDED_OBJECTS_TABLE.has_key?(delta['object']) and expanded_view
|
296
|
-
cls = EXPANDED_OBJECTS_TABLE[delta['object']]
|
297
|
-
end
|
298
|
-
|
299
|
-
obj = cls.new(self)
|
300
|
-
|
301
|
-
case delta["event"]
|
302
|
-
when 'create', 'modify'
|
303
|
-
obj.inflate(delta['attributes'])
|
304
|
-
obj.cursor = delta["cursor"]
|
305
|
-
yield delta["event"], obj
|
306
|
-
when 'delete'
|
307
|
-
obj.id = delta["id"]
|
308
|
-
obj.cursor = delta["cursor"]
|
309
|
-
yield delta["event"], obj
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
break if start_cursor == end_cursor
|
314
|
-
cursor = end_cursor
|
315
|
-
end
|
316
|
-
end
|
317
|
-
|
318
|
-
def delta_stream(cursor, exclude_types=[], timeout=0, expanded_view=false)
|
319
|
-
raise 'Please provide a block for receiving the delta objects' if !block_given?
|
320
|
-
|
321
|
-
exclude_string = ""
|
322
|
-
|
323
|
-
if exclude_types.any?
|
324
|
-
exclude_string = _build_exclude_types(exclude_types)
|
325
|
-
end
|
326
|
-
|
327
|
-
# loop and yield deltas indefinitely.
|
328
|
-
path = self.url_for_path("/delta/streaming?exclude_folders=false&cursor=#{cursor}#{exclude_string}")
|
329
|
-
if expanded_view
|
330
|
-
path += '&view=expanded'
|
331
|
-
end
|
332
|
-
|
333
|
-
parser = Yajl::Parser.new(:symbolize_keys => false)
|
334
|
-
parser.on_parse_complete = proc do |data|
|
335
|
-
delta = Inbox.interpret_response(OpenStruct.new(:code => '200'), data, {:expected_class => Object, :result_parsed => true})
|
336
|
-
|
337
|
-
if not OBJECTS_TABLE.has_key?(delta['object'])
|
338
|
-
next
|
339
|
-
end
|
340
|
-
|
341
|
-
cls = OBJECTS_TABLE[delta['object']]
|
342
|
-
if EXPANDED_OBJECTS_TABLE.has_key?(delta['object']) and expanded_view
|
343
|
-
cls = EXPANDED_OBJECTS_TABLE[delta['object']]
|
344
|
-
end
|
345
|
-
|
346
|
-
obj = cls.new(self)
|
347
|
-
|
348
|
-
case delta["event"]
|
349
|
-
when 'create', 'modify'
|
350
|
-
obj.inflate(delta['attributes'])
|
351
|
-
obj.cursor = delta["cursor"]
|
352
|
-
yield delta["event"], obj
|
353
|
-
when 'delete'
|
354
|
-
obj.id = delta["id"]
|
355
|
-
obj.cursor = delta["cursor"]
|
356
|
-
yield delta["event"], obj
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
http = EventMachine::HttpRequest.new(path, :connect_timeout => 0, :inactivity_timeout => timeout).get(:keepalive => true)
|
361
|
-
|
362
|
-
# set a callback on the HTTP stream that parses incoming chunks as they come in
|
363
|
-
http.stream do |chunk|
|
364
|
-
parser << chunk
|
365
|
-
end
|
366
|
-
|
367
|
-
http.errback do
|
368
|
-
raise UnexpectedResponse.new http.error
|
369
|
-
end
|
370
|
-
|
371
|
-
end
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
Nylas = Inbox.clone
|