npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

puppyproxy

v1.1.9

Published

Node.js library for managing, scraping, and using proxies with fetch and WebSocket support

Readme

puppyproxy

On npm @ https://www.npmjs.com/package/puppyproxy

And GitHub @ https://github.com/onlypuppy7/puppyproxy

Robust and reliable free proxy scraper and request wrapper, ported to npm. Another useless file taken out of my projects!

Install

npm install puppyproxy

Usage

import PuppyProxy from 'puppyproxy';
import log from 'puppylog'; //another one of my packages
import { wait } from 'puppymisc'; //another one of my packages!

const ipUrl = 'http://ifconfig.me/ip';
const wsUrl = 'wss://ws.postman-echo.com/raw';

const config = {
    // storeDir: './store', // optional, defaults to platform specific data dir
    ipvanish: {
        // use: true,
        // creds: { username: 'abc', password: '123' },
    },
    scraper: {
        use: true,
        timeBetweenScrapes: 45, // minutes
        maxProxiesToCheck: 5000, // the higher this is, the longer it takes, but the more proxies you get
        limitProxies: 500000,
        timeoutPerProxy: 15, // seconds
        timeoutMax: 5,
        verbose: true, // for logging purposes
        logStatus: true, // shows a convenient summary every so often
        proxyTypes: ['socks4', 'socks5'], //['socks4', 'socks5', 'http', 'https']
        method: 'ws', // 'ws' or 'http'
        wsUrl: wsUrl,

        timeoutFetch: 10000, // ms
        timeoutWs: 7000,
        maxRetries: 10,
    },
    LOG: { connect: true, error: true } // for logging purposes
};

const pp = new PuppyProxy(config);

log.highlight("TESTING: runCollector");

await pp.init(true); // true forces run the collector

console.log(pp.request.createProxyRequestPool().length, "proxies in the pool after init");

log.highlight("TESTING: RequestClient");

log.bold("----- pp.request.fetch")

//fetch with inbuilt resilient retrying in case of error. uses a new proxy from the pool each time
const resRequestFetch = await pp.request.fetch(ipUrl);
log.success("res from request fetch", await resRequestFetch.text());

log.bold("----- pp.request.fetchBasic")

//more basic fetch that just forwards to you the req object with a proxy agent set
try {
    const resFetchBasic = await (pp.request.fetchBasic(ipUrl));
    if (resFetchBasic?.ok) {
        log.success("res from basic fetch", await resFetchBasic.text());
    } else {
        //this may fail because free proxies are unreliable, and it only tries once
        log.error("basic fetch failed", resFetchBasic.status);
    }
} catch (error) {
    log.error("basic fetch error, which is sort of expected. create your own catching logic.", error.message);
}

log.bold("----- pp.request.ws")

//ws with inbuilt resilient retrying in case of error. uses a new proxy from the pool each time
const testWs = async (wsHandler) => {
    return new Promise(async (resolve, reject) => {
        await wsHandler(
            wsUrl,
            {
                // preferredAgent: 'none' // 'ipvanish', 'scraper', or 'none'
            },
            // open
            (ws) => {
                const testMessage = 'echo, test message';
                log.success(`Connected to echo server, sending test message ${testMessage}`);
                ws.send(JSON.stringify(testMessage));
            },
            // message
            (ws, event) => {
                try {
                    const data = JSON.parse(event.data);
                    log.success('Received JSON:', data);
                } catch {
                    log.success('Received text:', event.data);
                }

                // close after we get responses
                ws.close();
                wait(500).then(resolve);
            },
            // close
            (ws, event) => {
                log.italic(`WebSocket closed with code: ${event.code}, reason: ${event.reason}`);
            },
            // error
            (ws, error) => {
                // reject(error);
            }
        );
        resolve();
    });
}

try {
    await testWs(pp.request.ws.bind(pp.request));
} catch (error) {
    log.error("WebSocket connection failed:", error.message);
};

log.bold("----- pp.request.wsBasic");

