basecamp-sdk 0.10.0 → 0.12.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -10
  3. data/lib/basecamp/client.rb +28 -8
  4. data/lib/basecamp/config.rb +4 -3
  5. data/lib/basecamp/generated/metadata.json +178 -17
  6. data/lib/basecamp/generated/services/account_service.rb +1 -1
  7. data/lib/basecamp/generated/services/automation_service.rb +1 -1
  8. data/lib/basecamp/generated/services/bookmarks_service.rb +49 -0
  9. data/lib/basecamp/generated/services/boosts_service.rb +3 -3
  10. data/lib/basecamp/generated/services/calendars_service.rb +30 -0
  11. data/lib/basecamp/generated/services/campfires_service.rb +7 -7
  12. data/lib/basecamp/generated/services/card_columns_service.rb +1 -1
  13. data/lib/basecamp/generated/services/card_steps_service.rb +1 -1
  14. data/lib/basecamp/generated/services/card_tables_service.rb +1 -1
  15. data/lib/basecamp/generated/services/cards_service.rb +2 -2
  16. data/lib/basecamp/generated/services/checkins_service.rb +8 -8
  17. data/lib/basecamp/generated/services/client_approvals_service.rb +2 -2
  18. data/lib/basecamp/generated/services/client_correspondences_service.rb +2 -2
  19. data/lib/basecamp/generated/services/client_replies_service.rb +2 -2
  20. data/lib/basecamp/generated/services/comments_service.rb +2 -2
  21. data/lib/basecamp/generated/services/documents_service.rb +2 -2
  22. data/lib/basecamp/generated/services/drafts_service.rb +21 -0
  23. data/lib/basecamp/generated/services/events_service.rb +1 -1
  24. data/lib/basecamp/generated/services/everything_service.rb +69 -46
  25. data/lib/basecamp/generated/services/forwards_service.rb +5 -5
  26. data/lib/basecamp/generated/services/gauges_service.rb +3 -3
  27. data/lib/basecamp/generated/services/hill_charts_service.rb +1 -1
  28. data/lib/basecamp/generated/services/message_boards_service.rb +1 -1
  29. data/lib/basecamp/generated/services/message_types_service.rb +2 -2
  30. data/lib/basecamp/generated/services/messages_service.rb +2 -2
  31. data/lib/basecamp/generated/services/my_assignments_service.rb +34 -3
  32. data/lib/basecamp/generated/services/my_notes_service.rb +28 -0
  33. data/lib/basecamp/generated/services/my_notifications_service.rb +2 -2
  34. data/lib/basecamp/generated/services/people_service.rb +8 -8
  35. data/lib/basecamp/generated/services/projects_service.rb +2 -2
  36. data/lib/basecamp/generated/services/recordings_service.rb +2 -2
  37. data/lib/basecamp/generated/services/reports_service.rb +5 -5
  38. data/lib/basecamp/generated/services/schedules_service.rb +4 -4
  39. data/lib/basecamp/generated/services/search_service.rb +2 -2
  40. data/lib/basecamp/generated/services/subscriptions_service.rb +1 -1
  41. data/lib/basecamp/generated/services/templates_service.rb +3 -3
  42. data/lib/basecamp/generated/services/timeline_service.rb +1 -1
  43. data/lib/basecamp/generated/services/timesheets_service.rb +4 -4
  44. data/lib/basecamp/generated/services/todolist_groups_service.rb +1 -1
  45. data/lib/basecamp/generated/services/todolists_service.rb +2 -2
  46. data/lib/basecamp/generated/services/todos_service.rb +19 -2
  47. data/lib/basecamp/generated/services/todosets_service.rb +1 -1
  48. data/lib/basecamp/generated/services/tools_service.rb +1 -1
  49. data/lib/basecamp/generated/services/uploads_service.rb +3 -3
  50. data/lib/basecamp/generated/services/vaults_service.rb +2 -2
  51. data/lib/basecamp/generated/services/webhooks_service.rb +2 -2
  52. data/lib/basecamp/generated/types.rb +313 -34
  53. data/lib/basecamp/http.rb +47 -19
  54. data/lib/basecamp/oauth/device_authorization.rb +37 -0
  55. data/lib/basecamp/oauth/device_flow.rb +873 -0
  56. data/lib/basecamp/oauth/device_flow_error.rb +46 -0
  57. data/lib/basecamp/oauth/discovery.rb +13 -11
  58. data/lib/basecamp/oauth/exchange.rb +44 -5
  59. data/lib/basecamp/oauth/fetcher.rb +626 -56
  60. data/lib/basecamp/oauth/oauth_error.rb +3 -1
  61. data/lib/basecamp/oauth/refresh_request.rb +8 -2
  62. data/lib/basecamp/oauth/resource.rb +13 -10
  63. data/lib/basecamp/oauth/token.rb +10 -3
  64. data/lib/basecamp/oauth.rb +47 -2
  65. data/lib/basecamp/version.rb +2 -2
  66. data/scripts/generate-services.rb +10 -7
  67. metadata +9 -2
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Auto-generated from OpenAPI spec. Do not edit manually.
4
- # Generated: 2026-07-28T21:34:14Z
4
+ # Generated: 2026-07-31T15:59:31Z
5
5
 
