basecamp-sdk 0.2.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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +14 -0
  3. data/.yardopts +6 -0
  4. data/README.md +293 -0
  5. data/Rakefile +26 -0
  6. data/basecamp-sdk.gemspec +46 -0
  7. data/lib/basecamp/auth_strategy.rb +38 -0
  8. data/lib/basecamp/chain_hooks.rb +45 -0
  9. data/lib/basecamp/client.rb +428 -0
  10. data/lib/basecamp/config.rb +143 -0
  11. data/lib/basecamp/errors.rb +289 -0
  12. data/lib/basecamp/generated/metadata.json +2281 -0
  13. data/lib/basecamp/generated/services/attachments_service.rb +24 -0
  14. data/lib/basecamp/generated/services/boosts_service.rb +70 -0
  15. data/lib/basecamp/generated/services/campfires_service.rb +122 -0
  16. data/lib/basecamp/generated/services/card_columns_service.rb +103 -0
  17. data/lib/basecamp/generated/services/card_steps_service.rb +57 -0
  18. data/lib/basecamp/generated/services/card_tables_service.rb +20 -0
  19. data/lib/basecamp/generated/services/cards_service.rb +66 -0
  20. data/lib/basecamp/generated/services/checkins_service.rb +157 -0
  21. data/lib/basecamp/generated/services/client_approvals_service.rb +28 -0
  22. data/lib/basecamp/generated/services/client_correspondences_service.rb +28 -0
  23. data/lib/basecamp/generated/services/client_replies_service.rb +30 -0
  24. data/lib/basecamp/generated/services/client_visibility_service.rb +21 -0
  25. data/lib/basecamp/generated/services/comments_service.rb +49 -0
  26. data/lib/basecamp/generated/services/documents_service.rb +52 -0
  27. data/lib/basecamp/generated/services/events_service.rb +20 -0
  28. data/lib/basecamp/generated/services/forwards_service.rb +67 -0
  29. data/lib/basecamp/generated/services/lineup_service.rb +44 -0
  30. data/lib/basecamp/generated/services/message_boards_service.rb +20 -0
  31. data/lib/basecamp/generated/services/message_types_service.rb +59 -0
  32. data/lib/basecamp/generated/services/messages_service.rb +75 -0
  33. data/lib/basecamp/generated/services/people_service.rb +73 -0
  34. data/lib/basecamp/generated/services/projects_service.rb +63 -0
  35. data/lib/basecamp/generated/services/recordings_service.rb +64 -0
  36. data/lib/basecamp/generated/services/reports_service.rb +56 -0
  37. data/lib/basecamp/generated/services/schedules_service.rb +92 -0
  38. data/lib/basecamp/generated/services/search_service.rb +31 -0
  39. data/lib/basecamp/generated/services/subscriptions_service.rb +50 -0
  40. data/lib/basecamp/generated/services/templates_service.rb +82 -0
  41. data/lib/basecamp/generated/services/timeline_service.rb +20 -0
  42. data/lib/basecamp/generated/services/timesheets_service.rb +81 -0
  43. data/lib/basecamp/generated/services/todolist_groups_service.rb +41 -0
  44. data/lib/basecamp/generated/services/todolists_service.rb +53 -0
  45. data/lib/basecamp/generated/services/todos_service.rb +106 -0
  46. data/lib/basecamp/generated/services/todosets_service.rb +20 -0
  47. data/lib/basecamp/generated/services/tools_service.rb +80 -0
  48. data/lib/basecamp/generated/services/uploads_service.rb +61 -0
  49. data/lib/basecamp/generated/services/vaults_service.rb +49 -0
  50. data/lib/basecamp/generated/services/webhooks_service.rb +63 -0
  51. data/lib/basecamp/generated/types.rb +3196 -0
  52. data/lib/basecamp/hooks.rb +70 -0
  53. data/lib/basecamp/http.rb +440 -0
  54. data/lib/basecamp/logger_hooks.rb +46 -0
  55. data/lib/basecamp/noop_hooks.rb +9 -0
  56. data/lib/basecamp/oauth/discovery.rb +123 -0
  57. data/lib/basecamp/oauth/errors.rb +35 -0
  58. data/lib/basecamp/oauth/exchange.rb +291 -0
  59. data/lib/basecamp/oauth/pkce.rb +68 -0
  60. data/lib/basecamp/oauth/types.rb +133 -0
  61. data/lib/basecamp/oauth.rb +56 -0
  62. data/lib/basecamp/oauth_token_provider.rb +108 -0
  63. data/lib/basecamp/operation_info.rb +17 -0
  64. data/lib/basecamp/request_info.rb +10 -0
  65. data/lib/basecamp/request_result.rb +14 -0
  66. data/lib/basecamp/security.rb +112 -0
  67. data/lib/basecamp/services/attachments_service.rb +33 -0
  68. data/lib/basecamp/services/authorization_service.rb +47 -0
  69. data/lib/basecamp/services/base_service.rb +146 -0
  70. data/lib/basecamp/services/campfires_service.rb +141 -0
  71. data/lib/basecamp/services/card_columns_service.rb +106 -0
  72. data/lib/basecamp/services/card_steps_service.rb +86 -0
  73. data/lib/basecamp/services/card_tables_service.rb +23 -0
  74. data/lib/basecamp/services/cards_service.rb +93 -0
  75. data/lib/basecamp/services/checkins_service.rb +127 -0
  76. data/lib/basecamp/services/client_approvals_service.rb +33 -0
  77. data/lib/basecamp/services/client_correspondences_service.rb +33 -0
  78. data/lib/basecamp/services/client_replies_service.rb +35 -0
  79. data/lib/basecamp/services/comments_service.rb +63 -0
  80. data/lib/basecamp/services/documents_service.rb +74 -0
  81. data/lib/basecamp/services/events_service.rb +27 -0
  82. data/lib/basecamp/services/forwards_service.rb +80 -0
  83. data/lib/basecamp/services/lineup_service.rb +67 -0
  84. data/lib/basecamp/services/message_boards_service.rb +24 -0
  85. data/lib/basecamp/services/message_types_service.rb +79 -0
  86. data/lib/basecamp/services/messages_service.rb +133 -0
  87. data/lib/basecamp/services/people_service.rb +73 -0
  88. data/lib/basecamp/services/projects_service.rb +67 -0
  89. data/lib/basecamp/services/recordings_service.rb +127 -0
  90. data/lib/basecamp/services/reports_service.rb +80 -0
  91. data/lib/basecamp/services/schedules_service.rb +156 -0
  92. data/lib/basecamp/services/search_service.rb +36 -0
  93. data/lib/basecamp/services/subscriptions_service.rb +67 -0
  94. data/lib/basecamp/services/templates_service.rb +96 -0
  95. data/lib/basecamp/services/timeline_service.rb +62 -0
  96. data/lib/basecamp/services/timesheet_service.rb +68 -0
  97. data/lib/basecamp/services/todolist_groups_service.rb +100 -0
  98. data/lib/basecamp/services/todolists_service.rb +104 -0
  99. data/lib/basecamp/services/todos_service.rb +156 -0
  100. data/lib/basecamp/services/todosets_service.rb +23 -0
  101. data/lib/basecamp/services/tools_service.rb +89 -0
  102. data/lib/basecamp/services/uploads_service.rb +84 -0
  103. data/lib/basecamp/services/vaults_service.rb +84 -0
  104. data/lib/basecamp/services/webhooks_service.rb +88 -0
  105. data/lib/basecamp/static_token_provider.rb +24 -0
  106. data/lib/basecamp/token_provider.rb +42 -0
  107. data/lib/basecamp/version.rb +6 -0
  108. data/lib/basecamp/webhooks/event.rb +52 -0
  109. data/lib/basecamp/webhooks/rack_middleware.rb +49 -0
  110. data/lib/basecamp/webhooks/receiver.rb +161 -0
  111. data/lib/basecamp/webhooks/verify.rb +36 -0
  112. data/lib/basecamp.rb +107 -0
  113. data/scripts/generate-metadata.rb +106 -0
  114. data/scripts/generate-services.rb +778 -0
  115. data/scripts/generate-types.rb +191 -0
  116. metadata +316 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5251d36b00bfd269e7fce1bc324c2c9b491b9d0f0d30296807c0b1b724272adc
