UNPKG

@nori-zk/mina-token-bridge

Version:

Nori ethereum state settelment and nETH token bridge zkApp

121 lines 4.91 kB
import { filter, interval, map } from 'rxjs'; import { webSocket } from 'rxjs/webSocket'; import { reconnectingWebSocket } from './reconnectingSocket.js'; // Pong response. const pongReplyStr = '{"data":"pong"}'; // Subscription requests. const subscribeStateEth = { method: 'subscribe', topic: 'state.eth', }; const subscribeStateBridge = { method: 'subscribe', topic: 'state.bridge', }; const subscribeTimingsTransition = { method: 'subscribe', topic: 'timings.notices.transition', }; /** * Creates a basic `WebSocketSubject` for receiving bridge-related messages. * * This socket: * - Subscribes to `state.eth`, `state.bridge`, and `timings.notices.transition` on open. * - Sends `ping` messages at a fixed interval (heartbeat). * - Ignores `pong` replies in the message stream. * * Automatically unsubscribes from heartbeat pings on socket close. * * @param url WebSocket server URL (default: wss://wss.nori.it.com) * @param heartBeatInterval Interval for heartbeat pings in ms (default: 3000) * @returns A filtered WebSocketSubject that emits structured subscription messages. */ export function getBridgeSocket$(url = 'wss://wss.nori.it.com', heartBeatInterval = 3000) { const heartBeatPing = interval(heartBeatInterval).pipe(map(() => bridgeSocket$.next({ method: 'ping' }))); let heatBeatPingSub; const bridgeSocket$ = webSocket({ url, openObserver: { next: () => { if (heartBeatInterval) heatBeatPingSub = heartBeatPing.subscribe(); bridgeSocket$.next(subscribeStateEth); bridgeSocket$.next(subscribeStateBridge); bridgeSocket$.next(subscribeTimingsTransition); }, }, closeObserver: { next: () => { if (heatBeatPingSub) heatBeatPingSub.unsubscribe(); }, }, deserializer: (e) => e.data, }); return bridgeSocket$.pipe(filter((messageStr) => messageStr !== pongReplyStr), map((message) => JSON.parse(message))); } /** * Creates a reconnecting WebSocketSubject that: * - Handles connection state tracking and automatic reconnect with exponential backoff * - Sends regular `ping` heartbeats * - Watches for missing `pong` responses and triggers reconnection if threshold is exceeded * - Subscribes to key bridge-related topics on connection * - Filters out pong replies from message stream * * @param url WebSocket server URL (default: wss://wss.nori.it.com) * @param heartBeatInterval Interval in ms for sending pings (default: 3000) * @param pongTimeoutMultiplier Multiplier to determine allowed pong delay before reconnection (default: 2) * @returns An object containing: * - `bridgeSocket$`: the reconnecting WebSocket observable for bridge messages * - `bridgeSocketConnectionState$`: connection state observable */ export function getReconnectingBridgeSocket$(url = 'wss://wss.nori.it.com', heartBeatInterval = 3000, pongTimeoutMultiplier = 2) { const { webSocket$: bridgeSocket$, webSocketConnectionState$ } = reconnectingWebSocket({ url, openObserver: { next: () => { lastPongReceived = Date.now(); if (heartBeatInterval) { heartbeatPingSub = heartBeatPing.subscribe(); pongTimeoutCheckSub = pongTimeoutCheck.subscribe(); } bridgeSocket$.next(subscribeStateEth); bridgeSocket$.next(subscribeStateBridge); bridgeSocket$.next(subscribeTimingsTransition); }, }, closeObserver: { next: () => { heartbeatPingSub?.unsubscribe(); pongTimeoutCheckSub?.unsubscribe(); }, }, deserializer: (e) => e.data, }); let lastPongReceived = Date.now(); let heartbeatPingSub; let pongTimeoutCheckSub; // Heartbeat const heartBeatPing = interval(heartBeatInterval).pipe(map(() => bridgeSocket$.next({ method: 'ping' }))); // Detect bad connecions const pongTimeoutThreshold = heartBeatInterval * pongTimeoutMultiplier; const pongTimeoutCheck = interval(heartBeatInterval).pipe(map(() => { const now = Date.now(); if (now - lastPongReceived > pongTimeoutThreshold) { bridgeSocket$.forceReconnect(); } })); // Listen for pong replies and update timestamp const finalSocket$ = bridgeSocket$.pipe(map((messageStr) => { if (messageStr === pongReplyStr) { lastPongReceived = Date.now(); return null; } return JSON.parse(messageStr); }), filter((msg) => msg !== null)); return { bridgeSocket$: finalSocket$, bridgeSocketConnectionState$: webSocketConnectionState$, }; } //# sourceMappingURL=socket.js.map