nylas 6.6.0 → 6.7.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b88e46de5b92ca56b82971334bc996b373407c76612b7ae7cf560a6496c5fb57
4
- data.tar.gz: 25caea97521efd4fd2f903eed63b375b91a47142c797fd109cb60169a4d51920
3
+ metadata.gz: fa916629fd9482f57599bd8cb7887f69575cc12cffd4f2d3d0fa37c5cd163b70
4
+ data.tar.gz: 906042891eb1519df1ff287ae27104b5f871edcbb938e25cc23e65a1d71452a0
5
5
  SHA512:
6
- metadata.gz: 4af7e805753ad3df593249379933f58cfc42b25c962fafbfae041554d87fe66d59dd4ee1b18475bcf0c69e65dcfe57f1a384d9e9289e9c74fbda20b92d568fb6
7
- data.tar.gz: 704f122a8273212641bcb02c4b1dd6b225d00f64f23655cd3c35ec81d654e1c1e6585d44a99a7caf7a991f4ee8f3fc6f05c61008cc1496b4c2f5fc5521ee9c28
6
+ metadata.gz: 1f7203ab924ec04f7efda91875003a8ffa10a182800ae7f0ffa8fb1cb7ad01a5cb4403bf99d79d84e58514acd25b45e8446a5522f771b7bd110e1c17595d94c1
7
+ data.tar.gz: fb23ac5760b2ed22f253dba8316baf5542ca29f654b1ca5b92345d34918e138065051b207435a62c82d41cefdbb7d3f6ecec0df0eee8294c3471518cc54f3151
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "httparty"
4
4
  require "net/http"
5
+ require "net/http/post/multipart"
5
6
 
6
7
  require_relative "../errors"
7
8
  require_relative "../version"
@@ -41,7 +42,8 @@ module Nylas
41
42
  content_type = response.headers["content-type"].downcase
42
43
  end
43
44
 
44
- parsed_response = parse_json_evaluate_error(result.code.to_i, response.body, path, content_type)
45
+ parsed_response = parse_json_evaluate_error(result.code.to_i, response.body, path, content_type,
46
+ response.headers)
45
47
  # Include headers in the response
46
48
  parsed_response[:headers] = response.headers unless parsed_response.nil?
47
49
  parsed_response
@@ -134,7 +136,9 @@ module Nylas
134
136
 
135
137
  private
136
138
 
137
- # Sends a request to the Nylas REST API using HTTParty.
139
+ # Sends a request to the Nylas REST API using HTTParty or Net::HTTP for multipart.
140
+ # Multipart requests use Net::HTTP::Post::Multipart (multipart-post gem) because
141
+ # HTTParty's multipart handling produces malformed requests that the Nylas API rejects.
138
142
  #
139
143
  # @param method [Symbol] HTTP method for the API call. Either :get, :post, :delete, or :patch.
140
144
  # @param url [String] URL for the API call.
@@ -142,25 +146,16 @@ module Nylas
142
146
  # @param payload [String, Hash] Body to send with the request.
143
147
  # @param timeout [Hash] Timeout value to send with the request.
144
148
  def httparty_execute(method:, url:, headers:, payload:, timeout:)
145
- options = {
146
- headers: headers,
147
- timeout: timeout
148
- }
149
-
150
- # Handle multipart uploads
151
- if payload.is_a?(Hash) && file_upload?(payload)
152
- options[:multipart] = true
153
- options[:body] = prepare_multipart_payload(payload)
154
- elsif payload
155
- options[:body] = payload
149
+ if method == :post && payload.is_a?(Hash) && file_upload?(payload)
150
+ response = execute_multipart_request(url: url, headers: headers, payload: payload, timeout: timeout)
151
+ else
152
+ options = { headers: headers, timeout: timeout }
153
+ options[:body] = payload if payload
154
+ response = HTTParty.send(method, url, options)
156
155
  end
157
156
 