6
6
  require "json"
7
7
  require "time"
@@ -255,6 +255,62 @@ module Basecamp
255
255
  end
256
256
  end
257
257
 
258
+ # Bookmark
259
+ class Bookmark
260
+ include TypeHelpers
261
+ attr_accessor :created_at, :id, :recording, :updated_at
262
+
263
+ # @return [Array<Symbol>]
264
+ def self.required_fields
265
+ %i[created_at id recording updated_at].freeze
266
+ end
267
+
268
+ def initialize(data = {})
269
+ @created_at = parse_datetime(data["created_at"])
270
+ @id = parse_integer(data["id"])
271
+ @recording = parse_type(data["recording"], "Recording")
272
+ @updated_at = parse_datetime(data["updated_at"])
273
+ end
274
+
275
+ def to_h
276
+ {
277
+ "created_at" => @created_at,
278
+ "id" => @id,
279
+ "recording" => @recording,
280
+ "updated_at" => @updated_at,
281
+ }.compact
282
+ end
283
+
284
+ def to_json(*args)
285
+ to_h.to_json(*args)
286
+ end
287
+ end
288
+
289
+ # BookmarkStatus
290
+ class BookmarkStatus
291
+ include TypeHelpers
292
+ attr_accessor :bookmarked
293
+
294
+ # @return [Array<Symbol>]
295
+ def self.required_fields
296
+ %i[bookmarked].freeze
297
+ end
298
+
299
+ def initialize(data = {})
300
+ @bookmarked = parse_boolean(data["bookmarked"])
301
+ end
302
+
303
+ def to_h
304
+ {
305
+ "bookmarked" => @bookmarked,
306
+ }.compact
307
+ end
308
+
309
+ def to_json(*args)
310
+ to_h.to_json(*args)
311
+ end
312
+ end
313
+
258
314
  # Boost
259
315
  class Boost
260
316
  include TypeHelpers
@@ -342,6 +398,72 @@ module Basecamp
342
398
  end
343
399
  end
344
400
 
401
+ # Calendar
402
+ class Calendar
403
+ include TypeHelpers
404
+ attr_accessor :app_url, :color, :created_at, :id, :name, :schedule_url, :type, :updated_at, :url
405
+
406
+ # @return [Array<Symbol>]
407
+ def self.required_fields
408
+ %i[app_url color created_at id name schedule_url type updated_at url].freeze
409
+ end
410
+
411
+ def initialize(data = {})
412
+ @app_url = data["app_url"]
413
+ @color = data["color"]
414
+ @created_at = parse_datetime(data["created_at"])
415
+ @id = parse_integer(data["id"])
416
+ @name = data["name"]
417
+ @schedule_url = data["schedule_url"]
418
+ @type = data["type"]
419
+ @updated_at = parse_datetime(data["updated_at"])
420
+ @url = data["url"]
421
+ end
422
+
423
+ def to_h
424
+ {
425
+ "app_url" => @app_url,
426
+ "color" => @color,
427
+ "created_at" => @created_at,
428
+ "id" => @id,
429
+ "name" => @name,
430
+ "schedule_url" => @schedule_url,
431
+ "type" => @type,
432
+ "updated_at" => @updated_at,
433
+ "url" => @url,
434
+ }.compact
435
+ end
436
+
437
+ def to_json(*args)
438
+ to_h.to_json(*args)
439
+ end
440
+ end
441
+
442
+ # CalendarAttributes
443
+ class CalendarAttributes
444
+ include TypeHelpers
445
+ attr_accessor :color
446
+
447
+ # @return [Array<Symbol>]
448
+ def self.required_fields
449
+ %i[color].freeze
450
+ end
451
+
452
+ def initialize(data = {})
453
+ @color = data["color"]
454
+ end
455
+
456
+ def to_h
457
+ {
458
+ "color" => @color,
459
+ }.compact
460
+ end
461
+
462
+ def to_json(*args)
463
+ to_h.to_json(*args)
464
+ end
465
+ end
466
+
345
467
  # Campfire
