diff --git a/extension/js/common/api/email-provider/gmail/gmail.ts b/extension/js/common/api/email-provider/gmail/gmail.ts index 42b1b4929b9..9a19ff4519f 100644 --- a/extension/js/common/api/email-provider/gmail/gmail.ts +++ b/extension/js/common/api/email-provider/gmail/gmail.ts @@ -17,7 +17,8 @@ import { Google } from './google.js'; import { GoogleOAuth } from '../../authentication/google/google-oauth.js'; import { SendableMsg } from '../sendable-msg.js'; import { KeyStore } from '../../../platform/store/key-store.js'; -import { AjaxErr } from '../../shared/api-error.js'; +import { AjaxErr, ApiErr, MAX_RATE_LIMIT_ERROR_RETRY_COUNT } from '../../shared/api-error.js'; +import { Time } from '../../../browser/time.js'; export type GmailResponseFormat = 'raw' | 'full' | 'metadata'; @@ -33,8 +34,16 @@ export class Gmail extends EmailProviderApi implements EmailProviderInterface { } }; - public threadGet = async (threadId: string, format?: GmailResponseFormat, progressCb?: ProgressCb): Promise => { - return await Google.gmailCall(this.acctEmail, `threads/${threadId}`, { method: 'GET', data: { format } }, { download: progressCb }); + public threadGet = async (threadId: string, format?: GmailResponseFormat, progressCb?: ProgressCb, retryCount = 0): Promise => { + try { + return await Google.gmailCall(this.acctEmail, `threads/${threadId}`, { method: 'GET', data: { format } }, { download: progressCb }); + } catch (e) { + if (ApiErr.isRateLimit(e) && retryCount < MAX_RATE_LIMIT_ERROR_RETRY_COUNT) { + await Time.sleep(1000); + return await this.threadGet(threadId, format, progressCb, retryCount + 1); + } + throw e; + } }; public threadList = async (labelId: string): Promise => { @@ -126,13 +135,21 @@ export class Gmail extends EmailProviderApi implements EmailProviderInterface { * because strings over 1 MB may fail to get to/from bg page. A way to mitigate that would be to pass `R.GmailMsg$raw` prop * as a Buf instead of a string. */ - public msgGet = async (msgId: string, format: GmailResponseFormat, progressCb?: ProgressCb): Promise => { - return await Google.gmailCall( - this.acctEmail, - `messages/${msgId}`, - { method: 'GET', data: { format: format || 'full' } }, - progressCb ? { download: progressCb } : undefined - ); + public msgGet = async (msgId: string, format: GmailResponseFormat, progressCb?: ProgressCb, retryCount = 0): Promise => { + try { + return await Google.gmailCall( + this.acctEmail, + `messages/${msgId}`, + { method: 'GET', data: { format: format || 'full' } }, + progressCb ? { download: progressCb } : undefined + ); + } catch (e) { + if (ApiErr.isRateLimit(e) && retryCount < MAX_RATE_LIMIT_ERROR_RETRY_COUNT) { + await Time.sleep(1000); + return await this.msgGet(msgId, format, progressCb, retryCount + 1); + } + throw e; + } }; public msgsGet = async (msgIds: string[], format: GmailResponseFormat): Promise => { diff --git a/extension/js/common/api/shared/api-error.ts b/extension/js/common/api/shared/api-error.ts index 01a9319377b..8d2b70c94c7 100644 --- a/extension/js/common/api/shared/api-error.ts +++ b/extension/js/common/api/shared/api-error.ts @@ -29,6 +29,8 @@ abstract class AuthErr extends Error {} export class GoogleAuthErr extends AuthErr {} export class BackendAuthErr extends AuthErr {} +export const MAX_RATE_LIMIT_ERROR_RETRY_COUNT = 5; + abstract class ApiCallErr extends Error { protected static describeApiAction = (req: { url: string; method?: string; data?: unknown }) => { const describeBody = typeof req.data === 'undefined' ? '(no body)' : typeof req.data;