158
- response = HTTParty.send(method, url, options)
159
-
160
- # Create a compatible response object that mimics RestClient::Response
161
157
  result = create_response_wrapper(response)
162
158
 
163
- # Call the block with the response in the same format as rest-client
164
159
  if block_given?
165
160
  yield response, nil, result
166
161
  else
@@ -168,6 +163,77 @@ module Nylas
168
163
  end
169
164
  end
170
165
 
166
+ # Executes multipart POST using Net::HTTP::Post::Multipart (fixes issue #538).
167
+ # HTTParty's multipart produces malformed requests; multipart-post/UploadIO works correctly.
168
+ def execute_multipart_request(url:, headers:, payload:, timeout:)
169
+ uri = URI.parse(url)
170
+ params = build_multipart_params(payload)
171
+
172
+ req = Net::HTTP::Post::Multipart.new(uri.path, params)
173
+ headers.each { |key, value| req[key] = value }
174
+
175
+ http = Net::HTTP.new(uri.host, uri.port)
176
+ http.use_ssl = (uri.scheme == "https")
177
+ http.read_timeout = timeout
178
+ http.open_timeout = timeout
179
+
180
+ response = http.request(req)
181
+
182
+ create_httparty_like_response(response)
183
+ end
184
+
185
+ # Build params hash for Net::HTTP::Post::Multipart with UploadIO for file fields.
186
+ def build_multipart_params(payload)
187
+ params = {}
188
+ payload.each do |key, value|
189
+ params[key] = if key.is_a?(String) && key != "message" && file_like_value?(value)
190
+ value_to_upload_io(value)
191
+ else
192
+ value.to_s
193
+ end
194
+ end
195
+ params
196
+ end
197
+
198
+ def file_like_value?(value)
199
+ return true if value.respond_to?(:read) && (value.is_a?(File) ? !value.closed? : true)
200
+ if value.is_a?(String) && (value.respond_to?(:original_filename) || value.respond_to?(:content_type))
201
+ return true
202
+ end
203
+
204
+ false
205
+ end
206
+
207
+ # Convert File, String, or StringIO to UploadIO for multipart-post.
208
+ def value_to_upload_io(value)
209
+ content_type = value.respond_to?(:content_type) ? value.content_type : "application/octet-stream"
210
+ filename = value.respond_to?(:original_filename) ? value.original_filename : "file.bin"
211
+
212
+ io = if value.respond_to?(:read) && value.respond_to?(:rewind)
213
+ value.rewind if value.respond_to?(:rewind)
214
+ value
215
+ else
216
+ require "stringio"
217
+ content = value.to_s
218
+ content = content.dup.force_encoding(Encoding::ASCII_8BIT) if content.is_a?(String)
219
+ StringIO.new(content)
220
+ end
221
+
222
+ UploadIO.new(io, content_type, filename)
223
+ end
224
+
225
+ # Create response object compatible with HTTParty::Response interface.
226
+ def create_httparty_like_response(net_http_response)
227
+ headers = net_http_response.to_hash
228
+ headers = headers.transform_values { |v| v.is_a?(Array) && v.one? ? v.first : v }
229
+
230
+ OpenStruct.new(
231
+ body: net_http_response.body,
232
+ code: net_http_response.code.to_i,
233
+ headers: headers
234
+ )
235
+ end
236
+
171
237
  # Create a response wrapper that mimics RestClient::Response.code behavior
172
238
  def create_response_wrapper(response)
173
239
  OpenStruct.new(code: response.code)
@@ -187,10 +253,20 @@ module Nylas
187
253
  # Check if payload was prepared by FileUtils.build_form_request for multipart uploads
188
254
  # This handles binary content attachments that are strings with added singleton methods
189
255
  has_message_field = payload.key?("message") && payload["message"].is_a?(String)
190
- has_attachment_fields = payload.keys.any? { |key| key.is_a?(String) && key.match?(/^file\d+$/) }
191
256
 