346
468
  class Campfire
347
469
  include TypeHelpers
@@ -1351,6 +1473,107 @@ module Basecamp
1351
1473
  end
1352
1474
  end
1353
1475
 
1476
+ # Draft
1477
+ class Draft
1478
+ include TypeHelpers
1479
+ attr_accessor :app_url, :bucket, :created_at, :excerpt, :id, :parent, :scheduled_posting_at, :title, :type, :updated_at
1480
+
1481
+ # @return [Array<Symbol>]
1482
+ def self.required_fields
1483
+ %i[app_url bucket created_at excerpt id parent scheduled_posting_at title type updated_at].freeze
1484
+ end
1485
+
1486
+ def initialize(data = {})
1487
+ @app_url = data["app_url"]
1488
+ @bucket = parse_type(data["bucket"], "DraftBucket")
1489
+ @created_at = parse_datetime(data["created_at"])
1490
+ @excerpt = data["excerpt"]
1491
+ @id = parse_integer(data["id"])
1492
+ @parent = data["parent"]
1493
+ @scheduled_posting_at = data["scheduled_posting_at"]
1494
+ @title = data["title"]
1495
+ @type = data["type"]
1496
+ @updated_at = parse_datetime(data["updated_at"])
1497
+ end
1498
+
1499
+ def to_h
1500
+ {
1501
+ "app_url" => @app_url,
1502
+ "bucket" => @bucket,
1503
+ "created_at" => @created_at,
1504
+ "excerpt" => @excerpt,
1505
+ "id" => @id,
1506
+ "parent" => @parent,
1507
+ "scheduled_posting_at" => @scheduled_posting_at,
1508
+ "title" => @title,
1509
+ "type" => @type,
1510
+ "updated_at" => @updated_at,
1511
+ }.reject { |k, v| v.nil? && !["scheduled_posting_at"].include?(k) }
1512
+ end
1513
+
1514
+ def to_json(*args)
1515
+ to_h.to_json(*args)
1516
+ end
1517
+ end
1518
+
1519
+ # DraftBucket
1520
+ class DraftBucket
1521
+ include TypeHelpers
1522
+ attr_accessor :app_url, :id, :name
1523
+
1524
+ # @return [Array<Symbol>]
1525
+ def self.required_fields
1526
+ %i[app_url id name].freeze
1527
+ end
1528
+
1529
+ def initialize(data = {})
1530
+ @app_url = data["app_url"]
1531
+ @id = parse_integer(data["id"])
1532
+ @name = data["name"]
1533
+ end
1534
+
1535
+ def to_h
1536
+ {
1537
+ "app_url" => @app_url,
1538
+ "id" => @id,
1539
+ "name" => @name,
1540
+ }.compact
1541
+ end
1542
+
1543
+ def to_json(*args)
1544
+ to_h.to_json(*args)
1545
+ end
1546
+ end
1547
+
1548
+ # DraftParent
1549
+ class DraftParent
1550
+ include TypeHelpers
1551
+ attr_accessor :app_url, :id, :title
1552
+
1553
+ # @return [Array<Symbol>]
1554
+ def self.required_fields
1555
+ %i[app_url id title].freeze
1556
+ end
1557
+
1558
+ def initialize(data = {})
1559
+ @app_url = data["app_url"]
1560
+ @id = parse_integer(data["id"])
1561
+ @title = data["title"]
1562
+ end
1563
+
1564
+ def to_h
1565
+ {
1566
+ "app_url" => @app_url,
1567
+ "id" => @id,
1568
+ "title" => @title,
1569
+ }.compact
1570
+ end
1571
+
1572
+ def to_json(*args)
1573
+ to_h.to_json(*args)
1574
+ end
1575
+ end
1576
+
1354
1577
  # Event
1355
1578
  class Event
1356
1579
  include TypeHelpers
@@ -1414,39 +1637,6 @@ module Basecamp
1414
1637
  end
1415
1638
  end
1416
1639
 
