nylas 3.2.0 → 4.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9418683f9825515764e97bf23b26d8221583c9ca
4
- data.tar.gz: 0b6b65e34c35da57205f4b58de3f2e27ded1ae36
3
+ metadata.gz: 5fd4eb7f8982e3da95740a92b4ff5d1307d22bc7
4
+ data.tar.gz: 3806c9465a159c517fc71165570a0c787751f79f
5
5
  SHA512:
6
- metadata.gz: 75d304cd89b172f67956a4b942ded7fa25c40cb6b330fa3e7123a5daf3ae1e8c132217fd236925db6898f3870c0fc41a0b611db8dfc013433708525cdeeb7954
7
- data.tar.gz: 97d3c77893776e5d6d8aa6b5590179db2a97e50300d2b1488d7e0f03b59f9e2b001203ed925287ce64252e133440a8d043e567a32665225611468f6559a1acdb
6
+ metadata.gz: a1841957b48b50212c6026428401306cadf9c83fddb109e5567c0cb5bb5aa187e2a6383c804b546ffe1082d7bca395e73a2f63f035d20ab42a516fd44e831474
7
+ data.tar.gz: a21d29a3a95331f855b5f4a312183fea5da5dcf73de7546df452d4723b080838bdc26fe376eec1455b98b108e3d92c0e8a2ec6742e05efaa1e80d72f5ff89d61
data/lib/nylas.rb CHANGED
@@ -1,389 +1,42 @@
1
- require 'json'
2
- require 'rest-client'
3
- require 'yajl'
4
- require 'em-http'
1
+ require "json"
2
+ require "rest-client"
5
3
 
4
+ require "ostruct"
5
+ require "forwardable"
6
6
 
7
- require 'ostruct'
8
- require 'active_support/core_ext/hash'
7
+ require_relative "nylas/version"
8
+ require_relative "nylas/errors"
9
9
 
10
- require 'account'
11
- require 'api_account'
12
- require 'api_thread'
13
- require 'calendar'
14
- require 'account'
15
- require 'message'
16
- require 'expanded_message'
17
- require 'draft'
18
- require 'contact'
19
- require 'file'
20
- require 'calendar'
21
- require 'event'
22
- require 'folder'
23
- require 'restful_model'
24
- require 'restful_model_collection'
25
- require 'version'
10
+ require_relative "nylas/logging"
11
+ require_relative "nylas/registry"
12
+ require_relative "nylas/types"
13
+ require_relative "nylas/constraints"
26
14
 
