| 95 | >() |
| 96 | |
| 97 | export async function getSocketRpcClient<socket extends {}>( |
| 98 | parameters: GetSocketRpcClientParameters<socket>, |
| 99 | ): Promise<SocketRpcClient<socket>> { |
| 100 | const { |
| 101 | getSocket, |
| 102 | keepAlive = true, |
| 103 | key = 'socket', |
| 104 | reconnect = true, |
| 105 | url, |
| 106 | } = parameters |
| 107 | const { interval: keepAliveInterval = 30_000 } = |
| 108 | typeof keepAlive === 'object' ? keepAlive : {} |
| 109 | const { attempts = 5, delay = 2_000 } = |
| 110 | typeof reconnect === 'object' ? reconnect : {} |
| 111 | |
| 112 | const id = JSON.stringify({ keepAlive, key, url, reconnect }) |
| 113 | let socketClient = socketClientCache.get(id) |
| 114 | |
| 115 | // If the socket already exists, return it. |
| 116 | if (socketClient) return socketClient as {} as SocketRpcClient<socket> |
| 117 | |
| 118 | let reconnectCount = 0 |
| 119 | const { schedule } = createBatchScheduler< |
| 120 | undefined, |
| 121 | [SocketRpcClient<socket>] |
| 122 | >({ |
| 123 | id, |
| 124 | fn: async () => { |
| 125 | // Set up a cache for incoming "synchronous" requests. |
| 126 | const requests = new Map<Id, CallbackFn>() |
| 127 | |
| 128 | // Set up a cache for subscriptions (eth_subscribe). |
| 129 | const subscriptions = new Map<Id, CallbackFn>() |
| 130 | |
| 131 | let error: Error | Event | undefined |
| 132 | let socket: Socket<{}> |
| 133 | let keepAliveTimer: ReturnType<typeof setInterval> | undefined |
| 134 | let reconnectTimer: ReturnType<typeof setTimeout> | undefined |
| 135 | |
| 136 | let reconnectInProgress = false |
| 137 | let intentionallyClosed = false |
| 138 | function attemptReconnect() { |
| 139 | // Attempt to reconnect. |
| 140 | if (reconnect && !intentionallyClosed && reconnectCount < attempts) { |
| 141 | if (reconnectInProgress) return |
| 142 | reconnectInProgress = true |
| 143 | reconnectCount++ |
| 144 | |
| 145 | // Make sure the previous socket is definitely closed. |
| 146 | socket?.close() |
| 147 | |
| 148 | reconnectTimer = setTimeout(async () => { |
| 149 | reconnectTimer = undefined |
| 150 | // Bail if the client was intentionally closed during the delay. |
| 151 | if (intentionallyClosed) { |
| 152 | reconnectInProgress = false |
| 153 | return |
| 154 | } |