FRE-600: Fix code review blockers

- 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>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

117
node_modules/viem/utils/rpc/compat.ts generated vendored Normal file
View File

@@ -0,0 +1,117 @@
// TODO(v3): This file is here for backwards compatibility, and to prevent breaking changes.
// These APIs will be removed in v3.
/* c8 ignore start */
import type {
TimeoutErrorType,
WebSocketRequestError,
} from '../../errors/request.js'
import type { ErrorType } from '../../errors/utils.js'
import type { RpcResponse } from '../../types/rpc.js'
import type { WithTimeoutErrorType } from '../promise/withTimeout.js'
import { getHttpRpcClient, type HttpRequestParameters } from './http.js'
import type { SocketRpcClient } from './socket.js'
import { getWebSocketRpcClient } from './webSocket.js'
export type WebSocketOptions = Parameters<
SocketRpcClient<WebSocket>['request']
>[0]
export type WebSocketReturnType = SocketRpcClient<WebSocket>
export type WebSocketErrorType = WebSocketRequestError | ErrorType
function webSocket(
socketClient: SocketRpcClient<WebSocket>,
{ body, onError, onResponse }: WebSocketOptions,
): WebSocketReturnType {
socketClient.request({
body,
onError,
onResponse,
})
return socketClient
}
export type WebSocketAsyncOptions = Parameters<
SocketRpcClient<WebSocket>['requestAsync']
>[0]
export type WebSocketAsyncReturnType = RpcResponse
export type WebSocketAsyncErrorType =
| WebSocketErrorType
| TimeoutErrorType
| WithTimeoutErrorType
| ErrorType
async function webSocketAsync(
socketClient: SocketRpcClient<WebSocket>,
{ body, timeout = 10_000 }: WebSocketAsyncOptions,
): Promise<WebSocketAsyncReturnType> {
return socketClient.requestAsync({
body,
timeout,
})
}
/**
* @deprecated use `getSocketClient` instead.
*
* ```diff
* -import { getSocket } from 'viem/utils'
* +import { getSocketClient } from 'viem/utils'
*
* -const socket = await getSocket(url)
* +const socketClient = await getSocketClient(url)
* +const socket = socketClient.socket
* ```
*/
export async function getSocket(url: string) {
const client = await getWebSocketRpcClient(url)
return Object.assign(client.socket, {
requests: client.requests,
subscriptions: client.subscriptions,
})
}
export const rpc = {
/**
* @deprecated use `getHttpRpcClient` instead.
*
* ```diff
* -import { rpc } from 'viem/utils'
* +import { getHttpRpcClient } from 'viem/utils'
*
* -rpc.http(url, params)
* +const httpClient = getHttpRpcClient(url)
* +httpClient.request(params)
* ```
*/
http(url: string, params: HttpRequestParameters) {
return getHttpRpcClient(url).request(params)
},
/**
* @deprecated use `getWebSocketRpcClient` instead.
*
* ```diff
* -import { rpc } from 'viem/utils'
* +import { getWebSocketRpcClient } from 'viem/utils'
*
* -rpc.webSocket(url, params)
* +const webSocketClient = getWebSocketRpcClient(url)
* +webSocketClient.request(params)
* ```
*/
webSocket,
/**
* @deprecated use `getWebSocketRpcClient` instead.
*
* ```diff
* -import { rpc } from 'viem/utils'
* +import { getWebSocketRpcClient } from 'viem/utils'
*
* -const response = await rpc.webSocketAsync(url, params)
* +const webSocketClient = getWebSocketRpcClient(url)
* +const response = await webSocketClient.requestAsync(params)
* ```
*/
webSocketAsync,
}
/* c8 ignore end */

214
node_modules/viem/utils/rpc/http.ts generated vendored Normal file
View File

