"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const sinon_1 = __importDefault(require("sinon"));
const io = __importStar(require("socket.io-client"));
const config_1 = __importDefault(require("../../src/config"));
const p2p_1 = require("../../src/modules/bitcoin/p2p");
const rpc_1 = require("../../src/rpc");
const api_1 = require("../../src/services/api");
const event_1 = require("../../src/services/event");
const helpers_1 = require("../helpers");
const crypto_wallet_core_1 = require("@bitpay-labs/crypto-wallet-core");
const bitcore_client_1 = require("@bitpay-labs/bitcore-client");
const wallet_1 = require("../../src/models/wallet");
const walletAddress_1 = require("../../src/models/walletAddress");
const socket_1 = require("../../src/services/socket");
const utils_1 = require("../../src/utils");
const integration_1 = require("../helpers/integration");
describe('Websockets', function () {
    const chain = 'BTC';
    const network = 'regtest';
    let chainConfig;
    let creds;
    let rpc;
    const { PrivateKey } = crypto_wallet_core_1.BitcoreLib;
    function getSocket() {
        const socket = io.connect('http://localhost:3000', { transports: ['websocket'] });
        return socket;
    }
    let p2pWorker;
    let socket = getSocket();
    const bwsPrivKey = new PrivateKey();
    const bwsKey = bwsPrivKey.toPublicKey().toString();
    const authKey = new PrivateKey();
    const pubKey = authKey.toPublicKey().toString();
    const address = '2MuYKLUaKCenkEpwPkWUwYpBoDBNA2dgY3t';
    const sandbox = sinon_1.default.createSandbox();
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const suite = this;
    this.timeout(60000);
    before(async function () {
        chainConfig = config_1.default.chains[chain][network];
        creds = chainConfig.rpc;
        rpc = new rpc_1.AsyncRPC(creds.username, creds.password, creds.host, creds.port);
        await (0, integration_1.intBeforeHelper)();
        sandbox.stub(socket_1.Socket.serviceConfig, 'bwsKeys').value([bwsKey]);
        await (0, helpers_1.resetDatabase)();
        await event_1.Event.start();
        await api_1.Api.start();
        const inserted = await wallet_1.WalletStorage.collection.insertOne({
            chain,
            network,
            name: 'WalletSocketTest',
            singleAddress: false,
            pubKey,
            path: ''
        });
        await walletAddress_1.WalletAddressStorage.collection.insertOne({
            address,
            chain,
            network,
            processed: true,
            wallet: inserted.insertedId
        });
    });
    after(async () => {
        await event_1.Event.stop();
        await api_1.Api.stop();
        await (0, helpers_1.resetDatabase)();
        await (0, integration_1.intAfterHelper)(suite);
    });
    beforeEach(async () => {
        socket = getSocket();
        const connected = new Promise(r => {
            socket.on('connect', () => {
                console.log('Socket connected');
                r();
            });
        });
        await connected;
        p2pWorker = new p2p_1.BitcoinP2PWorker({
            chain,
            network,
            chainConfig
        });
        console.log('Starting p2p worker');
        p2pWorker.start();
        if (p2pWorker.isSyncing) {
            console.log('Worker is syncing. Waiting til done');
            await p2pWorker.syncDone();
        }
        console.log('Waiting til p2p done');
        await p2pWorker.waitTilSync();
        console.log('P2P done');
    });
    afterEach(async () => {
        try {
            console.log('Stopping p2p worker');
            await p2pWorker.stop();
            console.log('Disconnecting socket');
            await socket.disconnect();
        }
        catch (e) {
            console.log('Error stopping p2p worker');
        }
    });
    it('should get websocket events', async () => {
        socket.emit('room', '/BTC/regtest/inv');
        let hasSeenTxEvent = false;
        let hasSeenBlockEvent = false;
        let hasSeenCoinEvent = false;
        const anAddress = await rpc.getnewaddress('');
        const sawEvents = new Promise(async (resolve) => {
            socket.on('block', () => {
                hasSeenBlockEvent = true;
                console.log('Block event received');
                if (hasSeenTxEvent && hasSeenCoinEvent && hasSeenBlockEvent) {
                    resolve();
                }
            });
            socket.on('tx', () => {
                hasSeenTxEvent = true;
                console.log('Transaction event received');
                if (hasSeenTxEvent && hasSeenCoinEvent && hasSeenBlockEvent) {
                    resolve();
                }
            });
            socket.on('coin', () => {
                hasSeenCoinEvent = true;
                console.log('Coin event received');
                if (hasSeenTxEvent && hasSeenCoinEvent && hasSeenBlockEvent) {
                    resolve();
                }
            });
            while (!hasSeenBlockEvent) {
                console.log('WAITING FOR BLOCK EVENT ON SOCKET');
                if (hasSeenTxEvent && hasSeenCoinEvent && hasSeenBlockEvent) {
                    return resolve();
                }
                await (0, utils_1.wait)(1000);
            }
        });
        console.log('Generating 100 blocks');
        await rpc.call('generatetoaddress', [101, anAddress]);
        await p2pWorker.syncDone();
        console.log('Sync done, generating new block');
        await rpc.call('generatetoaddress', [1, anAddress]);
        console.log('Sending bitcoin');
        await rpc.sendtoaddress(address, 0.1);
        await sawEvents;
        (0, chai_1.expect)(hasSeenBlockEvent).to.equal(true);
        (0, chai_1.expect)(hasSeenTxEvent).to.equal(true);
        (0, chai_1.expect)(hasSeenCoinEvent).to.equal(true);
    });
    it('should get wallet events', async () => {
        const authClient = new bitcore_client_1.Client({ apiUrl: 'http://localhost:3000/api', authKey });
        const payload = { method: 'socket', url: 'http://localhost:3000/api' };
        const authPayload = { pubKey, message: authClient.getMessage(payload), signature: authClient.sign(payload) };
        const chain = 'BTC';
        const network = 'regtest';
        const roomPrefix = `/${chain}/${network}/`;
        socket.emit('room', roomPrefix + 'wallet', authPayload);
        let hasSeenTxEvent = false;
        let hasSeenCoinEvent = false;
        const sawEvents = new Promise(async (resolve) => {
            socket.on('tx', () => {
                hasSeenTxEvent = true;
                console.log('Transaction event received');
                if (hasSeenTxEvent && hasSeenCoinEvent) {
                    resolve();
                }
            });
            socket.on('coin', () => {
                hasSeenCoinEvent = true;
                console.log('Coin event received');
                if (hasSeenTxEvent && hasSeenCoinEvent) {
                    resolve();
                }
            });
            while (!hasSeenTxEvent) {
                console.log('WAITING FOR TX EVENT ON SOCKET');
                if (hasSeenTxEvent && hasSeenCoinEvent) {
                    return resolve();
                }
                await (0, utils_1.wait)(1000);
            }
        });
        await rpc.sendtoaddress(address, 0.1);
        await sawEvents;
        (0, chai_1.expect)(hasSeenTxEvent).to.equal(true);
        (0, chai_1.expect)(hasSeenCoinEvent).to.equal(true);
    });
    it('should get all wallet events', async () => {
        const authClient = new bitcore_client_1.Client({ apiUrl: 'http://localhost:3000/api', authKey: bwsPrivKey });
        const payload = { method: 'socket', url: 'http://localhost:3000/api' };
        const authPayload = {
            pubKey: bwsKey,
            message: authClient.getMessage(payload),
            signature: authClient.sign(payload)
        };
        const chain = 'BTC';
        const network = 'regtest';
        const roomPrefix = `/${chain}/${network}/`;
        socket.emit('room', roomPrefix + 'wallets', authPayload);
        let hasSeenTxEvent = false;
        let hasSeenCoinEvent = false;
        const sawEvents = new Promise(async (resolve) => {
            socket.on('tx', () => {
                hasSeenTxEvent = true;
                console.log('Transaction event received');
                if (hasSeenTxEvent && hasSeenCoinEvent) {
                    resolve();
                }
            });
            socket.on('coin', () => {
                hasSeenCoinEvent = true;
                console.log('Coin event received');
                if (hasSeenTxEvent && hasSeenCoinEvent) {
                    resolve();
                }
            });
            while (!hasSeenTxEvent) {
                console.log('WAITING FOR Wallet-TX EVENT ON SOCKET');
                if (hasSeenTxEvent && hasSeenCoinEvent) {
                    return resolve();
                }
                await (0, utils_1.wait)(1000);
            }
        });
        await rpc.sendtoaddress(address, 0.1);
        await sawEvents;
        (0, chai_1.expect)(hasSeenTxEvent).to.equal(true);
        (0, chai_1.expect)(hasSeenCoinEvent).to.equal(true);
        sandbox.restore();
    });
    it('should get an error when the key does not match the bwsKey', async () => {
        const pubKey = authKey.toPublicKey().toString();
        const wrongKey = new PrivateKey();
        const authClient = new bitcore_client_1.Client({ apiUrl: 'http://localhost:3000/api', authKey: wrongKey });
        const payload = { method: 'socket', url: 'http://localhost:3000/api' };
        const authPayload = { pubKey, message: authClient.getMessage(payload), signature: authClient.sign(payload) };
        const chain = 'BTC';
        const network = 'regtest';
        const roomPrefix = `/${chain}/${network}/`;
        const failed = new Promise(resolve => {
            socket.on('failure', e => {
                (0, chai_1.expect)(e.message).to.include('Authentication failed');
                resolve();
            });
        });
        socket.emit('room', roomPrefix + 'wallets', authPayload);
        await failed;
    });
    it('should get an error when the signature is invalid', async () => {
        const wrongKey = new PrivateKey();
        const authClient = new bitcore_client_1.Client({ apiUrl: 'http://localhost:3000/api', authKey: wrongKey });
        const payload = { method: 'socket', url: 'http://localhost:3000/api' };
        const authPayload = { pubKey, message: authClient.getMessage(payload), signature: 'invalid' };
        const chain = 'BTC';
        const network = 'regtest';
        const roomPrefix = `/${chain}/${network}/`;
        const failed = new Promise(resolve => {
            socket.on('failure', e => {
                (0, chai_1.expect)(e.message).to.include('Authentication failed');
                resolve();
            });
        });
        socket.emit('room', roomPrefix + 'wallet', authPayload);
        await failed;
    });
});
//# sourceMappingURL=websocket.test.js.map