"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchLengthPrefixedFrameHttpBinaryStream = exports.FetchHttpStreamResponseError = exports.createQueryString = exports.createTheGraphClient = exports.formLookupKey = exports.generateClientId = exports.convertPeerDescriptorToNetworkPeerDescriptor = exports.peerDescriptorTranslator = exports.MaxSizedSet = exports.formStorageNodeAssignmentStreamId = exports.getEndpointUrl = exports.instanceId = exports.counterId = exports.CounterId = void 0;
const dht_1 = require("@streamr/dht");
const protocol_1 = require("@streamr/protocol");
const utils_1 = require("@streamr/utils");
const compact_1 = __importDefault(require("lodash/compact"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const quick_lru_1 = __importDefault(require("../../vendor/quick-lru"));
const Config_1 = require("../Config");
const WebStreamToNodeStream_1 = require("./WebStreamToNodeStream");
const uuid_1 = require("./uuid");
const dht_2 = require("@streamr/dht");
const logger = new utils_1.Logger(module);
const CounterId = (rootPrefix, { maxPrefixes = 256 } = {}) => {
    let counts = {}; // possible we could switch this to WeakMap and pass functions or classes.
    let didWarn = false;
    const counterIdFn = (prefix = 'ID', separator = uuid_1.SEPARATOR) => {
        // pedantic: wrap around if count grows too large
        counts[prefix] = (counts[prefix] + 1 || 0) % Number.MAX_SAFE_INTEGER;
        // warn once if too many prefixes
        if (!didWarn) {
            const numTracked = Object.keys(counts).length;
            if (numTracked > maxPrefixes) {
                didWarn = true;
                console.warn(`counterId should not be used for a large number of unique prefixes: ${numTracked} > ${maxPrefixes}`);
            }
        }
        // connect prefix with separator
        return [rootPrefix, prefix, counts[prefix]]
            .filter((v) => v != null) // remove {root}Prefix if not set
            .join(separator);
    };
    /**
     * Clears counts for prefix or all if no prefix supplied.
     *
     * @param {string?} prefix
     */
    counterIdFn.clear = (...args) => {
        // check length to differentiate between clear(undefined) & clear()
        if (args.length) {
            const [prefix] = args;
            delete counts[prefix];
        }
        else {
            // clear all
            counts = {};
        }
    };
    return counterIdFn;
};
exports.CounterId = CounterId;
exports.counterId = (0, exports.CounterId)();
function instanceId(instance, suffix = '') {
    return (0, exports.counterId)(instance.constructor.name) + suffix;
}
exports.instanceId = instanceId;
const getEndpointUrl = (baseUrl, ...pathParts) => {
    return baseUrl + '/' + pathParts.map((part) => encodeURIComponent(part)).join('/');
};
exports.getEndpointUrl = getEndpointUrl;
function formStorageNodeAssignmentStreamId(clusterAddress) {
    return (0, protocol_1.toStreamID)('/assignments', (0, utils_1.toEthereumAddress)(clusterAddress));
}
exports.formStorageNodeAssignmentStreamId = formStorageNodeAssignmentStreamId;
class MaxSizedSet {
    delegate;
    constructor(maxSize) {
        this.delegate = new quick_lru_1.default({ maxSize });
    }
    add(value) {
        this.delegate.set(value, true);
    }
    has(value) {
        return this.delegate.has(value);
    }
    delete(value) {
        this.delegate.delete(value);
    }
}
exports.MaxSizedSet = MaxSizedSet;
// TODO: rename to convertNetworkPeerDescriptorToPeerDescriptor
// This function contains temporary compatibility layer which allows that PeerDescriptor can be configured with 
// "id" field instead of "nodeId" field. This is done so that pretestnet users don't need to change their configs.
// After strear-1.0 testnet1 or mainnet starts, remove this hack.
// - Good to ensure at that point that the new format has landed to the public documentation: 
//   https://docs.streamr.network/guides/become-an-operator
// - or maybe NET-1133 or NET-1004 have been implemented and the documentation no longer mentions the low
//   level way of configuring the entry points.
// Actions:
// - remove "temporary compatibility" test case from Broker's config.test.ts 
// - remove "id" property from config.schema.json (line 536) and make "nodeId" property required
// - remove "id" property handling from this method
function peerDescriptorTranslator(json) {
    const type = json.type === Config_1.NetworkNodeType.BROWSER ? dht_2.NodeType.BROWSER : dht_2.NodeType.NODEJS;
    const peerDescriptor = {
        ...json,
        nodeId: (0, dht_1.getRawFromDhtAddress)((json.nodeId ?? json.id)),
        type,
        websocket: json.websocket
    };
    if (peerDescriptor.id !== undefined) {
        delete peerDescriptor.id;
    }
    return peerDescriptor;
}
exports.peerDescriptorTranslator = peerDescriptorTranslator;
function convertPeerDescriptorToNetworkPeerDescriptor(descriptor) {
    // TODO maybe we should copy most/all fields of PeerDescription (NET-1255)
    return {
        nodeId: (0, dht_1.getDhtAddressFromRaw)(descriptor.nodeId),
        type: descriptor.type === dht_2.NodeType.NODEJS ? Config_1.NetworkNodeType.NODEJS : Config_1.NetworkNodeType.BROWSER,
        websocket: descriptor.websocket,
        region: descriptor.region
    };
}
exports.convertPeerDescriptorToNetworkPeerDescriptor = convertPeerDescriptorToNetworkPeerDescriptor;
function generateClientId() {
    return (0, exports.counterId)(process.pid ? `${process.pid}` : (0, utils_1.randomString)(4), '/');
}
exports.generateClientId = generateClientId;
// A unique internal identifier to some list of primitive values. Useful
// e.g. as a map key or a cache key.
const formLookupKey = (...args) => {
    return args.join('|');
};
exports.formLookupKey = formLookupKey;
/** @internal */
const createTheGraphClient = (eventEmitter, config) => {
    const instance = new utils_1.TheGraphClient({
        serverUrl: config.contracts.theGraphUrl,
        fetch: (url, init) => {
            // eslint-disable-next-line no-underscore-dangle
            const timeout = config._timeouts.theGraph.fetchTimeout;
            return (0, node_fetch_1.default)(url, (0, utils_1.merge)({ timeout }, init));
        },
        // eslint-disable-next-line no-underscore-dangle
        indexTimeout: config._timeouts.theGraph.indexTimeout,
        // eslint-disable-next-line no-underscore-dangle
        indexPollInterval: config._timeouts.theGraph.indexPollInterval
    });
    eventEmitter.on('confirmContractTransaction', (payload) => {
        instance.updateRequiredBlockNumber(payload.receipt.blockNumber);
    });
    return instance;
};
exports.createTheGraphClient = createTheGraphClient;
const createQueryString = (query) => {
    const withoutEmpty = Object.fromEntries(Object.entries(query).filter(([_k, v]) => v != null));
    return new URLSearchParams(withoutEmpty).toString();
};
exports.createQueryString = createQueryString;
class FetchHttpStreamResponseError extends Error {
    response;
    constructor(response) {
        super(`Fetch error, url=${response.url}`);
        this.response = response;
    }
}
exports.FetchHttpStreamResponseError = FetchHttpStreamResponseError;
const fetchLengthPrefixedFrameHttpBinaryStream = async function* (url, abortSignal) {
    logger.debug('Send HTTP request', { url });
    const abortController = new AbortController();
    const fetchAbortSignal = (0, utils_1.composeAbortSignals)(...(0, compact_1.default)([abortController.signal, abortSignal]));
    const response = await (0, node_fetch_1.default)(url, {
        signal: fetchAbortSignal
    });
    logger.debug('Received HTTP response', {
        url,
        status: response.status,
    });
    if (!response.ok) {
        throw new FetchHttpStreamResponseError(response);
    }
    if (!response.body) {
        throw new Error('No Response Body');
    }
    let stream;
    try {
        // in the browser, response.body will be a web stream. Convert this into a node stream.
        const source = (0, WebStreamToNodeStream_1.WebStreamToNodeStream)(response.body);
        stream = source.pipe(new utils_1.LengthPrefixedFrameDecoder());
        source.on('error', (err) => stream.destroy(err));
        stream.once('close', () => {
            abortController.abort();
        });
        yield* stream;
    }
    catch (err) {
        abortController.abort();
        throw err;
    }
    finally {
        stream?.destroy();
        fetchAbortSignal.destroy();
    }
};
exports.fetchLengthPrefixedFrameHttpBinaryStream = fetchLengthPrefixedFrameHttpBinaryStream;
//# sourceMappingURL=utils.js.map