diff --git a/lib/shared/farcaster/farcaster-hooks.js b/lib/shared/farcaster/farcaster-hooks.js --- a/lib/shared/farcaster/farcaster-hooks.js +++ b/lib/shared/farcaster/farcaster-hooks.js @@ -52,6 +52,7 @@ } from '../../types/minimally-encoded-thread-permissions-types'; import type { Dispatch } from '../../types/redux-types.js'; import type { ThreadType } from '../../types/thread-types-enum.js'; +import { tunnelbrokerNotConnected } from '../../types/tunnelbroker/errors.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ClientUpdateInfo } from '../../types/update-types.js'; import { extractFarcasterIDsFromPayload } from '../../utils/conversion-utils.js'; @@ -229,6 +230,8 @@ notAuthorizedToAccessConversationError, ]; +const alwaysRetryableErrors = [tunnelbrokerNotConnected]; + async function withRetry( operation: () => Promise, maxRetries: number = MAX_RETRIES, @@ -244,12 +247,13 @@ } catch (error) { lastError = error; - if (addLog && operationName) { - const message = getMessageForException(error); - const errorIsNotRetryable = notRetryableErrors.some(notRetryableError => - message?.includes(notRetryableError), - ); - if (errorIsNotRetryable) { + const message = getMessageForException(error); + + const errorIsNotRetryable = notRetryableErrors.some(notRetryableError => + message?.includes(notRetryableError), + ); + if (errorIsNotRetryable) { + if (addLog && operationName) { addLog( `Farcaster: Retry attempt ${attempt}/${maxRetries + 1} failed for ${operationName}`, `This isn't a retryable error, so giving up: ${JSON.stringify({ @@ -259,8 +263,27 @@ })}`, new Set([logTypes.FARCASTER]), ); - throw error; } + throw error; + } + + const errorIsAlwaysRetryable = alwaysRetryableErrors.some( + notRetryableError => message?.includes(notRetryableError), + ); + if (errorIsAlwaysRetryable) { + if (addLog && operationName) { + addLog( + `Farcaster: Retry attempt ${attempt}/${maxRetries + 1} ignored for ${operationName}`, + JSON.stringify({ + attempt, + maxRetries, + error: message, + }), + new Set([logTypes.FARCASTER]), + ); + } + attempt--; + } else if (addLog && operationName) { addLog( `Farcaster: Retry attempt ${attempt}/${maxRetries + 1} failed for ${operationName}`, JSON.stringify({ diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js --- a/lib/tunnelbroker/tunnelbroker-context.js +++ b/lib/tunnelbroker/tunnelbroker-context.js @@ -21,6 +21,7 @@ } from '../shared/timeouts.js'; import { isWebPlatform } from '../types/device-types.js'; import type { MessageSentStatus } from '../types/tunnelbroker/device-to-tunnelbroker-request-status-types.js'; +import { tunnelbrokerNotConnected } from '../types/tunnelbroker/errors.js'; import type { FarcasterAPIRequest } from '../types/tunnelbroker/farcaster-messages-types.js'; import type { MessageReceiveConfirmation } from '../types/tunnelbroker/message-receive-confirmation-types.js'; import type { MessageToDeviceRequest } from '../types/tunnelbroker/message-to-device-request-types.js'; @@ -447,7 +448,7 @@ const resultPromise: Promise = new Promise((resolve, reject) => { const socketActive = socketStateRef.current.connected && socket.current; if (!shouldBeClosed && !socketActive) { - throw new Error('Tunnelbroker not connected'); + throw new Error(tunnelbrokerNotConnected); } promises.current[identifier] = { resolve, @@ -517,7 +518,7 @@ const resultPromise: Promise = new Promise((resolve, reject) => { const socketActive = socketStateRef.current.connected && socket.current; if (!shouldBeClosed && !socketActive) { - throw new Error('Tunnelbroker not connected'); + throw new Error(tunnelbrokerNotConnected); } invariant(userID, 'userID should be defined'); diff --git a/lib/types/tunnelbroker/errors.js b/lib/types/tunnelbroker/errors.js new file mode 100644 --- /dev/null +++ b/lib/types/tunnelbroker/errors.js @@ -0,0 +1,3 @@ +// @flow + +export const tunnelbrokerNotConnected = 'Tunnelbroker not connected';