kapso-client-ruby 1.0.1 → 1.0.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +81 -81
- data/CHANGELOG.md +262 -91
- data/Gemfile +20 -20
- data/RAILS_INTEGRATION.md +477 -477
- data/README.md +1053 -752
- data/Rakefile +40 -40
- data/TEMPLATE_TOOLS_GUIDE.md +120 -120
- data/WHATSAPP_24_HOUR_GUIDE.md +133 -133
- data/examples/advanced_features.rb +352 -349
- data/examples/advanced_messaging.rb +241 -0
- data/examples/basic_messaging.rb +139 -136
- data/examples/enhanced_interactive.rb +400 -0
- data/examples/flows_usage.rb +307 -0
- data/examples/interactive_messages.rb +343 -0
- data/examples/media_management.rb +256 -253
- data/examples/rails/jobs.rb +387 -387
- data/examples/rails/models.rb +239 -239
- data/examples/rails/notifications_controller.rb +226 -226
- data/examples/template_management.rb +393 -390
- data/kapso-ruby-logo.jpg +0 -0
- data/lib/kapso_client_ruby/client.rb +321 -316
- data/lib/kapso_client_ruby/errors.rb +348 -329
- data/lib/kapso_client_ruby/rails/generators/install_generator.rb +75 -75
- data/lib/kapso_client_ruby/rails/generators/templates/env.erb +20 -20
- data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +32 -32
- data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +137 -137
- data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +61 -61
- data/lib/kapso_client_ruby/rails/railtie.rb +54 -54
- data/lib/kapso_client_ruby/rails/service.rb +188 -188
- data/lib/kapso_client_ruby/rails/tasks.rake +166 -166
- data/lib/kapso_client_ruby/resources/calls.rb +172 -172
- data/lib/kapso_client_ruby/resources/contacts.rb +190 -190
- data/lib/kapso_client_ruby/resources/conversations.rb +103 -103
- data/lib/kapso_client_ruby/resources/flows.rb +382 -0
- data/lib/kapso_client_ruby/resources/media.rb +205 -205
- data/lib/kapso_client_ruby/resources/messages.rb +760 -380
- data/lib/kapso_client_ruby/resources/phone_numbers.rb +85 -85
- data/lib/kapso_client_ruby/resources/templates.rb +283 -283
- data/lib/kapso_client_ruby/types.rb +348 -262
- data/lib/kapso_client_ruby/version.rb +5 -5
- data/lib/kapso_client_ruby.rb +75 -74
- data/scripts/.env.example +17 -17
- data/scripts/kapso_template_finder.rb +91 -91
- data/scripts/sdk_setup.rb +404 -404
- data/scripts/test.rb +60 -60
- metadata +12 -3
data/kapso-ruby-logo.jpg
ADDED
|
Binary file
|
|
@@ -1,317 +1,322 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'faraday'
|
|
4
|
-
require 'faraday/multipart'
|
|
5
|
-
require 'json'
|
|
6
|
-
require 'logger'
|
|
7
|
-
|
|
8
|
-
module KapsoClientRuby
|
|
9
|
-
class Client
|
|
10
|
-
DEFAULT_BASE_URL = 'https://graph.facebook.com'
|
|
11
|
-
DEFAULT_GRAPH_VERSION = 'v24.0'
|
|
12
|
-
KAPSO_PROXY_PATTERN = /kapso\.ai/
|
|
13
|
-
|
|
14
|
-
attr_reader :access_token, :kapso_api_key, :base_url, :graph_version,
|
|
15
|
-
:logger, :debug, :timeout, :open_timeout, :max_retries, :retry_delay
|
|
16
|
-
|
|
17
|
-
def initialize(access_token: nil, kapso_api_key: nil, base_url: nil,
|
|
18
|
-
graph_version: nil, logger: nil, debug: nil, timeout: nil,
|
|
19
|
-
open_timeout: nil, max_retries: nil, retry_delay: nil)
|
|
20
|
-
|
|
21
|
-
# Validation
|
|
22
|
-
unless access_token || kapso_api_key
|
|
23
|
-
raise Errors::ConfigurationError, 'Must provide either access_token or kapso_api_key'
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
@access_token = access_token
|
|
27
|
-
@kapso_api_key = kapso_api_key
|
|
28
|
-
@base_url = normalize_base_url(base_url || DEFAULT_BASE_URL)
|
|
29
|
-
@graph_version = graph_version || DEFAULT_GRAPH_VERSION
|
|
30
|
-
@kapso_proxy = detect_kapso_proxy(@base_url)
|
|
31
|
-
|
|
32
|
-
# Configuration with defaults
|
|
33
|
-
config = KapsoClientRuby.configuration
|
|
34
|
-
@logger = logger || KapsoClientRuby.logger
|
|
35
|
-
@debug = debug.nil? ? config.debug : debug
|
|
36
|
-
@timeout = timeout || config.timeout
|
|
37
|
-
@open_timeout = open_timeout || config.open_timeout
|
|
38
|
-
@max_retries = max_retries || config.max_retries
|
|
39
|
-
@retry_delay = retry_delay || config.retry_delay
|
|
40
|
-
|
|
41
|
-
# Initialize HTTP client
|
|
42
|
-
@http_client = build_http_client
|
|
43
|
-
|
|
44
|
-
# Initialize resource endpoints
|
|
45
|
-
@messages = nil
|
|
46
|
-
@media = nil
|
|
47
|
-
@templates = nil
|
|
48
|
-
@phone_numbers = nil
|
|
49
|
-
@calls = nil
|
|
50
|
-
@conversations = nil
|
|
51
|
-
@contacts = nil
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
#
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
url.match?(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'faraday/multipart'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'logger'
|
|
7
|
+
|
|
8
|
+
module KapsoClientRuby
|
|
9
|
+
class Client
|
|
10
|
+
DEFAULT_BASE_URL = 'https://graph.facebook.com'
|
|
11
|
+
DEFAULT_GRAPH_VERSION = 'v24.0'
|
|
12
|
+
KAPSO_PROXY_PATTERN = /kapso\.ai/
|
|
13
|
+
|
|
14
|
+
attr_reader :access_token, :kapso_api_key, :base_url, :graph_version,
|
|
15
|
+
:logger, :debug, :timeout, :open_timeout, :max_retries, :retry_delay
|
|
16
|
+
|
|
17
|
+
def initialize(access_token: nil, kapso_api_key: nil, base_url: nil,
|
|
18
|
+
graph_version: nil, logger: nil, debug: nil, timeout: nil,
|
|
19
|
+
open_timeout: nil, max_retries: nil, retry_delay: nil)
|
|
20
|
+
|
|
21
|
+
# Validation
|
|
22
|
+
unless access_token || kapso_api_key
|
|
23
|
+
raise Errors::ConfigurationError, 'Must provide either access_token or kapso_api_key'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@access_token = access_token
|
|
27
|
+
@kapso_api_key = kapso_api_key
|
|
28
|
+
@base_url = normalize_base_url(base_url || DEFAULT_BASE_URL)
|
|
29
|
+
@graph_version = graph_version || DEFAULT_GRAPH_VERSION
|
|
30
|
+
@kapso_proxy = detect_kapso_proxy(@base_url)
|
|
31
|
+
|
|
32
|
+
# Configuration with defaults
|
|
33
|
+
config = KapsoClientRuby.configuration
|
|
34
|
+
@logger = logger || KapsoClientRuby.logger
|
|
35
|
+
@debug = debug.nil? ? config.debug : debug
|
|
36
|
+
@timeout = timeout || config.timeout
|
|
37
|
+
@open_timeout = open_timeout || config.open_timeout
|
|
38
|
+
@max_retries = max_retries || config.max_retries
|
|
39
|
+
@retry_delay = retry_delay || config.retry_delay
|
|
40
|
+
|
|
41
|
+
# Initialize HTTP client
|
|
42
|
+
@http_client = build_http_client
|
|
43
|
+
|
|
44
|
+
# Initialize resource endpoints
|
|
45
|
+
@messages = nil
|
|
46
|
+
@media = nil
|
|
47
|
+
@templates = nil
|
|
48
|
+
@phone_numbers = nil
|
|
49
|
+
@calls = nil
|
|
50
|
+
@conversations = nil
|
|
51
|
+
@contacts = nil
|
|
52
|
+
@flows = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Resource accessors with lazy initialization
|
|
56
|
+
def messages
|
|
57
|
+
@messages ||= Resources::Messages.new(self)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def media
|
|
61
|
+
@media ||= Resources::Media.new(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def templates
|
|
65
|
+
@templates ||= Resources::Templates.new(self)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def phone_numbers
|
|
69
|
+
@phone_numbers ||= Resources::PhoneNumbers.new(self)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def calls
|
|
73
|
+
@calls ||= Resources::Calls.new(self)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def conversations
|
|
77
|
+
@conversations ||= Resources::Conversations.new(self)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def contacts
|
|
81
|
+
@contacts ||= Resources::Contacts.new(self)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def flows
|
|
85
|
+
@flows ||= Resources::Flows.new(self)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def kapso_proxy?
|
|
89
|
+
@kapso_proxy
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Main request method with retry logic and error handling
|
|
93
|
+
def request(method, path, options = {})
|
|
94
|
+
method = method.to_s.upcase
|
|
95
|
+
body = options[:body]
|
|
96
|
+
query = options[:query]
|
|
97
|
+
custom_headers = options[:headers] || {}
|
|
98
|
+
response_type = options[:response_type] || :auto
|
|
99
|
+
|
|
100
|
+
url = build_url(path, query)
|
|
101
|
+
headers = build_headers(custom_headers)
|
|
102
|
+
|
|
103
|
+
# Log request if debugging
|
|
104
|
+
log_request(method, url, headers, body) if debug
|
|
105
|
+
|
|
106
|
+
retries = 0
|
|
107
|
+
begin
|
|
108
|
+
response = @http_client.run_request(method.downcase.to_sym, url, body, headers)
|
|
109
|
+
|
|
110
|
+
# Log response if debugging
|
|
111
|
+
log_response(response) if debug
|
|
112
|
+
|
|
113
|
+
# Handle response based on type requested
|
|
114
|
+
handle_response(response, response_type)
|
|
115
|
+
rescue Faraday::Error => e
|
|
116
|
+
retries += 1
|
|
117
|
+
if retries <= max_retries && retryable_error?(e)
|
|
118
|
+
sleep(retry_delay * retries)
|
|
119
|
+
retry
|
|
120
|
+
else
|
|
121
|
+
raise Errors::GraphApiError.new(
|
|
122
|
+
message: "Network error: #{e.message}",
|
|
123
|
+
http_status: 0,
|
|
124
|
+
category: :server
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Raw HTTP method without automatic error handling (for media downloads, etc.)
|
|
131
|
+
def raw_request(method, url, options = {})
|
|
132
|
+
headers = build_headers(options[:headers] || {})
|
|
133
|
+
|
|
134
|
+
log_request(method, url, headers, options[:body]) if debug
|
|
135
|
+
|
|
136
|
+
response = @http_client.run_request(method.to_sym, url, options[:body], headers)
|
|
137
|
+
|
|
138
|
+
log_response(response) if debug
|
|
139
|
+
|
|
140
|
+
response
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Fetch with automatic auth headers (for absolute URLs)
|
|
144
|
+
def fetch(url, options = {})
|
|
145
|
+
headers = build_headers(options[:headers] || {})
|
|
146
|
+
method = options[:method] || 'GET'
|
|
147
|
+
|
|
148
|
+
log_request(method, url, headers, options[:body]) if debug
|
|
149
|
+
|
|
150
|
+
response = @http_client.run_request(method.downcase.to_sym, url, options[:body], headers)
|
|
151
|
+
|
|
152
|
+
log_response(response) if debug
|
|
153
|
+
|
|
154
|
+
if response.success?
|
|
155
|
+
response
|
|
156
|
+
else
|
|
157
|
+
handle_error_response(response)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def build_http_client
|
|
164
|
+
Faraday.new do |f|
|
|
165
|
+
f.options.timeout = timeout
|
|
166
|
+
f.options.open_timeout = open_timeout
|
|
167
|
+
f.request :multipart
|
|
168
|
+
f.request :url_encoded
|
|
169
|
+
f.adapter Faraday.default_adapter
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def build_headers(custom_headers = {})
|
|
174
|
+
headers = {}
|
|
175
|
+
|
|
176
|
+
# Authentication headers
|
|
177
|
+
if access_token
|
|
178
|
+
headers['Authorization'] = "Bearer #{access_token}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if kapso_api_key
|
|
182
|
+
headers['X-API-Key'] = kapso_api_key
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Default content type for JSON requests
|
|
186
|
+
headers['Content-Type'] = 'application/json' unless custom_headers.key?('Content-Type')
|
|
187
|
+
|
|
188
|
+
headers.merge(custom_headers.compact)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def build_url(path, query = nil)
|
|
192
|
+
# Remove leading slash from path
|
|
193
|
+
clean_path = path.to_s.sub(%r{^/}, '')
|
|
194
|
+
|
|
195
|
+
# Build base URL with version
|
|
196
|
+
base = "#{base_url}/#{graph_version}/"
|
|
197
|
+
full_url = URI.join(base, clean_path).to_s
|
|
198
|
+
|
|
199
|
+
# Add query parameters if present
|
|
200
|
+
if query && !query.empty?
|
|
201
|
+
# Convert to snake_case for API (Meta expects snake_case)
|
|
202
|
+
snake_query = Types.deep_snake_case_keys(query)
|
|
203
|
+
query_string = URI.encode_www_form(flatten_query(snake_query))
|
|
204
|
+
separator = full_url.include?('?') ? '&' : '?'
|
|
205
|
+
full_url += "#{separator}#{query_string}"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
full_url
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def flatten_query(query, prefix = nil)
|
|
212
|
+
result = []
|
|
213
|
+
query.each do |key, value|
|
|
214
|
+
param_key = prefix ? "#{prefix}[#{key}]" : key.to_s
|
|
215
|
+
|
|
216
|
+
case value
|
|
217
|
+
when Hash
|
|
218
|
+
result.concat(flatten_query(value, param_key))
|
|
219
|
+
when Array
|
|
220
|
+
value.each { |v| result << [param_key, v] }
|
|
221
|
+
else
|
|
222
|
+
result << [param_key, value] unless value.nil?
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
result
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def handle_response(response, response_type)
|
|
229
|
+
unless response.success?
|
|
230
|
+
handle_error_response(response)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
case response_type
|
|
234
|
+
when :json
|
|
235
|
+
parse_json_response(response)
|
|
236
|
+
when :raw
|
|
237
|
+
response
|
|
238
|
+
when :auto
|
|
239
|
+
content_type = response.headers['content-type'] || ''
|
|
240
|
+
if content_type.include?('application/json')
|
|
241
|
+
parse_json_response(response)
|
|
242
|
+
elsif response.status == 204
|
|
243
|
+
Types::GraphSuccessResponse.new
|
|
244
|
+
else
|
|
245
|
+
response.body
|
|
246
|
+
end
|
|
247
|
+
else
|
|
248
|
+
response.body
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def parse_json_response(response)
|
|
253
|
+
return Types::GraphSuccessResponse.new if response.body.nil? || response.body.strip.empty?
|
|
254
|
+
|
|
255
|
+
begin
|
|
256
|
+
json = JSON.parse(response.body)
|
|
257
|
+
# Convert camelCase keys to snake_case for Ruby conventions
|
|
258
|
+
Types.deep_snake_case_keys(json)
|
|
259
|
+
rescue JSON::ParserError => e
|
|
260
|
+
raise Errors::GraphApiError.new(
|
|
261
|
+
message: "Invalid JSON response: #{e.message}",
|
|
262
|
+
http_status: response.status,
|
|
263
|
+
raw_response: response.body
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def handle_error_response(response)
|
|
269
|
+
body = response.body
|
|
270
|
+
|
|
271
|
+
# Try to parse JSON error
|
|
272
|
+
begin
|
|
273
|
+
json_body = JSON.parse(body) if body && !body.strip.empty?
|
|
274
|
+
rescue JSON::ParserError
|
|
275
|
+
json_body = nil
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Create error with proper parameters
|
|
279
|
+
raise Errors::GraphApiError.from_response(response, json_body || {}, body)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def retryable_error?(error)
|
|
283
|
+
# Only retry on network errors, not HTTP errors
|
|
284
|
+
error.is_a?(Faraday::TimeoutError) ||
|
|
285
|
+
error.is_a?(Faraday::ConnectionFailed) ||
|
|
286
|
+
error.is_a?(Faraday::ServerError)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def normalize_base_url(url)
|
|
290
|
+
url = url.to_s
|
|
291
|
+
url = "https://#{url}" unless url.match?(%r{^https?://})
|
|
292
|
+
url.chomp('/')
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def detect_kapso_proxy(url)
|
|
296
|
+
url.match?(KAPSO_PROXY_PATTERN)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def log_request(method, url, headers, body)
|
|
300
|
+
logger.debug "WhatsApp API Request: #{method} #{url}"
|
|
301
|
+
logger.debug "Headers: #{headers.inspect}" if headers.any?
|
|
302
|
+
|
|
303
|
+
if body
|
|
304
|
+
if body.is_a?(String)
|
|
305
|
+
logger.debug "Body: #{body.length > 1000 ? "#{body[0..1000]}..." : body}"
|
|
306
|
+
else
|
|
307
|
+
logger.debug "Body: #{body.inspect}"
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def log_response(response)
|
|
313
|
+
logger.debug "WhatsApp API Response: #{response.status}"
|
|
314
|
+
logger.debug "Response Headers: #{response.headers.to_h.inspect}"
|
|
315
|
+
|
|
316
|
+
if response.body
|
|
317
|
+
body_preview = response.body.length > 1000 ? "#{response.body[0..1000]}..." : response.body
|
|
318
|
+
logger.debug "Response Body: #{body_preview}"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
317
322
|
end
|