192
- # If we have both a "message" field and "file{N}" fields, this indicates
193
- # the payload was prepared by FileUtils.build_form_request for multipart upload
257
+ # Check for attachment fields - these can have custom content_id values (not just "file{N}")
258
+ # FileUtils.build_form_request creates entries with string values that have singleton methods
259
+ # like original_filename and content_type defined on them
260
+ has_attachment_fields = payload.any? do |key, value|
261
+ next false unless key.is_a?(String) && key != "message"
262
+
263
+ # Check if the value is a string with attachment-like singleton methods
264
+ # (original_filename or content_type), which indicates it's a file content
265
+ value.is_a?(String) && (value.respond_to?(:original_filename) || value.respond_to?(:content_type))
266
+ end
267
+
268
+ # If we have both a "message" field and attachment fields with file metadata,
269
+ # this indicates the payload was prepared by FileUtils.build_form_request
194
270
  has_message_field && has_attachment_fields
195
271
  end
196
272
 
@@ -311,37 +387,34 @@ module Nylas
311
387
  end
312
388
 
313
389
  # Parses the response from the Nylas API and evaluates for errors.
314
- def parse_json_evaluate_error(http_code, response, path, content_type = nil)
390
+ def parse_json_evaluate_error(http_code, response, path, content_type = nil, headers = nil)
315
391
  begin
316
392
  response = parse_response(response) if content_type == "application/json"
317
393
  rescue Nylas::JsonParseError
318
- handle_failed_response(http_code, response, path)
394
+ handle_failed_response(http_code, response, path, headers)
319
395
  raise
320
396
  end
321
397
 
322
- handle_failed_response(http_code, response, path)
398
+ handle_failed_response(http_code, response, path, headers)
323
399
  response
324
400
  end
325
401
 
326
402
  # Handles failed responses from the Nylas API.
327
- def handle_failed_response(http_code, response, path)
403
+ def handle_failed_response(http_code, response, path, headers = nil)
328
404
  return if HTTP_SUCCESS_CODES.include?(http_code)
329
405
 
330
406
  case response
331
407
  when Hash
332
- raise error_hash_to_exception(response, http_code, path)
408
+ raise error_hash_to_exception(response, http_code, path, headers)
333
409
  else
334
410
  raise NylasApiError.parse_error_response(response, http_code)
335
411
  end
336
412
  end
337
413
 
338
414
  # Converts error hashes to exceptions.
339
- def error_hash_to_exception(response, status_code, path)
415
+ def error_hash_to_exception(response, status_code, path, headers = nil)
340
416
  return if !response || !response.key?(:error)
341
417
 
