nylas 6.7.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: cc90a16ef74fc2f47d6f35ada72d9cd80e3e842e08d9d5b1916a819ae36c0808
4
- data.tar.gz: b2c8494899f1b2ff4620dfbcce0aeb52e037adfe97f1609bcaaf455bfb843745
3
+ metadata.gz: fa916629fd9482f57599bd8cb7887f69575cc12cffd4f2d3d0fa37c5cd163b70
4
+ data.tar.gz: 906042891eb1519df1ff287ae27104b5f871edcbb938e25cc23e65a1d71452a0
5
5
  SHA512:
6
- metadata.gz: c9cda94765c3957d6457071ac616dd12a3a807378773b566da0be82b39c5a89db370049f21744b91ed8f3a5abf211b6c75363f8df10f52d102007becfcd2a635
7
- data.tar.gz: e50d00d31fdb2ae23058d146b38cc75a5a75f239ba19220b5c28280defe4624b4ab4d858ec3abd496555ab6b0f35033ac2c606ed2bc346572f0593df0cebb5cc
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"
@@ -135,7 +136,9 @@ module Nylas
135
136
 
136
137
  private
137
138
 
138
- # 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.
139
142
  #
140
143
  # @param method [Symbol] HTTP method for the API call. Either :get, :post, :delete, or :patch.
141
144
  # @param url [String] URL for the API call.
@@ -143,25 +146,16 @@ module Nylas
143
146
  # @param payload [String, Hash] Body to send with the request.
144
147
  # @param timeout [Hash] Timeout value to send with the request.
145
148
  def httparty_execute(method:, url:, headers:, payload:, timeout:)
146
- options = {
147
- headers: headers,
148
- timeout: timeout
149
- }
150
-
151
- # Handle multipart uploads
152
- if payload.is_a?(Hash) && file_upload?(payload)
153
- options[:multipart] = true
154
- options[:body] = prepare_multipart_payload(payload)
155
- elsif payload
156
- 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)
157
155
  end
158
156
 
159
- response = HTTParty.send(method, url, options)
160
-
161
- # Create a compatible response object that mimics RestClient::Response
162
157
  result = create_response_wrapper(response)
163
158
 
164
- # Call the block with the response in the same format as rest-client
165
159
  if block_given?
166
160
  yield response, nil, result
167
161
  else
@@ -169,6 +163,77 @@ module Nylas
169
163
  end
170
164
  end
171
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
+
172
237
  # Create a response wrapper that mimics RestClient::Response.code behavior
173
238
  def create_response_wrapper(response)
174
239
  OpenStruct.new(code: response.code)
@@ -188,10 +253,20 @@ module Nylas
188
253
  # Check if payload was prepared by FileUtils.build_form_request for multipart uploads
189
254
  # This handles binary content attachments that are strings with added singleton methods
190
255
  has_message_field = payload.key?("message") && payload["message"].is_a?(String)
191
- has_attachment_fields = payload.keys.any? { |key| key.is_a?(String) && key.match?(/^file\d+$/) }
192
256
 
193
- # If we have both a "message" field and "file{N}" fields, this indicates
194
- # 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
195
270
  has_message_field && has_attachment_fields
196
271
  end
197
272
 
@@ -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.7.0"
4
+ VERSION = "6.7.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nylas
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.7.0
4
+ version: 6.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nylas, Inc.
@@ -57,6 +57,20 @@ dependencies:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
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'
60
74
  - !ruby/object:Gem::Dependency
61
75
  name: ostruct
62
76
  requirement: !ruby/object:Gem::Requirement
@@ -139,14 +153,14 @@ dependencies:
139
153
  requirements:
140
154
  - - "~>"
141
155
  - !ruby/object:Gem::Version
142
- version: '2.22'
156
+ version: '3.5'
143
157
  type: :development
144
158
  prerelease: false
145
159
  version_requirements: !ruby/object:Gem::Requirement
146
160
  requirements:
147
161
  - - "~>"
148
162
  - !ruby/object:Gem::Version
149
- version: '2.22'
163
+ version: '3.5'
150
164
  - !ruby/object:Gem::Dependency
151
165
  name: rubocop-capybara
152
166
  requirement: !ruby/object:Gem::Requirement
@@ -317,7 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
317
331
  - !ruby/object:Gem::Version
318
332
  version: '0'
319
333
  requirements: []
320
- rubygems_version: 3.7.2
334
+ rubygems_version: 4.0.3
321
335
  specification_version: 4
322
336
  summary: Gem for interacting with the Nylas API
323
337
  test_files: []