4
+ data.tar.gz: c590cec999d1b5c687456d8cdf1259712df2fcb26260048410ac5edd41dd772b
5
+ SHA512:
6
+ metadata.gz: 874a76ab2b404c5ad8bada234f9002ba4e50d3ad5e574352d0f6cc53991efa656f0adb0ddd1701df52e66966ed201740b1156c9aaa7a3193422c9ab82fa877ae
7
+ data.tar.gz: 05570761aa1e7a59823ed614505391415f883b41fcf7ba4b4197f51ad2ad07cf7ce9d7ecd7722ecfe7e60d759fb963638f7bfe621b20406233dcf1b0a1909400
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ # 37signals house style
2
+ inherit_gem:
3
+ rubocop-37signals: rubocop.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 3.2
7
+ Exclude:
8
+ - "vendor/**/*"
9
+ - "lib/basecamp/generated/**/*"
10
+
11
+ # Documentation not required for tests
12
+ Style/Documentation:
13
+ Exclude:
14
+ - "test/**/*"
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --output-dir doc
2
+ --protected
3
+ --no-private
4
+ --embed-mixins
5
+ --title "Basecamp Ruby SDK"
6
+ lib/**/*.rb
data/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # Basecamp Ruby SDK
2
+
3
+ Official Ruby SDK for the [Basecamp API](https://github.com/basecamp/bc3-api).
4
+
5
+ ## Requirements
6
+
7
+ - Ruby 3.2+
8
+ - Faraday HTTP client
9
+
10
+ ## Installation
11
+
12
+ Add to your Gemfile:
13
+
14
+ ```ruby
15
+ gem "basecamp-sdk"
16
+ ```
17
+
18
+ Or install directly:
19
+
20
+ ```bash
21
+ gem install basecamp-sdk
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```ruby
27
+ require "basecamp"
28
+
29
+ # Create client with access token
30
+ client = Basecamp.client(access_token: ENV["BASECAMP_TOKEN"])
31
+
32
+ # Scope to an account
33
+ account = client.for_account(ENV["BASECAMP_ACCOUNT_ID"])
34
+
35
+ # List projects
36
+ account.projects.list.each do |project|
37
+ puts "#{project['id']}: #{project['name']}"
38
+ end
39
+
40
+ # Get a specific project
41
+ project = account.projects.get(project_id: 12345)
42
+
43
+ # Create a todo
44
+ todo = account.todos.create(
45
+ project_id: 12345,
46
+ todolist_id: 67890,
47
+ content: "Review PR",
48
+ due_on: "2024-12-31"
49
+ )
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ ### Basic Configuration
55
+
56
+ ```ruby
57
+ config = Basecamp::Config.new(
58
+ base_url: "https://3.basecampapi.com", # Default
59
+ timeout: 30, # Request timeout in seconds
60
+ max_retries: 3, # Max retry attempts for GET requests
61
+ base_delay: 1.0, # Base delay for exponential backoff
62
+ max_pages: 10_000 # Max pages for pagination
63
+ )
64
+
65
+ token_provider = Basecamp::StaticTokenProvider.new(ENV["BASECAMP_TOKEN"])
66
+ client = Basecamp::Client.new(config: config, token_provider: token_provider)
67
+ ```
68
+
69
+ ### Configuration Options
70
+
71
+ | Option | Default | Description |
72
+ |--------|---------|-------------|
73
+ | `base_url` | `https://3.basecampapi.com` | Basecamp API base URL |
74
+ | `timeout` | `30` | HTTP request timeout (seconds) |
75
+ | `max_retries` | `3` | Maximum retry attempts for GET requests |
76
+ | `base_delay` | `1.0` | Base delay for exponential backoff (seconds) |
77
+ | `max_jitter` | `0.1` | Maximum random jitter added to delays |
78
+ | `max_pages` | `10_000` | Maximum pages to fetch during pagination |
79
+
80
+ ## OAuth Authentication
81
+
82
+ ### Token Providers
83
+
84
+ The SDK supports multiple authentication patterns:
85
+
86
+ ```ruby
87
+ # Static token (simplest)
88
+ token_provider = Basecamp::StaticTokenProvider.new("your-access-token")
89
+
90
+ # OAuth with refresh
91
+ token_provider = Basecamp::OauthTokenProvider.new(
92
+ access_token: "access-token",
93
+ refresh_token: "refresh-token",
94
+ expires_at: Time.now + 3600,
95
+ client_id: ENV["BASECAMP_CLIENT_ID"],
96
+ client_secret: ENV["BASECAMP_CLIENT_SECRET"]
97
+ )
98
+ ```
99
+
100
+ ### OAuth Flow Helpers
101
+
102
+ ```ruby
103
+ # 1. Discover OAuth configuration
104
+ config = Basecamp::Oauth.discover_launchpad
105
+
106
+ # 2. Build authorization URL (redirect user here)
107
+ auth_url = "#{config.authorization_endpoint}?" + URI.encode_www_form(
108
+ type: "web_server",
109
+ client_id: ENV["BASECAMP_CLIENT_ID"],
110
+ redirect_uri: "https://myapp.com/callback"
111
+ )
112
+
113
+ # 3. Exchange code for tokens (in callback handler)
114
+ token = Basecamp::Oauth.exchange_code(
115
+ token_endpoint: config.token_endpoint,
116
+ code: params[:code],
117
+ redirect_uri: "https://myapp.com/callback",
118
+ client_id: ENV["BASECAMP_CLIENT_ID"],
119
+ client_secret: ENV["BASECAMP_CLIENT_SECRET"],
120
+ use_legacy_format: true # Required for Launchpad
121
+ )
122
+
123
+ # 4. Use the token
124
+ client = Basecamp.client(access_token: token.access_token)
125
+
126
+ # 5. Refresh when needed
127
+ if token.expired?
128
+ token = Basecamp::Oauth.refresh_token(
129
+ token_endpoint: config.token_endpoint,
130
+ refresh_token: token.refresh_token,
131
+ use_legacy_format: true
132
+ )
133
+ end
134
+ ```
135
+
136
+ ## Services
137
+
138
+ The SDK provides 37 services covering the complete Basecamp API:
139
+
140
+ | Service | Description |
141
+ |---------|-------------|
142
+ | `projects` | Project management |
143
+ | `todos` | Todo items |
144
+ | `todolists` | Todo lists |
145
+ | `todosets` | Todo set containers |
146
+ | `todolist_groups` | Todolist grouping/folders |
147
+ | `people` | People/users |
148
+ | `comments` | Comments on recordings |
149
+ | `messages` | Message posts |
150
+ | `message_boards` | Message boards |
151
+ | `message_types` | Message categories |
152
+ | `campfires` | Chat rooms |
153
+ | `schedules` | Calendar schedules |
154
+ | `documents` | Documents |
155
+ | `vaults` | File folders |
156
+ | `uploads` | File uploads |
157
+ | `attachments` | Binary attachments |
158
+ | `recordings` | Generic recordings |
159
+ | `webhooks` | Webhook subscriptions |
160
+ | `subscriptions` | Notification subscriptions |
161
+ | `templates` | Project templates |
162
+ | `events` | Activity events |
163
+ | `checkins` | Automatic check-ins |
164
+ | `forwards` | Email forwards |
165
+ | `cards` | Card table cards |
166
+ | `card_tables` | Card tables (kanban) |
167
+ | `card_columns` | Card table columns |
168
+ | `card_steps` | Card workflow steps |
169
+ | `lineup` | Card lineup view |
170
+ | `tools` | Project dock tools |
171
+ | `search` | Full-text search |
172
+ | `reports` | Activity reports |
173
+ | `timeline` | Activity timeline |
174
+ | `timesheet` | Time tracking reports |
175
+ | `client_approvals` | Client approval workflows |
176
+ | `client_correspondences` | Client communications |
177
+ | `client_replies` | Client replies |
178
+ | `authorization` | Auth info |
179
+
180
+ ## Pagination
181
+
182
+ All list methods return lazy Enumerators that automatically handle pagination:
183
+
184
+ ```ruby
185
+ # Automatically fetches all pages
186
+ account.projects.list.each do |project|
187
+ puts project["name"]
188
+ end
189
+
190
+ # Take only what you need
191
+ first_10 = account.todos.list(todolist_id: 456).take(10)
192
+
193
+ # Convert to array (fetches all pages)
194
+ all_projects = account.projects.list.to_a
195
+ ```
196
+
197
+ ## Retry Behavior
198
+
199
+ GET requests automatically retry on transient failures with exponential backoff:
200
+
201
+ - **Retryable errors**: 429 (rate limit), 502, 503, 504 (gateway errors)
202
+ - **Backoff**: Exponential with jitter (1s, 2s, 4s...)
203
+ - **Rate limits**: Respects `Retry-After` header
204
+
205
+ Mutation operations (POST, PUT, DELETE) do **not** retry to prevent data duplication.
206
+
207
+ ## Error Handling
208
+
209
+ ```ruby
210
+ begin
211
+ account.projects.get(project_id: 99999)
212
+ rescue Basecamp::NotFoundError => e
213
+ puts "Project not found: #{e.message}"
214
+ rescue Basecamp::RateLimitError => e
215
+ puts "Rate limited, retry after: #{e.retry_after} seconds"
216
+ rescue Basecamp::AuthError => e
217
+ puts "Authentication failed: #{e.message}"
218
+ rescue Basecamp::APIError => e
219
+ puts "API error (#{e.http_status}): #{e.message}"
220
+ end
221
+ ```
222
+
223
+ ### Error Types
224
+
225
+ | Error | Description |
226
+ |-------|-------------|
227
+ | `APIError` | Base error class for all API errors |
228
+ | `AuthError` | Authentication failures (401) |
229
+ | `ForbiddenError` | Access denied (403) |
230
+ | `NotFoundError` | Resource not found (404) |
231
+ | `ValidationError` | Invalid request data (400, 422) |
232
+ | `RateLimitError` | Rate limit exceeded (429) |
233
+ | `NetworkError` | Connection failures |
234
+
235
+ ## Observability Hooks
236
+
237
+ Monitor SDK behavior with hooks:
238
+
239
+ ```ruby
240
+ class MyHooks
241
+ include Basecamp::Hooks
242
+
243
+ def on_request_start(info)
244
+ puts "Starting #{info.method} #{info.url}"
245
+ end
246
+
247
+ def on_request_end(info, result)
248
+ puts "Completed in #{result.duration}s with status #{result.status_code}"
249
+ end
250
+
251
+ def on_retry(info, attempt, error, delay)
252
+ puts "Retrying attempt #{attempt} after #{delay}s"
253
+ end
254
+
255
+ def on_paginate(url, page)
256
+ puts "Fetching page #{page}"
257
+ end
258
+ end
259
+
260
+ client = Basecamp::Client.new(
261
+ config: config,
262
+ token_provider: token_provider,
263
+ hooks: MyHooks.new
264
+ )
265
+ ```
266
+
267
+ ## Environment Variables
268
+
269
+ | Variable | Description |
270
+ |----------|-------------|
271
+ | `BASECAMP_TOKEN` | OAuth access token |
272
+ | `BASECAMP_ACCOUNT_ID` | Account ID |
273
+ | `BASECAMP_BASE_URL` | API base URL (default: `https://3.basecampapi.com`) |
274
+
275
+ ## Development
276
+
277
+ ```bash
278
+ # Install dependencies
279
+ bundle install
280
+
281
+ # Run tests
282
+ bundle exec rake test
283
+
284
+ # Run linter
285
+ bundle exec rubocop
286
+
287
+ # Generate types from OpenAPI
288
+ ruby scripts/generate-types.rb
289
+ ```
290
+
291
+ ## License
292
+
293
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+ require 'yard'
7
+
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'test'
10
+ t.libs << 'lib'
11
+ t.test_files = FileList['test/**/*_test.rb']
12
+ end
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ YARD::Rake::YardocTask.new(:doc) do |t|
17
+ t.files = [ 'lib/**/*.rb' ]
18
+ t.options = [ '--output-dir', 'doc' ]
19
+ end
20
+
21
+ desc 'Start an interactive console with the SDK loaded'
22
+ task :console do
23
+ exec 'bin/console'
24
+ end
25
+
26
+ task default: %i[test rubocop]
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/basecamp/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'basecamp-sdk'
7
+ spec.version = Basecamp::VERSION
8
+ spec.authors = [ 'Basecamp' ]
9
+ spec.email = [ 'support@basecamp.com' ]
10
+
11
+ spec.summary = 'Official Ruby SDK for the Basecamp API'
12
+ spec.description = 'A Ruby SDK for the Basecamp API with automatic retry, ' \
13
+ 'exponential backoff, Link header pagination, and observability hooks.'
14
+ spec.homepage = 'https://github.com/basecamp/basecamp-sdk'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>= 3.2.0'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = spec.homepage
20
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/releases"
21
+ spec.metadata['rubygems_mfa_required'] = 'true'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (File.expand_path(f) == __FILE__) ||
27
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
28
+ end
29
+ end
30
+ spec.require_paths = [ 'lib' ]
31
+
32
+ # Runtime dependencies
33
+ spec.add_dependency 'faraday', '~> 2.0'
34
+ spec.add_dependency 'zeitwerk', '~> 2.6'
35
+
36
+ # Development dependencies
37
+ spec.add_development_dependency 'minitest', '~> 6.0'
38
+ spec.add_development_dependency 'rake', '~> 13.0'
39
+ spec.add_development_dependency 'rubocop-37signals'
40
+ spec.add_development_dependency 'simplecov', '~> 0.22'
41
+ spec.add_development_dependency 'webmock', '~> 3.24'
42
+ spec.add_development_dependency 'irb', '~> 1.15'
43
+ spec.add_development_dependency 'rdoc', '~> 7.1'
44
+ spec.add_development_dependency 'webrick', '~> 1.9'
45
+ spec.add_development_dependency 'yard', '~> 0.9'
46
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basecamp
4
+ # AuthStrategy controls how authentication is applied to HTTP requests.
5
+ # The default strategy is BearerAuth, which uses a TokenProvider to set
6
+ # the Authorization header with a Bearer token.
7
+ #
8
+ # Custom strategies can implement alternative auth schemes such as
9
+ # cookie-based auth, API keys, or mutual TLS.
10
+ #
11
+ # To implement a custom strategy, create a class that responds to
12
+ # #authenticate(headers), where headers is a Hash that you can modify.
13
+ module AuthStrategy
14
+ # Apply authentication to the given headers hash.
15
+ # @param headers [Hash] the request headers to modify
16
+ def authenticate(headers)
17
+ raise NotImplementedError, "#{self.class} must implement #authenticate"
18
+ end
19
+ end
20
+
21
+ # Bearer token authentication strategy (default).
22
+ # Sets the Authorization header with "Bearer {token}".
23
+ class BearerAuth
24
+ include AuthStrategy
25
+
26
+ # @param token_provider [TokenProvider] provides access tokens
27
+ def initialize(token_provider)
28
+ @token_provider = token_provider
29
+ end
30
+
31
+ # @return [TokenProvider] the underlying token provider
32
+ attr_reader :token_provider
33
+
34
+ def authenticate(headers)
35
+ headers["Authorization"] = "Bearer #{@token_provider.access_token}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basecamp
4
+ # Composes multiple Hooks implementations, calling them in sequence.
5
+ # Start events are called in order; end events are called in reverse order.
6
+ class ChainHooks
7
+ include Hooks
8
+
9
+ def initialize(*hooks)
10
+ @hooks = hooks
11
+ end
12
+
13
+ def on_operation_start(info)
14
+ @hooks.each { |h| safe_call { h.on_operation_start(info) } }
15
+ end
16
+
17
+ def on_operation_end(info, result)
18
+ @hooks.reverse_each { |h| safe_call { h.on_operation_end(info, result) } }
19
+ end
20
+
21
+ def on_request_start(info)
22
+ @hooks.each { |h| safe_call { h.on_request_start(info) } }
23
+ end
24
+
25
+ def on_request_end(info, result)
26
+ @hooks.reverse_each { |h| safe_call { h.on_request_end(info, result) } }
27
+ end
28
+
29
+ def on_retry(info, attempt, error, delay)
30
+ @hooks.each { |h| safe_call { h.on_retry(info, attempt, error, delay) } }
31
+ end
32
+
33
+ def on_paginate(url, page)
34
+ @hooks.each { |h| safe_call { h.on_paginate(url, page) } }
35
+ end
36
+
37
+ private
38
+
39
+ def safe_call
40
+ yield
41
+ rescue => e
42
+ warn "Basecamp::ChainHooks: hook raised #{e.class}: #{e.message}"
43
+ end
44
+ end
45
+ end