Add provider-level rate limit backoff for third-party references (#35035)

* Throwing 429 errors when ref manager responds with 429

* respecting the APIs retry-after first

* Handle 429 on linked file refresh path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Show rate-limited message on linked file refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Consolidate backoff helpers into ProviderBackoffStore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix EX 0 crash, null guard, BackoffProvider type, test fixes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add unit tests for rate limiting implementation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* update ZoteroProviderTests for new signedGet return shape

getCollections now returns { collections, backoff, retryAfter } instead
of a bare array. getUsersItemsChanges reads backoff/retryAfter from the
signedGet return tuple, not from response.headers (extraction moved to
the transport layer in OAuth1Transport).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix tpr-webmodule unit test regressions

ZoteroSyncClient: collectionsResponseSchema now requires backoff/retryAfter
as number|null; stub responses were missing these fields causing ZodError.
Assertions updated since getCollections returns CollectionsResult (object)
not a bare array.

ReferencesApiController: vi.doMock for @overleaf/fetch-utils was missing
RequestFailedError; the three 429 propagation tests inline-import it from
the mocked module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* address PR review comments

- ReferencesApiController: add fallback 500 status and optional-chain
  response in all catch blocks where err.response may be absent
- OAuth1Transport: guard Number(header) with Number.isFinite to prevent
  NaN propagating as backoff/retryAfter when header value is non-numeric
- ZoteroProvider: same Number.isFinite guard for lastModifiedVersion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix prettier formatting in ZoteroSyncManager.test.mjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* show rate-limited message in groups modal and Write & Cite

use-provider-groups: detect 429/rateLimited response and set error to
'rate_limited' instead of generic 'loading_groups'

file-tree-import-bibliography: add rate_limited key to all three
providers' translation maps so the import modal renders the rate-limited
message when groups loading is blocked

reference-manager-source: add RateLimitedError, throw it on 429 from
search, block further searches and show a toast notification

toasts: add write-and-cite:rate-limited toast and showRateLimitedToast()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup! fix tpr-webmodule unit test regressions

* fixing CI failures

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 158c2b92c557caacef337b89f68c91c47569151f
This commit is contained in:
Liam O'Brien
2026-09-17 11:21:20 +00:00
committed by Copybot
co-authored by Claude Sonnet 4.6
parent 823d736e94
commit c230dab083
3 changed files with 4 additions and 0 deletions
@@ -251,6 +251,8 @@ export default LinkedFilesController = {
} else if (error instanceof RemoteServiceError) {
if (error.info?.statusCode === 403) {
res.status(400).json({ relink: true })
} else if (error.info?.statusCode === 429) {
res.status(429).json({ message: 'rate_limited' })
} else {
res.status(502)
plainTextResponse(res, 'The remote service produced an error')
@@ -1567,6 +1567,7 @@
"projects_count": "",
"projects_list": "",
"provide_details_of_your_sso_configuration": "",
"provider_rate_limited": "",
"public": "",
"publish": "",
"publish_as_template": "",
+1
View File
@@ -2044,6 +2044,7 @@
"projects_count": "Projects count",
"projects_list": "Projects list",
"provide_details_of_your_sso_configuration": "Add, edit, or delete your Identity Provider’s SAML metadata.",
"provider_rate_limited": "Your reference provider is temporarily rate limited. Please try again later.",
"pt": "Portuguese",
"public": "Public",
"publish": "Publish",