27
- module Nylas
28
- Error = Class.new(::StandardError)
29
- NoAuthToken = Class.new(Error)
30
- UnexpectedAccountAction = Class.new(Error)
31
- UnexpectedResponse = Class.new(Error)
32
- class APIError < Error
33
- attr_accessor :type
34
- attr_accessor :message
35
- attr_accessor :server_error
36
-
37
- def initialize(type, message, server_error = nil)
38
- super(message)
39
- self.type = type
40
- self.message = message
41
- self.server_error = server_error
42
- end
43
- end
44
- AccessDenied = Class.new(APIError)
45
- ResourceNotFound = Class.new(APIError)
46
- InvalidRequest = Class.new(APIError)
47
- MessageRejected = Class.new(APIError)
48
- SendingQuotaExceeded = Class.new(APIError)
49
- ServiceUnavailable = Class.new(APIError)
50
- BadGateway = Class.new(APIError)
51
- InternalError = Class.new(APIError)
52
- MailProviderError = Class.new(APIError)
53
-
54
- HTTP_CODE_TO_EXCEPTIONS = {
55
- 400 => InvalidRequest,
56
- 402 => MessageRejected,
57
- 403 => AccessDenied,
58
- 404 => ResourceNotFound,
59
- 422 => MailProviderError,
60
- 429 => SendingQuotaExceeded,
61
- 500 => InternalError,
62
- 502 => BadGateway,
63
- 503 => ServiceUnavailable,
64
- }.freeze
65
-
66
- def self.http_code_to_exception(http_code)
67
- HTTP_CODE_TO_EXCEPTIONS.fetch(http_code, APIError)
68
- end
69
-
70
- def self.interpret_response(result, result_content, options = {})
71
-
72
- # We expected a certain kind of object, but the API didn't return anything
73
- raise UnexpectedResponse.new if options[:expected_class] && result_content.empty?
74
-
75
- # If it's already parsed, or if we've received an actual raw payload on success, don't parse
76
- if options[:result_parsed] || (options[:raw_response] && result.code.to_i == 200)
77
- response = result_content
78
- else
79
- response = JSON.parse(result_content)
80
- end
81
-
82
- if result.code.to_i != 200
83
- exc = Nylas.http_code_to_exception(result.code.to_i)
84
- if response.is_a?(Hash)
85
- raise exc.new(response['type'], response['message'], response.fetch('server_error', nil))
86
- end
87
- end
88
-
89
- raise UnexpectedResponse.new(result.msg) if result.is_a?(Net::HTTPClientError)
90
- raise UnexpectedResponse.new if options[:expected_class] && !response.is_a?(options[:expected_class])
91
- response
92
-
93
- rescue JSON::ParserError => e
94
- # Handle parsing errors
95
- raise UnexpectedResponse.new(e.message)
96
- end
97
-
98
-
99
- class API
100
- attr_accessor :api_server
101
- attr_reader :access_token
102
- attr_reader :app_id
103
- attr_reader :app_secret
104
-
105
- def initialize(app_id, app_secret, access_token = nil, api_server = 'https://api.nylas.com',
106
- service_domain = 'api.nylas.com')
107
- raise "When overriding the Nylas API server address, you must include https://" unless api_server.include?('://')
108
- @api_server = api_server
109
- @access_token = access_token
110
- @app_secret = app_secret
111
- @app_id = app_id
112
- @service_domain = service_domain
113
- @version = Nylas::VERSION
114
-
115
- if ::RestClient.before_execution_procs.empty?
116
- ::RestClient.add_before_execution_proc do |req, params|
117
- req.add_field('X-Nylas-API-Wrapper', 'ruby')
118
- req['User-Agent'] = "Nylas Ruby SDK #{@version} - #{RUBY_VERSION}"
119
- end
120
- end
121
- end
122
-
123
- def url_for_path(path)
124
- raise NoAuthToken.new if @access_token == nil and (@app_secret != nil or @app_id != nil)
125
- protocol, domain = @api_server.split('//')
126
- "#{protocol}//#{@access_token}:@#{domain}#{path}"
127
- end
128
-
129
- def url_for_authentication(redirect_uri, login_hint = '', options = {})
130
- params = {
131
- :client_id => @app_id,
132
- :trial => options.fetch(:trial, false),
133
- :response_type => 'code',
134
- :scope => 'email',
135
- :login_hint => login_hint,
136
- :redirect_uri => redirect_uri,
137
- }
138
-
139
- if options.has_key?(:state) then
140
- params[:state] = options[:state]
141
- end
142
-
143
- "https://#{@service_domain}/oauth/authorize?" + params.to_query
144
- end
145
-
146
- def url_for_management
147
- protocol, domain = @api_server.split('//')
148
- accounts_path = "#{protocol}//#{@app_secret}:@#{domain}/a/#{@app_id}/accounts"
149
- end
150
-
151
- def set_access_token(token)
152
- @access_token = token
153
- end
154
-
155
- def token_for_code(code)
156
- data = {
157
- 'client_id' => app_id,
158
- 'client_secret' => app_secret,
159
- 'grant_type' => 'authorization_code',
160
- 'code' => code
161
- }
162
-
163
- ::RestClient.post("https://#{@service_domain}/oauth/token", data) do |response, request, result|
164
- json = Nylas.interpret_response(result, response, :expected_class => Object)
165
- return json['access_token']
166
- end
167
- end
168
-
169
- # API Methods
170
- def threads
171
- @threads ||= RestfulModelCollection.new(Thread, self)
172
- end
173
-
174
- def messages(expanded: false)
175
- @messages ||= Hash.new do |h, is_expanded|
176
- h[is_expanded] = \
177
- if is_expanded
178
- RestfulModelCollection.new(ExpandedMessage, self, view: 'expanded')
179
- else
180
- RestfulModelCollection.new(Message, self)
181
- end
182
- end
183
- @messages[expanded]
184
- end
185
-
186
- def files
187
- @files ||= RestfulModelCollection.new(File, self)
188
- end
189
-
190
- def drafts
191
- @drafts ||= RestfulModelCollection.new(Draft, self)
192
- end
193
-
194
- def contacts
195
- @contacts ||= RestfulModelCollection.new(Contact, self)
196
- end
197
-
198
- def calendars
199
- @calendars ||= RestfulModelCollection.new(Calendar, self)
200
- end
201
-
202
- def events
203
- @events ||= RestfulModelCollection.new(Event, self)
204
- end
205
-
206
- def folders
207
- @folders ||= RestfulModelCollection.new(Folder, self)
208
- end
209
-
210
- def labels
211
- @labels ||= RestfulModelCollection.new(Label, self)
212
- end
213
-
214
- def account
215
- path = self.url_for_path("/account")
15
+ require_relative "nylas/collection"
16
+ require_relative "nylas/model"
216
17
 