1417
- # EverythingBoost
1418
- class EverythingBoost
1419
- include TypeHelpers
1420
- attr_accessor :booster, :content, :created_at, :id, :recording
1421
-
1422
- # @return [Array<Symbol>]
1423
- def self.required_fields
1424
- %i[booster content created_at id recording].freeze
1425
- end
1426
-
1427
- def initialize(data = {})
1428
- @booster = parse_type(data["booster"], "Person")
1429
- @content = data["content"]
1430
- @created_at = parse_datetime(data["created_at"])
1431
- @id = parse_integer(data["id"])
1432
- @recording = parse_type(data["recording"], "Recording")
1433
- end
1434
-
1435
- def to_h
1436
- {
1437
- "booster" => @booster,
1438
- "content" => @content,
1439
- "created_at" => @created_at,
1440
- "id" => @id,
1441
- "recording" => @recording,
1442
- }.compact
1443
- end
1444
-
1445
- def to_json(*args)
1446
- to_h.to_json(*args)
1447
- end
1448
- end
1449
-
1450
1640
  # EverythingFile
1451
1641
  class EverythingFile
1452
1642
  include TypeHelpers
@@ -1529,6 +1719,31 @@ module Basecamp
1529
1719
  end
1530
1720
  end
1531
1721
 
1722
+ # FieldKeyedErrors
1723
+ class FieldKeyedErrors
1724
+ include TypeHelpers
1725
+ attr_accessor :errors
1726
+
1727
+ # @return [Array<Symbol>]
1728
+ def self.required_fields
1729
+ %i[errors].freeze
1730
+ end
1731
+
1732
+ def initialize(data = {})
1733
+ @errors = parse_type(data["errors"], "FieldErrorMap")
1734
+ end
1735
+
1736
+ def to_h
1737
+ {
1738
+ "errors" => @errors,
1739
+ }.compact
1740
+ end
1741
+
1742
+ def to_json(*args)
1743
+ to_h.to_json(*args)
1744
+ end
1745
+ end
1746
+
1532
1747
  # Forward
1533
1748
  class Forward
1534
1749
  include TypeHelpers
@@ -2312,6 +2527,70 @@ module Basecamp
2312
2527
  end
2313
2528
  end
2314
2529
 
2530
+ # MyNote
2531
+ class MyNote
2532
+ include TypeHelpers
2533
+ attr_accessor :app_url, :content, :content_attachments, :created_at, :id, :type, :updated_at, :url
2534
+
2535
+ # @return [Array<Symbol>]
2536
+ def self.required_fields
2537
+ %i[app_url content content_attachments created_at id type updated_at url].freeze
2538
+ end
2539
+
2540
+ def initialize(data = {})
2541
+ @app_url = data["app_url"]
2542
+ @content = data["content"]
2543
+ @content_attachments = parse_array(data["content_attachments"], "RichTextAttachment")
2544
+ @created_at = data["created_at"]
2545
+ @id = data["id"]
2546
+ @type = data["type"]
2547
+ @updated_at = data["updated_at"]
2548
+ @url = data["url"]
2549
+ end
2550
+
2551
+ def to_h
2552
+ {
2553
+ "app_url" => @app_url,
2554
+ "content" => @content,
2555
+ "content_attachments" => @content_attachments,
2556
+ "created_at" => @created_at,
2557
+ "id" => @id,
2558
+ "type" => @type,
2559
+ "updated_at" => @updated_at,
2560
+ "url" => @url,
2561
+ }.reject { |k, v| v.nil? && !["created_at", "id", "updated_at"].include?(k) }
2562
+ end
2563
+
2564
+ def to_json(*args)
2565
+ to_h.to_json(*args)
2566
+ end
2567
+ end
2568
+
2569
+ # MyNoteAttributes
2570
+ class MyNoteAttributes
2571
+ include TypeHelpers
2572
+ attr_accessor :content
2573
+
2574
+ # @return [Array<Symbol>]
2575
+ def self.required_fields
2576
+ %i[content].freeze
2577
+ end
2578
+
2579
+ def initialize(data = {})
2580
+ @content = data["content"]
2581
+ end
2582
+
2583
+ def to_h
2584
+ {
2585
+ "content" => @content,
2586
+ }.compact
2587
+ end
2588
+
2589
+ def to_json(*args)
2590
+ to_h.to_json(*args)
2591
+ end
2592
+ end
2593
+
2315
2594
  # Notification
2316
2595
  class Notification
2317
2596
  include TypeHelpers
data/lib/basecamp/http.rb CHANGED
@@ -55,9 +55,12 @@ module Basecamp
55
55
  # Performs a GET request.
