import { WalletManager } from '../wallets.js'; import { WalletStreamManager } from '../stream.js'; // --- 1. Infrastructure Mocks --- const mockStorage = {}; globalThis.localStorage = { getItem: (key) => mockStorage[key] || null, setItem: (key, value) => { mockStorage[key] = String(value); }, clear: () => { Object.keys(mockStorage).forEach(k => delete mockStorage[k]); } }; // Accelerate timers so leader election (200ms) and backoff happen instantly const originalSetTimeout = globalThis.setTimeout; globalThis.setTimeout = (callback, delay) => originalSetTimeout(callback, 1); // Standard WebSocket Mock class MockWebSocket { constructor(url) { this.readyState = 1; // Immediately OPEN originalSetTimeout(() => { if (this.onopen) this.onopen(); }, 1); } send(data) { globalLastSentPayload = JSON.parse(data); } close() { if (this.onclose) this.onclose({ code: 1000 }); } } globalThis.WebSocket = MockWebSocket; // --- 2. The Cross-Tab Broadcast Router Mock --- const activeChannels = new Set(); class MockBroadcastChannel { constructor(name) { this.name = name; activeChannels.add(this); } postMessage(data) { // Distribute the message to all OTHER open channel instances simulation offline cross-tab delivery for (const channel of activeChannels) { if (channel !== this && channel.onmessage) { channel.onmessage({ data }); } } } close() { activeChannels.delete(this); } } globalThis.BroadcastChannel = MockBroadcastChannel; // Global tracking variables for assertions let globalLastSentPayload = null; function assert(condition, message) { if (!condition) throw new Error(`❌ FAIL: ${message}`); console.log(`✅ PASS: ${message}`); } // --- 3. The Multi-Tab Simulation Scenario --- async function runMultiTabTest() { console.log("🚀 Starting Multi-Tab Synchronization & Leader Election Test Suite...\n"); localStorage.clear(); // Seed initial wallet configuration const wm = new WalletManager(); wm.addWallet("0x04f728C520C438A000f7A5E9d904F0e725FFAEFE", "base", "Shared Wallet"); // --- STEP 1: Boot First Tab --- console.log("▶️ Initializing Tab 1..."); const tab1 = new WalletStreamManager(wm); tab1.connect(); await new Promise(r => originalSetTimeout(r, 15)); // Await leader election window assert(tab1.isLeader === true, "Tab 1 detected no existing leaders and claimed leadership role."); assert(tab1.ws !== null, "Tab 1 successfully initialized a physical WebSocket network connection."); // --- STEP 2: Boot Second Tab --- console.log("\n▶️ Initializing Tab 2 (Simulating a new browser window/tab)..."); const tab2 = new WalletStreamManager(wm); tab2.connect(); await new Promise(r => originalSetTimeout(r, 15)); assert(tab2.isLeader === false, "Tab 2 discovered active Leader and assigned itself as a Follower."); assert(tab2.ws === undefined || tab2.ws === null, "Tab 2 kept its local WebSocket offline to save system resources."); // --- STEP 3: Verify Event Cross-Broadcasting Flow --- let tab2ReceivedData = false; tab2.on('balance_update', (msg) => { if (msg.balance === "9999") tab2ReceivedData = true; }); // Simulate server pushing an update specifically to Tab 1's socket connection tab1.ws.onmessage({ data: JSON.stringify({ type: "balance_update", balance: "9999" }) }); await new Promise(r => originalSetTimeout(r, 5)); assert(tab2ReceivedData === true, "Tab 1 received WebSocket data and pushed it locally across the BroadcastChannel to Tab 2."); // --- STEP 4: Proxy Subscription Modification Check --- console.log("\n▶️ Simulating Follower (Tab 2) subscribing to a new asset dynamically..."); const newAsset = { address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", chain: "bitcoin" }; globalLastSentPayload = null; tab2.subscribeToNewWallet(newAsset); await new Promise(r => originalSetTimeout(r, 5)); assert(globalLastSentPayload !== null, "Tab 2's subscription was successfully intercepted and routed to the channel."); assert(globalLastSentPayload.action === "subscribe_wallet", "Tab 1 successfully executed the proxy network subscription on behalf of Tab 2."); // --- STEP 5: Graceful Failover Handover (Leader Death Scenario) --- console.log("\n🕵️‍♂️ Simulating user abruptly closing Tab 1 (Leader)..."); // Simulate browser exit/unload routine for Tab 1 tab1.disconnect(); // Dispatch the exit event if your module binds directly to window event listeners if (tab1._handleExit) tab1._handleExit(); await new Promise(r => originalSetTimeout(r, 15)); // Await emergency election frame assert(tab2.isLeader === true, "Tab 2 caught the LEADER_DEATH signal and successfully promoted itself to Leader."); assert(tab2.ws !== null, "Tab 2 gracefully opened a brand new physical WebSocket connection to pick up the stream."); console.log("\n🎉 Massive Success! Cross-tab synchronization, proxy forwarding, and leader failovers function perfectly."); } runMultiTabTest().catch(console.error);