217
- RestClient.get(path, {}) do |response,request,result|
218
- json = Nylas.interpret_response(result, response, {:expected_class => Object})
219
- model = APIAccount.new(self)
220
- model.inflate(json)
221
- model
222
- end
223
- end
18
+ # Attribute types supported by the API
19
+ require_relative "nylas/email_address"
20
+ require_relative "nylas/im_address"
21
+ require_relative "nylas/physical_address"
22
+ require_relative "nylas/phone_number"
23
+ require_relative "nylas/web_page"
24
+ require_relative "nylas/nylas_date"
224
25
 
225
- def using_hosted_api?
226
- return !@app_id.nil?
227
- end
26
+ # Models supported by the API
27
+ require_relative "nylas/contact"
28
+ require_relative "nylas/current_account"
228
29
 
229
- def accounts
230
- if self.using_hosted_api?
231
- @accounts ||= ManagementModelCollection.new(Account, self)
232
- else
233
- @accounts ||= RestfulModelCollection.new(APIAccount, self)
234
- end
235
- end
30
+ require_relative "nylas/http_client"
31
+ require_relative "nylas/api"
236
32
 
237
- def latest_cursor
238
- # Get the cursor corresponding to a specific timestamp.
239
- path = self.url_for_path("/delta/latest_cursor")
240
-
241
- cursor = nil
242
-
243
- RestClient.post(path, :content_type => :json) do |response,request,result|
244
- json = Nylas.interpret_response(result, response, {:expected_class => Object})
245
- cursor = json["cursor"]
246
- end
247
-
248
- cursor
249
- end
250
-
251
- OBJECTS_TABLE = {
252
- "account" => Nylas::Account,
253
- "calendar" => Nylas::Calendar,
254
- "draft" => Nylas::Draft,
255
- "thread" => Nylas::Thread,
256
- "contact" => Nylas::Contact,
257
- "event" => Nylas::Event,
258
- "file" => Nylas::File,
259
- "message" => Nylas::Message,
260
- "folder" => Nylas::Folder,
261
- "label" => Nylas::Label,
262
- }
263
-
264
- # It's possible to ask the API to expand objects.
265
- # In this case, we do the right thing and return
266
- # an expanded object.
267
- EXPANDED_OBJECTS_TABLE = {
268
- "message" => Nylas::ExpandedMessage,
269
- }
270
-
271
- def _build_types_filter_string(filter, types)
272
- return "" if types.empty?
273
- query_string = "&#{filter}_types="
274
-
275
- types.each do |value|
276
- count = 0
277
- if OBJECTS_TABLE.has_value?(value)
278
- param_name = OBJECTS_TABLE.key(value)
279
- query_string += "#{param_name},"
280
- end
281
- end
282
-
283
- query_string = query_string[0..-2]
284
- end
285
-
286
- def deltas(cursor, exclude_types=[], expanded_view=false, include_types=[])
287
- return enum_for(:deltas, cursor, exclude_types, expanded_view, include_types) unless block_given?
288
-
289
- exclude_string = _build_types_filter_string(:exclude, exclude_types)
290
- include_string = _build_types_filter_string(:include, include_types)
291
-
292
- # loop and yield deltas until we've come to the end.
293
- loop do
294
- path = self.url_for_path("/delta?exclude_folders=false&cursor=#{cursor}#{exclude_string}#{include_string}")
295
- if expanded_view
296
- path += '&view=expanded'
297
- end
298
-
299
- json = nil
300
-
301
- RestClient.get(path) do |response,request,result|
302
- json = Nylas.interpret_response(result, response, {:expected_class => Object})
303
- end
304
-
305
- start_cursor = json["cursor_start"]
306
- end_cursor = json["cursor_end"]
307
-
308
- json["deltas"].each do |delta|
309
- if not OBJECTS_TABLE.has_key?(delta['object'])
310
- next
311
- end
312
-
313
- cls = OBJECTS_TABLE[delta['object']]
314
- if EXPANDED_OBJECTS_TABLE.has_key?(delta['object']) and expanded_view
315
- cls = EXPANDED_OBJECTS_TABLE[delta['object']]
316
- end
317
-
318
- obj = cls.new(self)
319
-
320
- case delta["event"]
321
- when 'create', 'modify'
322
- obj.inflate(delta['attributes'])
323
- obj.cursor = delta["cursor"]
324
- yield delta["event"], obj
325
- when 'delete'
326
- obj.id = delta["id"]
327
- obj.cursor = delta["cursor"]
328
- yield delta["event"], obj
329
- end
330
- end
331
-
332
- break if start_cursor == end_cursor
333
- cursor = end_cursor
334
- end
335
- end
336
-
337
- def delta_stream(cursor, exclude_types=[], timeout=0, expanded_view=false, include_types=[])
338
- raise 'Please provide a block for receiving the delta objects' if !block_given?
339
-
340
- exclude_string = _build_types_filter_string(:exclude, exclude_types)
341
- include_string = _build_types_filter_string(:include, include_types)
342
-
343
- # loop and yield deltas indefinitely.
344
- path = self.url_for_path("/delta/streaming?exclude_folders=false&cursor=#{cursor}#{exclude_string}#{include_string}")
345
- if expanded_view
346
- path += '&view=expanded'
347
- end
348
-
349
- parser = Yajl::Parser.new(:symbolize_keys => false)
350
- parser.on_parse_complete = proc do |data|
351
- delta = Nylas.interpret_response(OpenStruct.new(:code => '200'), data, {:expected_class => Object, :result_parsed => true})
352
-
353
- if not OBJECTS_TABLE.has_key?(delta['object'])
354
- next
355
- end
356
-
357
- cls = OBJECTS_TABLE[delta['object']]
358
- if EXPANDED_OBJECTS_TABLE.has_key?(delta['object']) and expanded_view
359
- cls = EXPANDED_OBJECTS_TABLE[delta['object']]
360
- end
361
-
362
- obj = cls.new(self)
363
-
364
- case delta["event"]
365
- when 'create', 'modify'
366
- obj.inflate(delta['attributes'])
367
- obj.cursor = delta["cursor"]
368
- yield delta["event"], obj
369
- when 'delete'
370
- obj.id = delta["id"]
371
- obj.cursor = delta["cursor"]
372
- yield delta["event"], obj
373
- end
374
- end
375
-
376
- http = EventMachine::HttpRequest.new(path, :connect_timeout => 0, :inactivity_timeout => timeout).get(:keepalive => true)
377
-
378
- # set a callback on the HTTP stream that parses incoming chunks as they come in
379
- http.stream do |chunk|
380
- parser << chunk
381
- end
382
-
383
- http.errback do
384
- raise UnexpectedResponse.new http.error
385
- end
386
-
387
- end
388
- end
33
+ # an SDK for interacting with the Nylas API
34
+ # @see https://docs.nylas.com/reference
35
+ module Nylas
36
+ Types.registry[:email_address] = EmailAddressType.new
37
+ Types.registry[:im_address] = IMAddressType.new
38
+ Types.registry[:physical_address] = PhysicalAddressType.new
39
+ Types.registry[:phone_number] = PhoneNumberType.new
40
+ Types.registry[:web_page] = WebPageType.new
41
+ Types.registry[:nylas_date] = NylasDateType.new
389
42
  end