56
56
  # @param path [String] URL path
57
57
  # @param params [Hash] query parameters
58
+ # @param operation [String, nil] canonical operation ID; when given, the
59
+ # operation's declared retry block governs attempts (max as a ceiling on
60
+ # the configured cap) and retryable statuses (the declared retryOn set)
58
61
  # @return [Response]
59
- def get(path, params: {})
60
- request(:get, path, params: params)
62
+ def get(path, params: {}, operation: nil)
63
+ request(:get, path, params: params, operation: operation)
61
64
  end
62
65
 
63
66
  # Performs a GET request to an absolute URL.
@@ -171,8 +174,8 @@ module Basecamp
171
174
  # @param params [Hash] query parameters
172
175
  # @yield [Hash] each item from the response
173
176
  # @return [Enumerator] if no block given
174
- def paginate(path, params: {}, &block)
175
- return to_enum(:paginate, path, params: params) unless block
177
+ def paginate(path, params: {}, operation: nil, &block)
178
+ return to_enum(:paginate, path, params: params, operation: operation) unless block
176
179
 
177
180
  base_url = build_url(path)
178
181
  url = base_url
@@ -183,7 +186,7 @@ module Basecamp
183
186
  break if page > @config.max_pages
184
187
 
185
188
  @hooks.on_paginate(url, page)
186
- response = get(url, params: page == 1 ? params : {})
189
+ response = get(url, params: page == 1 ? params : {}, operation: operation)
187
190
 
188
191
  Security.check_body_size!(response.body, Security::MAX_RESPONSE_BODY_BYTES)
189
192
 
@@ -217,8 +220,8 @@ module Basecamp
217
220
  # @param params [Hash] query parameters
218
221
  # @yield [Hash] each item from the response
219
222
  # @return [Enumerator] if no block given
220
- def paginate_key(path, key:, params: {}, &block)
221
- return to_enum(:paginate_key, path, key: key, params: params) unless block
223
+ def paginate_key(path, key:, params: {}, operation: nil, &block)
224
+ return to_enum(:paginate_key, path, key: key, params: params, operation: operation) unless block
222
225
 
223
226
  base_url = build_url(path)
224
227
  url = base_url
@@ -229,7 +232,7 @@ module Basecamp
229
232
  break if page > @config.max_pages
230
233
 
231
234
  @hooks.on_paginate(url, page)
232
- response = get(url, params: page == 1 ? params : {})
235
+ response = get(url, params: page == 1 ? params : {}, operation: operation)
233
236
 
234
237
  Security.check_body_size!(response.body, Security::MAX_RESPONSE_BODY_BYTES)
235
238
 
@@ -266,11 +269,11 @@ module Basecamp
266
269
  # @param key [String] the key containing the array of paginated items
267
270
  # @param params [Hash] query parameters
268
271
  # @return [Hash] wrapper fields merged with key => Enumerator of all items
269
- def paginate_wrapped(path, key:, params: {})
272
+ def paginate_wrapped(path, key:, params: {}, operation: nil)
270
273
  base_url = build_url(path)
271
274
 
272
275
  @hooks.on_paginate(base_url, 1)
273
- first_response = get(base_url, params: params)
276
+ first_response = get(base_url, params: params, operation: operation)
274
277
  Security.check_body_size!(first_response.body, Security::MAX_RESPONSE_BODY_BYTES)
275
278
 
276
279
  begin
@@ -304,7 +307,7 @@ module Basecamp
304
307
  end
305
308
 
306
309
  @hooks.on_paginate(next_url, page)
307
- response = get(next_url)
310
+ response = get(next_url, operation: operation)
308
311
  Security.check_body_size!(response.body, Security::MAX_RESPONSE_BODY_BYTES)
309
312
 
310
313
  begin
@@ -340,34 +343,37 @@ module Basecamp
340
343
  end
341
344
  end
342
345
 
343
- def request(method, path, params: {}, body: nil, allow_cross_origin: false)
346
+ def request(method, path, params: {}, body: nil, allow_cross_origin: false, operation: nil)
344
347
  url = build_url(path, allow_cross_origin: allow_cross_origin)
345
348
 
346
349
  # Mutations don't retry on 429/5xx to avoid duplicating data
347
350
  if method == :get
348
- request_with_retry(method, url, params: params, allow_cross_origin: allow_cross_origin)
351
+ request_with_retry(method, url, params: params, allow_cross_origin: allow_cross_origin, operation: operation)
349
352
  else