342
- # Safely get headers without risking KeyError
343
- headers = response.key?(:headers) ? response[:headers] : nil
344
-
345
418
  if %W[#{api_uri}/v3/connect/token #{api_uri}/v3/connect/revoke].include?(path)
346
419
  NylasOAuthError.new(response[:error], response[:error_description], response[:error_uri],
347
420
  response[:error_code], status_code)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cgi"
4
3
  require_relative "resource"
5
4
  require_relative "../handler/api_operations"
6
5
 
@@ -115,14 +114,14 @@ module Nylas
115
114
  # @return [String] The challenge parameter
116
115
  def self.extract_challenge_parameter(url)
117
116
  url_object = URI.parse(url)
118
- query = CGI.parse(url_object.query || "")
117
+ params = URI.decode_www_form(url_object.query || "")
118
+ challenge_pair = params.find { |k, _| k == "challenge" }
119
119
 
120
- challenge_parameter = query["challenge"]
121
- if challenge_parameter.nil? || challenge_parameter.empty? || challenge_parameter.first.nil?
120
+ if challenge_pair.nil? || challenge_pair.last.to_s.empty?
122
121
  raise "Invalid URL or no challenge parameter found."
123
122
  end
124
123
 
125
- challenge_parameter.first
124
+ challenge_pair.last
126
125
  end
127
126
  end
128
127
  end
@@ -28,12 +28,13 @@ module Nylas
28
28
 
29
29
  attachments.each_with_index do |attachment, index|
30
30
  file = attachment[:content] || attachment["content"]
31
+ file_path = attachment[:file_path] || attachment["file_path"]
31
32
  if file.respond_to?(:closed?) && file.closed?
32
- unless attachment[:file_path]
33
+ unless file_path
33
34
  raise ArgumentError, "The file at index #{index} is closed and no file_path was provided."
34
35
  end
35
36
 
36
- file = File.open(attachment[:file_path], "rb")
37
+ file = File.open(file_path, "rb")
37
38
  end
38
39
 
39
40
  # Setting original filename and content type if available. See rest-client#lib/restclient/payload.rb
@@ -87,7 +88,9 @@ module Nylas
87
88
 
88
89
  # Use form data only if the attachment size is greater than 3mb
89
90
  attachments = payload[:attachments]
90
- attachment_size = attachments&.sum { |attachment| attachment[:size] || 0 } || 0
91
+ # Support both string and symbol keys for attachment size to handle
92
+ # user-provided hashes that may use either key type
93
+ attachment_size = attachments&.sum { |attachment| attachment[:size] || attachment["size"] || 0 } || 0
91
94
 
92
95
  # Handle the attachment encoding depending on the size
93
96
  if attachment_size >= FORM_DATA_ATTACHMENT_SIZE
data/lib/nylas/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nylas
4
- VERSION = "6.6.0"
4
+ VERSION = "6.7.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nylas
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.6.0
4
+ version: 6.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nylas, Inc.
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-07-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: base64
@@ -58,6 +57,20 @@ dependencies:
58
57
  - - ">="
59
58
  - !ruby/object:Gem::Version
60
59
  version: 3.5.1
60
+ - !ruby/object:Gem::Dependency
61
+ name: multipart-post
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '2.0'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '2.0'
61
74
  - !ruby/object:Gem::Dependency
62
75
  name: ostruct
63
76
  requirement: !ruby/object:Gem::Requirement
@@ -140,14 +153,14 @@ dependencies:
140
153
  requirements:
141
154
  - - "~>"
142
155
  - !ruby/object:Gem::Version
143
- version: '2.22'
156
+ version: '3.5'
144
157
  type: :development
145
158
  prerelease: false
146
159
  version_requirements: !ruby/object:Gem::Requirement
147
160
  requirements:
148
161
  - - "~>"
149
162
  - !ruby/object:Gem::Version
150
- version: '2.22'
163
+ version: '3.5'
151
164
  - !ruby/object:Gem::Dependency
152
165
  name: rubocop-capybara
153
166
  requirement: !ruby/object:Gem::Requirement
@@ -295,7 +308,6 @@ files:
295
308
  - lib/nylas/resources/webhooks.rb
296
309
  - lib/nylas/utils/file_utils.rb
297
310
  - lib/nylas/version.rb
298
- homepage:
299
311
  licenses:
300
312
  - MIT
301
313
  metadata:
@@ -305,7 +317,6 @@ metadata:
305
317
  homepage_uri: https://www.nylas.com
306
318
  source_code_uri: https://github.com/nylas/nylas-ruby
307
319
  github_repo: https://github.com/nylas/nylas-ruby
308
- post_install_message:
309
320
  rdoc_options: []
310
321
  require_paths:
311
322
  - lib
@@ -320,8 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
320
331
  - !ruby/object:Gem::Version
321
332
  version: '0'
322
333
  requirements: []
323
- rubygems_version: 3.4.18
324
- signing_key:
334
+ rubygems_version: 4.0.3
325
335
  specification_version: 4
326
336
  summary: Gem for interacting with the Nylas API
327
337
  test_files: []