data/lib/nylas/api.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Nylas
2
+ # Methods to retrieve data from the Nylas API as Ruby objects
3
+ class API
4
+ attr_accessor :client
5
+ extend Forwardable
6
+ def_delegators :client, :execute, :get, :post, :put, :delete
7
+
8
+ include Logging
9
+
10
+ # @param client [HttpClient] Http Client to use for retrieving data
11
+ # @param app_id [String] Your application id from the Nylas Dashboard
12
+ # @param app_secret [String] Your application secret from the Nylas Dashboard
13
+ # @param access_token [String] (Optional) Your users access token.
14
+ # @param api_server [String] (Optional) Which Nylas API Server to connect to. Only change this if
15
+ # you're using a self-hosted Nylas instance.
16
+ # @param service_domain [String] (Optional) Host you are authenticating OAuth against.
17
+ # @return [Nylas::API]
18
+ # rubocop:disable Metrics/ParameterLists
19
+ def initialize(client: nil, app_id: nil, app_secret: nil, access_token: nil,
20
+ api_server: "https://api.nylas.com", service_domain: "api.nylas.com")
21
+ self.client = client || HttpClient.new(app_id: app_id, app_secret: app_secret,
22
+ access_token: access_token, api_server: api_server,
23
+ service_domain: service_domain)
24
+ end
25
+ # rubocop:enable Metrics/ParameterLists
26
+
27
+ # @return [Collection<Contact>] A queryable collection of Contacts
28
+ def contacts
29
+ @contacts ||= Collection.new(model: Contact, api: self)
30
+ end
31
+
32
+ # @return [CurrentAccount] The account details for whomevers access token is set
33
+ def current_account
34
+ prevent_calling_if_missing_access_token(:current_account)
35
+ CurrentAccount.from_hash(client.execute(method: :get, path: "/account"), api: self)
36
+ end
37
+
38
+ private def prevent_calling_if_missing_access_token(method_name)
39
+ return if client.access_token && !client.access_token.empty?
40
+ raise NoAuthToken, method_name
41
+ end
42
+ end
43
+ end