350
353
  single_request(method, url, params: params, body: body, attempt: 1, allow_cross_origin: allow_cross_origin)
351
354
  end
352
355
  end
353
356
 
354
- def request_with_retry(method, url, params: {}, allow_cross_origin: false)
357
+ def request_with_retry(method, url, params: {}, allow_cross_origin: false, operation: nil)
358
+ op_retry = operation && Http.operation_retry(operation)
359
+ caller_cap = [ @config.max_retries, 1 ].max
360
+ max_attempts = op_retry ? [ caller_cap, op_retry.fetch("maxAttempts") ].min : @config.max_retries
355
361
  attempt = 0
356
362
  last_error = nil
357
363
 
358
364
  loop do
359
365
  attempt += 1
360
- break if attempt > @config.max_retries
366
+ break if attempt > max_attempts
361
367
 
362
368
  begin
363
369
  return single_request(method, url, params: params, body: nil, attempt: attempt, allow_cross_origin: allow_cross_origin)
364
370
  rescue Basecamp::RateLimitError, Basecamp::NetworkError, Basecamp::ApiError => e
365
- raise e unless e.retryable?
371
+ raise e unless retry_eligible?(e, op_retry)
366
372
 
367
373
  last_error = e
368
374
 
369
375
  # Don't sleep if this was the last attempt
370
- break if attempt >= @config.max_retries
376
+ break if attempt >= max_attempts
371
377
 
372
378
  delay = calculate_delay(attempt, e.retry_after)
373
379
 
@@ -377,8 +383,30 @@ module Basecamp
377
383
  end
378
384
  end
379
385
 
380
- noun = @config.max_retries == 1 ? "attempt" : "attempts"
381
- raise last_error || Basecamp::ApiError.new("Request failed after #{@config.max_retries} #{noun}")
386
+ noun = max_attempts == 1 ? "attempt" : "attempts"
387
+ raise last_error || Basecamp::ApiError.new("Request failed after #{max_attempts} #{noun}")
388
+ end
389
+
390
+ # For a governed request (operation given), a status-bearing error retries
391
+ # exactly when the operation's declared retryOn set says so — the error
392
+ # taxonomy's retryable flag neither widens the set (500 is retryable in
393
+ # errors.rb but not declared) nor vetoes it. Status-less errors (network
394
+ # failures) and all ungoverned traffic keep the taxonomy's judgment.
395
+ def retry_eligible?(error, op_retry)
396
+ if op_retry && error.http_status
397
+ op_retry.fetch("retryOn").include?(error.http_status)
398
+ else
399
+ error.retryable?
400
+ end
401
+ end
402
+
403
+ # Memoized per-operation retry metadata, keyed by canonical operation ID.
404
+ # Benign-race memoization: concurrent first loads compute identical values.
405
+ def self.operation_retry(operation)
406
+ @operation_metadata ||= JSON.parse(
407
+ File.read(File.join(__dir__, "generated", "metadata.json"))
408
+ ).fetch("operations").freeze
409
+ @operation_metadata.dig(operation, "retry")
382
410
  end
383
411
 
384
412
  def single_request(method, url, params:, body:, attempt:, retry_count: 0, allow_cross_origin: false)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basecamp
4
+ module Oauth
5
+ # RFC 8628 device authorization response (the device/user code pair the user
6
+ # approves out of band).
7
+ #
8
+ # @attr device_code [String] The device verification code polled at the token
9
+ # endpoint.
10
+ # @attr user_code [String] The code the user enters at the verification URI.
11
+ # @attr verification_uri [String] Where the user goes to approve the request.
12
+ # @attr verification_uri_complete [String, nil] The verification URI with the
13
+ # user code pre-filled (optional; absent when the server omits it).
14
+ # @attr expires_in [Integer] Lifetime of the device/user codes in seconds.
15
+ # @attr interval [Integer] Minimum seconds to wait between token polls
16
+ # (defaults to 5 when the server omits it).
17
+ DeviceAuthorization = Data.define(
18
+ :device_code,
19
+ :user_code,
20
+ :verification_uri,
21
+ :verification_uri_complete,
22
+ :expires_in,
23
+ :interval
24
+ ) do
25
+ def initialize(
26
+ device_code:,
27
+ user_code:,
28
+ verification_uri:,
29
+ expires_in:,
30
+ interval:,
31
+ verification_uri_complete: nil
32
+ )
33
+ super
34
+ end
35
+ end
36
+ end
37
+ end