const testWsBasic = async (wsBasicHandler) => {
    try {
        await new Promise(async (resolve, reject) => {
            const ws = wsBasicHandler(wsUrl, {});

            ws.onopen = (event) => {
                const testMessage = 'echo, test message via wsBasic';
                log.success(`Connected to echo server via wsBasic, sending test message ${testMessage}`);
                ws.send(JSON.stringify(testMessage));
            };

            ws.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    log.success('wsBasic Received JSON:', data);
                } catch {
                    log.success('wsBasic Received text:', event.data);
                }

                ws.close();
                wait(500).then(resolve);
            };

            ws.onclose = (event) => {
                log.italic(`wsBasic WebSocket closed with code: ${event.code}, reason: ${event.reason}`);
            };

            ws.onerror = (error) => {
                log.error("wsBasic WebSocket error, which is sort of expected. create your own catching logic.", error.message);
                reject(error);
            };
        });
    } catch (error) {
        // log.error("proxyRequest wsBasic WebSocket connection failed:", error.message);
    };
};

//similar basic method like pp.request.fetchBasic exists for pp.request.wsBasic if you want less resilience and more control.

await testWsBasic(pp.request.wsBasic.bind(pp.request));

log.highlight("TESTING: ProxyRequest");

//proxyRequest is a wrapper that allows you to use a more consistent proxy for multiple requests, with auto-rotation when it fails
const proxyRequest = pp.createProxyRequest(ipUrl, {
    autoNewAgent: true, // auto rotate the proxy when a request fails. set this to false if you really need just one proxy you can manage yourself
});

log.bold("----- proxyRequest.fetch");

//all methods are similar to pp.request but use the same proxy until it fails
const resProxyRequestFetch = await proxyRequest.fetch(); //url can be passed here, but it already was set in constructor
log.success("res from proxyRequest fetch", await resProxyRequestFetch.text());

log.bold("----- proxyRequest.fetchBasic");

const testProxyRequestFetchBasic = async () => {
    try {
        const resProxyRequestFetchBasic = await (proxyRequest.fetchBasic());
        if (resProxyRequestFetchBasic?.ok) {
            log.success("res from proxyRequest basic fetch", await resProxyRequestFetchBasic.text());
        } else {
            log.error("proxyRequest basic fetch failed", resProxyRequestFetchBasic.status);
        }
    } catch (error) {
        log.error("proxyRequest basic fetch error, which is sort of expected. create your own catching logic.", error.message);
    }
};

await testProxyRequestFetchBasic();
//try again to see it use the same proxy
await testProxyRequestFetchBasic();

log.bold("----- proxyRequest.newAgent");

//you can manually rotate the proxy too
proxyRequest.newAgent();

await testProxyRequestFetchBasic();

log.bold("----- proxyRequest.ws");

try {
    await testWs(proxyRequest.ws.bind(proxyRequest));
} catch (error) {
    log.error("proxyRequest WebSocket connection failed:", error.message);
}

log.bold("----- proxyRequest.wsBasic");

await testWsBasic(proxyRequest.wsBasic.bind(proxyRequest));

log.highlight("TESTING: createGlobalPatch");

pp.createGlobalPatch();

log.bold("----- global fetch")

const resGlobalFetch = await fetch(ipUrl);
log.success("res from global fetch", await resGlobalFetch.text());

//note: i dont think you can override node-fetch. sad!

log.bold("----- global WebSocket")

try {
    await new Promise((resolve, reject) => {
        const ws = new WebSocket(wsUrl);
        ws.onopen = (event) => {
            const testMessage = 'echo, test message via global WebSocket';
            log.success(`Connected to echo server via global WebSocket, sending test message ${testMessage}`);
            ws.send(JSON.stringify(testMessage));
        };

        ws.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                log.success('global WebSocket Received JSON:', data);
            } catch {
                log.success('global WebSocket Received text:', event.data);
            }
            ws.close();
            wait(500).then(resolve);
        };

        ws.onclose = (event) => {
            log.italic(`global WebSocket closed with code: ${event.code}, reason: ${event.reason}`);
        };

        ws.onerror = (error) => {
            log.error("global WebSocket error, which is sort of expected. create your own catching logic.", error.message);
            reject(error);
        };
    });
} catch (error) {
    log.error("global WebSocket connection failed:", error.message);
}