- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
324 lines
11 KiB
TypeScript
324 lines
11 KiB
TypeScript
import { BaseError } from '../errors/base.js'
|
||
import {
|
||
HttpRequestError,
|
||
type HttpRequestErrorType,
|
||
type RpcRequestErrorType,
|
||
type TimeoutErrorType,
|
||
type WebSocketRequestErrorType,
|
||
} from '../errors/request.js'
|
||
import {
|
||
AtomicityNotSupportedError,
|
||
type AtomicityNotSupportedErrorType,
|
||
AtomicReadyWalletRejectedUpgradeError,
|
||
type AtomicReadyWalletRejectedUpgradeErrorType,
|
||
BundleTooLargeError,
|
||
type BundleTooLargeErrorType,
|
||
ChainDisconnectedError,
|
||
type ChainDisconnectedErrorType,
|
||
DuplicateIdError,
|
||
type DuplicateIdErrorType,
|
||
InternalRpcError,
|
||
type InternalRpcErrorType,
|
||
InvalidInputRpcError,
|
||
type InvalidInputRpcErrorType,
|
||
InvalidParamsRpcError,
|
||
type InvalidParamsRpcErrorType,
|
||
InvalidRequestRpcError,
|
||
type InvalidRequestRpcErrorType,
|
||
JsonRpcVersionUnsupportedError,
|
||
type JsonRpcVersionUnsupportedErrorType,
|
||
LimitExceededRpcError,
|
||
type LimitExceededRpcErrorType,
|
||
MethodNotFoundRpcError,
|
||
type MethodNotFoundRpcErrorType,
|
||
MethodNotSupportedRpcError,
|
||
type MethodNotSupportedRpcErrorType,
|
||
ParseRpcError,
|
||
type ParseRpcErrorType,
|
||
ProviderDisconnectedError,
|
||
type ProviderDisconnectedErrorType,
|
||
type ProviderRpcErrorCode,
|
||
ResourceNotFoundRpcError,
|
||
type ResourceNotFoundRpcErrorType,
|
||
ResourceUnavailableRpcError,
|
||
type ResourceUnavailableRpcErrorType,
|
||
type RpcError,
|
||
type RpcErrorCode,
|
||
type RpcErrorType,
|
||
SwitchChainError,
|
||
type SwitchChainErrorType,
|
||
TransactionRejectedRpcError,
|
||
type TransactionRejectedRpcErrorType,
|
||
UnauthorizedProviderError,
|
||
type UnauthorizedProviderErrorType,
|
||
UnknownBundleIdError,
|
||
type UnknownBundleIdErrorType,
|
||
UnknownRpcError,
|
||
type UnknownRpcErrorType,
|
||
UnsupportedChainIdError,
|
||
type UnsupportedChainIdErrorType,
|
||
UnsupportedNonOptionalCapabilityError,
|
||
type UnsupportedNonOptionalCapabilityErrorType,
|
||
UnsupportedProviderMethodError,
|
||
type UnsupportedProviderMethodErrorType,
|
||
UserRejectedRequestError,
|
||
type UserRejectedRequestErrorType,
|
||
WalletConnectSessionSettlementError,
|
||
type WalletConnectSessionSettlementErrorType,
|
||
} from '../errors/rpc.js'
|
||
import type { ErrorType } from '../errors/utils.js'
|
||
import type {
|
||
EIP1193RequestFn,
|
||
EIP1193RequestOptions,
|
||
} from '../types/eip1193.js'
|
||
import type { CreateBatchSchedulerErrorType } from './promise/createBatchScheduler.js'
|
||
import { withDedupe } from './promise/withDedupe.js'
|
||
import { type WithRetryErrorType, withRetry } from './promise/withRetry.js'
|
||
import type { GetSocketRpcClientErrorType } from './rpc/socket.js'
|
||
import { stringify } from './stringify.js'
|
||
|
||
export type RequestErrorType =
|
||
| AtomicityNotSupportedErrorType
|
||
| AtomicReadyWalletRejectedUpgradeErrorType
|
||
| BundleTooLargeErrorType
|
||
| ChainDisconnectedErrorType
|
||
| CreateBatchSchedulerErrorType
|
||
| DuplicateIdErrorType
|
||
| HttpRequestErrorType
|
||
| InternalRpcErrorType
|
||
| InvalidInputRpcErrorType
|
||
| InvalidParamsRpcErrorType
|
||
| InvalidRequestRpcErrorType
|
||
| GetSocketRpcClientErrorType
|
||
| JsonRpcVersionUnsupportedErrorType
|
||
| LimitExceededRpcErrorType
|
||
| MethodNotFoundRpcErrorType
|
||
| MethodNotSupportedRpcErrorType
|
||
| ParseRpcErrorType
|
||
| ProviderDisconnectedErrorType
|
||
| ResourceNotFoundRpcErrorType
|
||
| ResourceUnavailableRpcErrorType
|
||
| RpcErrorType
|
||
| RpcRequestErrorType
|
||
| SwitchChainErrorType
|
||
| TimeoutErrorType
|
||
| TransactionRejectedRpcErrorType
|
||
| UnauthorizedProviderErrorType
|
||
| UnknownBundleIdErrorType
|
||
| UnknownRpcErrorType
|
||
| UnsupportedChainIdErrorType
|
||
| UnsupportedNonOptionalCapabilityErrorType
|
||
| UnsupportedProviderMethodErrorType
|
||
| UserRejectedRequestErrorType
|
||
| WalletConnectSessionSettlementErrorType
|
||
| WebSocketRequestErrorType
|
||
| WithRetryErrorType
|
||
| ErrorType
|
||
|
||
export function buildRequest<request extends (args: any) => Promise<any>>(
|
||
request: request,
|
||
options: EIP1193RequestOptions = {},
|
||
): EIP1193RequestFn {
|
||
return async (args, overrideOptions = {}) => {
|
||
const {
|
||
dedupe = false,
|
||
methods,
|
||
retryDelay = 150,
|
||
retryCount = 3,
|
||
uid,
|
||
} = {
|
||
...options,
|
||
...overrideOptions,
|
||
}
|
||
|
||
const { method } = args
|
||
if (methods?.exclude?.includes(method))
|
||
throw new MethodNotSupportedRpcError(new Error('method not supported'), {
|
||
method,
|
||
})
|
||
if (methods?.include && !methods.include.includes(method))
|
||
throw new MethodNotSupportedRpcError(new Error('method not supported'), {
|
||
method,
|
||
})
|
||
|
||
const requestId = dedupe
|
||
? hashString(`${uid}.${stringify(args)}`)
|
||
: undefined
|
||
return withDedupe(
|
||
() =>
|
||
withRetry(
|
||
async () => {
|
||
try {
|
||
return await request(args)
|
||
} catch (err_) {
|
||
const err = err_ as unknown as RpcError<
|
||
RpcErrorCode | ProviderRpcErrorCode
|
||
>
|
||
switch (err.code) {
|
||
// -32700
|
||
case ParseRpcError.code:
|
||
throw new ParseRpcError(err)
|
||
// -32600
|
||
case InvalidRequestRpcError.code:
|
||
throw new InvalidRequestRpcError(err)
|
||
// -32601
|
||
case MethodNotFoundRpcError.code:
|
||
throw new MethodNotFoundRpcError(err, { method: args.method })
|
||
// -32602
|
||
case InvalidParamsRpcError.code:
|
||
throw new InvalidParamsRpcError(err)
|
||
// -32603
|
||
case InternalRpcError.code:
|
||
throw new InternalRpcError(err)
|
||
// -32000
|
||
case InvalidInputRpcError.code:
|
||
throw new InvalidInputRpcError(err)
|
||
// -32001
|
||
case ResourceNotFoundRpcError.code:
|
||
throw new ResourceNotFoundRpcError(err)
|
||
// -32002
|
||
case ResourceUnavailableRpcError.code:
|
||
throw new ResourceUnavailableRpcError(err)
|
||
// -32003
|
||
case TransactionRejectedRpcError.code:
|
||
throw new TransactionRejectedRpcError(err)
|
||
// -32004
|
||
case MethodNotSupportedRpcError.code:
|
||
throw new MethodNotSupportedRpcError(err, {
|
||
method: args.method,
|
||
})
|
||
// -32005
|
||
case LimitExceededRpcError.code:
|
||
throw new LimitExceededRpcError(err)
|
||
// -32006
|
||
case JsonRpcVersionUnsupportedError.code:
|
||
throw new JsonRpcVersionUnsupportedError(err)
|
||
|
||
// 4001
|
||
case UserRejectedRequestError.code:
|
||
throw new UserRejectedRequestError(err)
|
||
// 4100
|
||
case UnauthorizedProviderError.code:
|
||
throw new UnauthorizedProviderError(err)
|
||
// 4200
|
||
case UnsupportedProviderMethodError.code:
|
||
throw new UnsupportedProviderMethodError(err)
|
||
// 4900
|
||
case ProviderDisconnectedError.code:
|
||
throw new ProviderDisconnectedError(err)
|
||
// 4901
|
||
case ChainDisconnectedError.code:
|
||
throw new ChainDisconnectedError(err)
|
||
// 4902
|
||
case SwitchChainError.code:
|
||
throw new SwitchChainError(err)
|
||
|
||
// 5700
|
||
case UnsupportedNonOptionalCapabilityError.code:
|
||
throw new UnsupportedNonOptionalCapabilityError(err)
|
||
// 5710
|
||
case UnsupportedChainIdError.code:
|
||
throw new UnsupportedChainIdError(err)
|
||
// 5720
|
||
case DuplicateIdError.code:
|
||
throw new DuplicateIdError(err)
|
||
// 5730
|
||
case UnknownBundleIdError.code:
|
||
throw new UnknownBundleIdError(err)
|
||
// 5740
|
||
case BundleTooLargeError.code:
|
||
throw new BundleTooLargeError(err)
|
||
// 5750
|
||
case AtomicReadyWalletRejectedUpgradeError.code:
|
||
throw new AtomicReadyWalletRejectedUpgradeError(err)
|
||
// 5760
|
||
case AtomicityNotSupportedError.code:
|
||
throw new AtomicityNotSupportedError(err)
|
||
|
||
// CAIP-25: User Rejected Error
|
||
// https://docs.walletconnect.com/2.0/specs/clients/sign/error-codes#rejected-caip-25
|
||
case 5000:
|
||
throw new UserRejectedRequestError(err)
|
||
|
||
// WalletConnect: Session Settlement Failed
|
||
// https://docs.walletconnect.com/2.0/specs/clients/sign/error-codes
|
||
case WalletConnectSessionSettlementError.code:
|
||
throw new WalletConnectSessionSettlementError(err)
|
||
|
||
default:
|
||
if (err_ instanceof BaseError) throw err_
|
||
throw new UnknownRpcError(err as Error)
|
||
}
|
||
}
|
||
},
|
||
{
|
||
delay: ({ count, error }) => {
|
||
// If we find a Retry-After header, let's retry after the given time.
|
||
if (error && error instanceof HttpRequestError) {
|
||
const retryAfter = error?.headers?.get('Retry-After')
|
||
if (retryAfter?.match(/\d/))
|
||
return Number.parseInt(retryAfter, 10) * 1000
|
||
}
|
||
|
||
// Otherwise, let's retry with an exponential backoff.
|
||
return ~~(1 << count) * retryDelay
|
||
},
|
||
retryCount,
|
||
shouldRetry: ({ error }) => shouldRetry(error),
|
||
},
|
||
),
|
||
{ enabled: dedupe, id: requestId },
|
||
)
|
||
}
|
||
}
|
||
|
||
/** @internal */
|
||
export function shouldRetry(error: Error) {
|
||
if ('code' in error && typeof error.code === 'number') {
|
||
if (error.code === -1) return true // Unknown error
|
||
if (error.code === LimitExceededRpcError.code) return true
|
||
if (error.code === InternalRpcError.code) return true
|
||
// Too Many Requests — some providers (e.g. Alchemy in batch mode) return
|
||
// HTTP 200 with a JSON-RPC body of `{ code: 429 }` instead of an HTTP 429,
|
||
// so we need to handle this code in addition to the HTTP status check below.
|
||
if (error.code === 429) return true
|
||
return false
|
||
}
|
||
if (error instanceof HttpRequestError && error.status) {
|
||
// Forbidden
|
||
if (error.status === 403) return true
|
||
// Request Timeout
|
||
if (error.status === 408) return true
|
||
// Request Entity Too Large
|
||
if (error.status === 413) return true
|
||
// Too Many Requests
|
||
if (error.status === 429) return true
|
||
// Internal Server Error
|
||
if (error.status === 500) return true
|
||
// Bad Gateway
|
||
if (error.status === 502) return true
|
||
// Service Unavailable
|
||
if (error.status === 503) return true
|
||
// Gateway Timeout
|
||
if (error.status === 504) return true
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
/** @internal cyrb53 – fast, non-cryptographic 53-bit string hash */
|
||
function hashString(str: string, seed = 0): string {
|
||
let h1 = 0xdeadbeef ^ seed
|
||
let h2 = 0x41c6ce57 ^ seed
|
||
for (let i = 0; i < str.length; i++) {
|
||
const ch = str.charCodeAt(i)
|
||
h1 = Math.imul(h1 ^ ch, 2654435761)
|
||
h2 = Math.imul(h2 ^ ch, 1597334677)
|
||
}
|
||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)
|
||
h1 ^= Math.imul(h2 ^ (h2 >>> 16), 3266489909)
|
||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)
|
||
h2 ^= Math.imul(h1 ^ (h1 >>> 16), 3266489909)
|
||
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36)
|
||
}
|