@@ -0,0 +1,214 @@
import {
HttpRequestError,
type HttpRequestErrorType as HttpRequestErrorType_,
TimeoutError,
type TimeoutErrorType,
} from '../../errors/request.js'
import type { ErrorType } from '../../errors/utils.js'
import type { RpcRequest, RpcResponse } from '../../types/rpc.js'
import type { MaybePromise } from '../../types/utils.js'
import {
type WithTimeoutErrorType,
withTimeout,
} from '../promise/withTimeout.js'
import { stringify } from '../stringify.js'
import { idCache } from './id.js'
export type HttpRpcClientOptions = {
/** Override for the fetch function used to make requests. */
fetchFn?:
| ((input: string | URL | Request, init?: RequestInit) => Promise<Response>)
| undefined
/** Request configuration to pass to `fetch`. */
fetchOptions?: Omit<RequestInit, 'body'> | undefined
/** A callback to handle the request. */
onRequest?:
| ((
request: Request,
init: RequestInit,
) => MaybePromise<
void | undefined | (RequestInit & { url?: string | undefined })
>)
| undefined
/** A callback to handle the response. */
onResponse?: ((response: Response) => Promise<void> | void) | undefined
/** The timeout (in ms) for the request. */
timeout?: number | undefined
}
export type HttpRequestParameters<
body extends RpcRequest | RpcRequest[] = RpcRequest,
> = {
/** The RPC request body. */
body: body
/** Override for the fetch function used to make requests. */
fetchFn?: HttpRpcClientOptions['fetchFn'] | undefined
/** Request configuration to pass to `fetch`. */
fetchOptions?: HttpRpcClientOptions['fetchOptions'] | undefined
/** A callback to handle the response. */
onRequest?:
| ((
request: Request,
init: RequestInit,
) => MaybePromise<
void | undefined | (RequestInit & { url?: string | undefined })
>)
| undefined
/** A callback to handle the response. */
onResponse?: ((response: Response) => Promise<void> | void) | undefined
/** The timeout (in ms) for the request. */
timeout?: HttpRpcClientOptions['timeout'] | undefined
}
export type HttpRequestReturnType<
body extends RpcRequest | RpcRequest[] = RpcRequest,
> = body extends RpcRequest[] ? RpcResponse[] : RpcResponse
export type HttpRequestErrorType =
| HttpRequestErrorType_
| TimeoutErrorType
| WithTimeoutErrorType
| ErrorType
export type HttpRpcClient = {
request<body extends RpcRequest | RpcRequest[]>(
params: HttpRequestParameters<body>,
): Promise<HttpRequestReturnType<body>>
}
export function getHttpRpcClient(
url_: string,
options: HttpRpcClientOptions = {},
): HttpRpcClient {
const { url, headers: headers_url } = parseUrl(url_)
return {
async request(params) {
const {
body,
fetchFn = options.fetchFn ?? fetch,
onRequest = options.onRequest,
onResponse = options.onResponse,
timeout = options.timeout ?? 10_000,
} = params
const fetchOptions = {
...(options.fetchOptions ?? {}),
...(params.fetchOptions ?? {}),
}
const { headers, method, signal: signal_ } = fetchOptions
try {
const response = await withTimeout(
async ({ signal }) => {
const init: RequestInit = {
...fetchOptions,
body: Array.isArray(body)
? stringify(
body.map((body) => ({
jsonrpc: '2.0',
id: body.id ?? idCache.take(),
...body,
})),
)
: stringify({
jsonrpc: '2.0',
id: body.id ?? idCache.take(),
...body,
}),
headers: {
...headers_url,
'Content-Type': 'application/json',
...headers,
},
method: method || 'POST',
signal: signal_ || (timeout > 0 ? signal : null),
}
const request = new Request(url, init)
const args = (await onRequest?.(request, init)) ?? { ...init, url }
const response = await fetchFn(args.url ?? url, args)
return response
},
{
errorInstance: new TimeoutError({ body, url }),
timeout,
signal: true,
},
)
if (onResponse) await onResponse(response)
let data: any
if (
response.headers.get('Content-Type')?.startsWith('application/json')
)
data = await response.json()
else {
data = await response.text()
try {
data = JSON.parse(data || '{}')
} catch (err) {
if (response.ok) throw err
data = { error: data }
}
}
if (!response.ok) {
// If the response body contains a valid JSON-RPC error, return it
// so it flows through the normal RPC error handling pipeline.
if (
typeof data.error?.code === 'number' &&
typeof data.error?.message === 'string'
)
return data
throw new HttpRequestError({
body,
details: stringify(data.error) || response.statusText,
headers: response.headers,
status: response.status,
url,
})
}
return data
} catch (err) {
if (err instanceof HttpRequestError) throw err
if (err instanceof TimeoutError) throw err
throw new HttpRequestError({
body,
cause: err as Error,
url,
})
}
},
}
}
/** @internal */
export function parseUrl(url_: string) {
try {
const url = new URL(url_)
const result = (() => {
// Handle Basic authentication credentials
if (url.username) {
const credentials = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`
url.username = ''
url.password = ''
return {
url: url.toString(),
headers: { Authorization: `Basic ${btoa(credentials)}` },
}
}
return
})()
return { url: url.toString(), ...result }
} catch {
return { url: url_ }
}
}

13
node_modules/viem/utils/rpc/id.ts generated vendored Normal file
View File

@@ -0,0 +1,13 @@
function createIdStore() {
return {
current: 0,
take() {
return this.current++
},
reset() {
this.current = 0
},
}
}
export const idCache = /*#__PURE__*/ createIdStore()

109
node_modules/viem/utils/rpc/ipc.ts generated vendored Normal file
View File

@@ -0,0 +1,109 @@
import { connect, type Socket as NetSocket } from 'node:net'
import { WebSocketRequestError } from '../../index.js'
import {
type GetSocketRpcClientParameters,
getSocketRpcClient,
type Socket,
type SocketRpcClient,
} from './socket.js'
export type GetIpcRpcClientOptions = Pick<
GetSocketRpcClientParameters,
'reconnect'
>
const openingBrace = '{'.charCodeAt(0)
const closingBrace = '}'.charCodeAt(0)
/** @internal */
export function extractMessages(buffer: Buffer): [Buffer[], Buffer] {
const messages: Buffer[] = []
let cursor = 0
let level = 0
for (let i = 0; i < buffer.length; i++) {
if (buffer[i] === openingBrace) level++
if (buffer[i] === closingBrace) level--
if (level === 0) {
const message = buffer.subarray(cursor, i + 1)
if (
message[0] === openingBrace &&
message[message.length - 1] === closingBrace
)
messages.push(message)
cursor = i + 1
}
}
return [messages, buffer.subarray(cursor)]
}
export type IpcRpcClient = SocketRpcClient<NetSocket>
export async function getIpcRpcClient(
path: string,
options: GetIpcRpcClientOptions = {},
): Promise<IpcRpcClient> {
const { reconnect } = options
return getSocketRpcClient({
async getSocket({ onError, onOpen, onResponse }) {
const socket = connect(path)
function onClose() {
socket.off('close', onClose)
socket.off('message', onData)
socket.off('error', onError)
socket.off('connect', onOpen)
}
let lastRemaining = Buffer.alloc(0) as Buffer
function onData(buffer: Buffer) {
const [messages, remaining] = extractMessages(
Buffer.concat([
Uint8Array.from(lastRemaining),
Uint8Array.from(buffer),
]),
)
for (const message of messages) {
const response = JSON.parse(Buffer.from(message).toString())
onResponse(response)
}
lastRemaining = remaining
}
socket.on('close', onClose)
socket.on('data', onData)
socket.on('error', onError)
socket.on('connect', onOpen)
// Wait for the socket to open.
await new Promise<void>((resolve, reject) => {
socket.on('ready', () => {
resolve()
socket.off('error', reject)
})
socket.on('error', reject)
})
return Object.assign(socket, {
close() {
socket.destroy()
socket.end()
},
request({ body }) {
if (socket.readyState !== 'open')
throw new WebSocketRequestError({
body,
url: path,
details: 'Socket is closed.',
})
return socket.write(JSON.stringify(body))
},
} as Socket<{}>)
},
reconnect,
url: path,
})
}

299
node_modules/viem/utils/rpc/socket.ts generated vendored Normal file
View File

@@ -0,0 +1,299 @@
import { SocketClosedError, TimeoutError } from '../../errors/request.js'
import type { ErrorType } from '../../errors/utils.js'
import type { RpcRequest, RpcResponse } from '../../types/rpc.js'
import {
type CreateBatchSchedulerErrorType,
createBatchScheduler,
} from '../promise/createBatchScheduler.js'
import { withTimeout } from '../promise/withTimeout.js'
import { idCache } from './id.js'
type Id = string | number
type CallbackFn = {
onResponse: (message: any) => void
onError?: ((error?: Error | Event | undefined) => void) | undefined
body?: RpcRequest
}
type CallbackMap = Map<Id, CallbackFn>
export type GetSocketParameters = {
onClose: () => void
onError: (error?: Error | Event | undefined) => void
onOpen: () => void
onResponse: (data: RpcResponse) => void
}
export type Socket<socket extends {}> = socket & {
close(): void
ping?: (() => void) | undefined
request(params: { body: RpcRequest }): void
}
export type SocketRpcClient<socket extends {}> = {
close(): void
socket: Socket<socket>
request(params: {
body: RpcRequest
onError?: ((error?: Error | Event | undefined) => void) | undefined
onResponse: (message: RpcResponse) => void
}): void
requestAsync(params: {
body: RpcRequest
timeout?: number | undefined
}): Promise<RpcResponse>
requests: CallbackMap
subscriptions: CallbackMap
url: string
}
export type GetSocketRpcClientParameters<socket extends {} = {}> = {
getSocket(params: GetSocketParameters): Promise<Socket<socket>>
/**
* Whether or not to send keep-alive messages.
* @default true
*/
keepAlive?:
| boolean
| {
/**
* The interval (in ms) to send keep-alive messages.
* @default 30_000
*/
interval?: number | undefined
}
| undefined
key?: string
/**
* Whether or not to attempt to reconnect on socket failure or closure.
* @default true
*/
reconnect?:
| boolean
| {
/**
* The maximum number of reconnection attempts.
* @default 5
*/
attempts?: number | undefined
/**
* The delay (in ms) between reconnection attempts.
* @default 2_000
*/
delay?: number | undefined
}
| undefined
url: string
}
export type GetSocketRpcClientErrorType =
| CreateBatchSchedulerErrorType
| ErrorType
export const socketClientCache = /*#__PURE__*/ new Map<
string,
SocketRpcClient<Socket<{}>>
>()
export async function getSocketRpcClient<socket extends {}>(
parameters: GetSocketRpcClientParameters<socket>,
): Promise<SocketRpcClient<socket>> {
const {
getSocket,
keepAlive = true,
key = 'socket',
reconnect = true,
url,
} = parameters
const { interval: keepAliveInterval = 30_000 } =
typeof keepAlive === 'object' ? keepAlive : {}
const { attempts = 5, delay = 2_000 } =
typeof reconnect === 'object' ? reconnect : {}
const id = JSON.stringify({ keepAlive, key, url, reconnect })
let socketClient = socketClientCache.get(id)
// If the socket already exists, return it.
if (socketClient) return socketClient as {} as SocketRpcClient<socket>
let reconnectCount = 0
const { schedule } = createBatchScheduler<
undefined,
[SocketRpcClient<socket>]
>({
id,
fn: async () => {
// Set up a cache for incoming "synchronous" requests.
const requests = new Map<Id, CallbackFn>()
// Set up a cache for subscriptions (eth_subscribe).
const subscriptions = new Map<Id, CallbackFn>()
let error: Error | Event | undefined
let socket: Socket<{}>
let keepAliveTimer: ReturnType<typeof setInterval> | undefined
let reconnectInProgress = false
function attemptReconnect() {
// Attempt to reconnect.
if (reconnect && reconnectCount < attempts) {
if (reconnectInProgress) return
reconnectInProgress = true
reconnectCount++
// Make sure the previous socket is definitely closed.
socket?.close()
setTimeout(async () => {
// biome-ignore lint/suspicious/noConsole: _
await setup().catch(console.error)
reconnectInProgress = false
}, delay)
}
// Otherwise, clear all requests and subscriptions.
else {
requests.clear()
subscriptions.clear()
}
}
// Set up socket implementation.
async function setup() {
const result = await getSocket({
onClose() {
// Notify all requests and subscriptions of the closure error.
for (const request of requests.values())
request.onError?.(new SocketClosedError({ url }))
for (const subscription of subscriptions.values())
subscription.onError?.(new SocketClosedError({ url }))
attemptReconnect()
},
onError(error_) {
error = error_
// Notify all requests and subscriptions of the error.
for (const request of requests.values()) request.onError?.(error)
for (const subscription of subscriptions.values())
subscription.onError?.(error)
attemptReconnect()
},
onOpen() {
error = undefined
reconnectCount = 0
},
onResponse(data) {
const isSubscription = data.method === 'eth_subscription'
const id = isSubscription ? data.params.subscription : data.id
const cache = isSubscription ? subscriptions : requests
const callback = cache.get(id)
if (callback) callback.onResponse(data)
if (!isSubscription) cache.delete(id)
},
})
socket = result
if (keepAlive) {
if (keepAliveTimer) clearInterval(keepAliveTimer)
keepAliveTimer = setInterval(() => socket.ping?.(), keepAliveInterval)
}
if (reconnect && subscriptions.size > 0) {
const subscriptionEntries = subscriptions.entries()
for (const [
key,
{ onResponse, body, onError },
] of subscriptionEntries) {
if (!body) continue
subscriptions.delete(key)
socketClient?.request({ body, onResponse, onError })
}
}
return result
}
await setup()
error = undefined
// Create a new socket instance.
socketClient = {
close() {
keepAliveTimer && clearInterval(keepAliveTimer)
socket.close()
socketClientCache.delete(id)
},
get socket() {
return socket
},
request({ body, onError, onResponse }) {
if (error && onError) onError(error)
const id = body.id ?? idCache.take()
const callback = (response: RpcResponse) => {
if (typeof response.id === 'number' && id !== response.id) return
// If we are subscribing to a topic, we want to set up a listener for incoming
// messages.
if (
body.method === 'eth_subscribe' &&
typeof response.result === 'string'
)
subscriptions.set(response.result, {
onResponse: callback,
onError,
body,
})
onResponse(response)
}
// If we are unsubscribing from a topic, remove the listener immediately
// to prevent it from being re-subscribed on reconnect.
if (body.method === 'eth_unsubscribe')
subscriptions.delete(body.params?.[0])
requests.set(id, { onResponse: callback, onError })
try {
socket.request({
body: {
jsonrpc: '2.0',
id,
...body,
},
})
} catch (error) {
onError?.(error as Error)
}
},
requestAsync({ body, timeout = 10_000 }) {
return withTimeout(
() =>
new Promise<RpcResponse>((onResponse, onError) =>
this.request({
body,
onError,
onResponse,
}),
),
{
errorInstance: new TimeoutError({ body, url }),
timeout,
},
)
},
requests,
subscriptions,
url,
}
socketClientCache.set(id, socketClient)
return [socketClient as {} as SocketRpcClient<socket>]
},
})
const [_, [socketClient_]] = await schedule()
return socketClient_
}

113
node_modules/viem/utils/rpc/webSocket.ts generated vendored Normal file
View File

@@ -0,0 +1,113 @@
import type { MessageEvent } from 'isows'
import {
SocketClosedError,
WebSocketRequestError,
} from '../../errors/request.js'
import type { RpcRequest } from '../../types/rpc.js'
import {
type GetSocketRpcClientParameters,
getSocketRpcClient,
type Socket,
type SocketRpcClient,
} from './socket.js'
export type GetWebSocketRpcClientOptions = Pick<
GetSocketRpcClientParameters,
'keepAlive' | 'reconnect'
>
export async function getWebSocketRpcClient(
url: string,
options: GetWebSocketRpcClientOptions | undefined = {},
): Promise<SocketRpcClient<WebSocket>> {
const { keepAlive, reconnect } = options
return getSocketRpcClient({
async getSocket({ onClose, onError, onOpen, onResponse }) {
const WebSocket = await import('isows').then((module) => module.WebSocket)
const socket = new WebSocket(url)
function onClose_() {
socket.removeEventListener('close', onClose_)
socket.removeEventListener('message', onMessage)
socket.removeEventListener('error', onError)
socket.removeEventListener('open', onOpen)
onClose()
}
function onMessage({ data }: MessageEvent) {
// ignore empty messages
if (typeof data === 'string' && data.trim().length === 0) return
try {
const _data = JSON.parse(data)
onResponse(_data)
} catch (error) {
onError(error as Error)
}
}
// Setup event listeners for RPC & subscription responses.
socket.addEventListener('close', onClose_)
socket.addEventListener('message', onMessage)
socket.addEventListener('error', onError)
socket.addEventListener('open', onOpen)
// Wait for the socket to open.
if (socket.readyState === WebSocket.CONNECTING) {
await new Promise((resolve, reject) => {
if (!socket) return
socket.onopen = resolve
socket.onerror = reject
})
}
const { close: close_ } = socket
return Object.assign(socket, {
close() {
close_.bind(socket)()
onClose_()
},
ping() {
try {
if (
socket.readyState === socket.CLOSED ||
socket.readyState === socket.CLOSING
)
throw new WebSocketRequestError({
url: socket.url,
cause: new SocketClosedError({ url: socket.url }),
})
const body: RpcRequest = {
jsonrpc: '2.0',
id: null,
method: 'net_version',
params: [],
}
socket.send(JSON.stringify(body))
} catch (error) {
onError(error as Error)
}
},
request({ body }) {
if (
socket.readyState === socket.CLOSED ||
socket.readyState === socket.CLOSING
)
throw new WebSocketRequestError({
body,
url: socket.url,
cause: new SocketClosedError({ url: socket.url }),
})
return socket.send(JSON.stringify(body))
},
} as Socket<WebSocket>)
},
keepAlive,
reconnect,
url,
})
}