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

@aitofy/browser-profiles

v0.2.12

Published

Self-hosted anti-detect browser profiles. Open-source AdsPower alternative for Puppeteer & Playwright.

Readme

@aitofy/browser-profiles

🔒 Self-hosted anti-detect browser profiles. The open-source alternative to AdsPower & Multilogin. Run locally, own your data, no subscriptions.

npm version License: MIT TypeScript Self-Hosted

🎯 Why browser-profiles?

Like n8n for automation or Affine for notes, this is AdsPower for developers — self-hosted, open-source, and privacy-first.

| ❌ AdsPower/Multilogin | ✅ browser-profiles | |------------------------|---------------------| | $99+/month | Free forever | | Cloud storage (data not yours) | Local storage (your data) | | GUI only | Code-first (Puppeteer/Playwright) | | Vendor lock-in | Open source (MIT) | | No customization | Full control |

✨ Features

  • 🏠 Self-hosted - Data stays on your machine, no cloud dependency
  • 🔐 Privacy-first - Zero telemetry, zero tracking
  • 🛡️ Anti-detect - WebRTC, Canvas, WebGL, Audio fingerprint protection
  • 🌐 Proxy support - HTTP, HTTPS, SOCKS5 with auto timezone detection
  • 📦 Profile management - Create, update, delete browser profiles
  • 🎭 Puppeteer & Playwright - First-class integration
  • Zero config - Works out of the box
  • 🪶 Lightweight - No extensions, pure CDP injection

📦 Installation

# Recommended: rebrowser-puppeteer-core (better anti-detect!)
npm install @aitofy/browser-profiles rebrowser-puppeteer-core

# Or standard puppeteer
npm install @aitofy/browser-profiles puppeteer-core

Anti-Detect Score

| Site | Score | Notes | |------|-------|-------| | browserleaks.com | ✅ 100% | All checks passed | | pixelscan.net | ✅ 100% | Hardware fingerprint passed | | browserscan.net | ⚠️ 95% | Bot Control -5% (Puppeteer limitation) | | creepjs | ⚠️ 85% | Minor deductions |

Note: 95% is the best achievable with Puppeteer/Playwright. 100% requires modified Chromium (like AdsPower).

💻 CLI Tool

Install globally to use the CLI:

npm install -g @aitofy/browser-profiles

Commands

# List all profiles
browser-profiles list

# Create a new profile
browser-profiles create my-account
browser-profiles create my-account --proxy http://user:[email protected]:8080

# Open browser with a profile
browser-profiles open <profile-id>

# Quick launch (no profile needed)
browser-profiles launch
browser-profiles launch --proxy http://proxy.com:8080

# Show profile details
browser-profiles info <profile-id>

# Delete a profile
browser-profiles delete <profile-id>

# Show storage path
browser-profiles path

🚀 Quick Start

⚡ 30-Second Example

import { quickLaunch } from '@aitofy/browser-profiles';

// Launch anti-detect browser with proxy (timezone auto-detected!)
const { page, close } = await quickLaunch({
  proxy: { type: 'http', host: 'your-proxy.com', port: 8080 },
});

await page.goto('https://browserscan.net');
await page.screenshot({ path: 'proof.png' });
await close();

That's it! 🎉 Browser fingerprint is protected, IP is hidden, timezone matches proxy location.


Profile Management

import { BrowserProfiles } from '@aitofy/browser-profiles';

const profiles = new BrowserProfiles();

// Create a profile (saved to ~/.aitofy/browser-profiles/)
const profile = await profiles.create({
  name: 'My Profile',
  proxy: {
    type: 'http',
    host: 'proxy.example.com',
    port: 8080,
  },
});

// Launch browser
const { wsEndpoint, close } = await profiles.launch(profile.id);

// ... automation work ...

await close();

🆕 Custom Profile IDs

You can define your own custom profile IDs instead of using auto-generated ones:

// Create profile with custom ID
const profile = await profiles.create({
  id: 'google-main',      // Custom ID (alphanumeric + hyphen/underscore)
  name: 'Google Account',
});

// Launch by custom ID directly
const { wsEndpoint } = await profiles.launch('google-main');

Custom ID rules:

  • 1-64 characters
  • Alphanumeric with hyphens and underscores only (a-z, A-Z, 0-9, -, _)
  • Must be unique

🆕 Launch by Profile Name

You can now launch browsers using the profile name instead of ID:

// Create profile
await profiles.create({ name: 'Facebook Account' });

// Launch by name (case-insensitive)
const { wsEndpoint } = await profiles.launchByName('Facebook Account');

// Or use launchByIdOrName (works with both ID and name)
await profiles.launchByIdOrName('google-main');     // By ID
await profiles.launchByIdOrName('Facebook Account'); // By name

With Puppeteer

import { withPuppeteer } from '@aitofy/browser-profiles/puppeteer';

const { browser, page, close } = await withPuppeteer({
  profile: 'my-profile-id', // or profile name
});

await page.goto('https://whoer.net');
await page.screenshot({ path: 'screenshot.png' });

await close();

Quick Launch (No Profile Needed)

import { quickLaunch } from '@aitofy/browser-profiles/puppeteer';

const { browser, page, close } = await quickLaunch({
  proxy: {
    type: 'http',
    host: 'proxy.example.com',
    port: 8080,
  },
  // timezone is now AUTO-DETECTED from proxy IP!
  // No need to specify manually. If no proxy, system timezone is used.
  fingerprint: {
    platform: 'Win32',           // Spoof as Windows
    hardwareConcurrency: 8,      // Spoof CPU cores
    language: 'en-US',
  },
});

// Output: [browser-profiles] 🌍 Auto-detected timezone: America/New_York (New York, United States)

await page.goto('https://browserscan.net');
await close();

With Playwright

import { withPlaywright } from '@aitofy/browser-profiles/playwright';

const { browser, page, close } = await withPlaywright({
  profile: 'my-profile-id',
});

await page.goto('https://example.com');
await close();

🆕 v0.2.0: Advanced Features

Temporary Sessions (No Profile Persistence)

import { createSession } from '@aitofy/browser-profiles/puppeteer';

// Quick session with random fingerprint - perfect for scraping
const session = await createSession({
  temporary: true,
  randomFingerprint: true, // Random platform, language, CPU cores
  proxy: { type: 'http', host: 'proxy.com', port: 8080 },
});

await session.page.goto('https://example.com');
await session.close(); // Cleanup

Patch Existing Pages

import { patchPage } from '@aitofy/browser-profiles/puppeteer';

// Apply anti-detect patches to any page
await patchPage(page, {
  webdriver: true,     // Hide webdriver flag
  plugins: true,       // Spoof Chrome plugins
  chrome: true,        // Fix chrome object
  webrtc: true,        // WebRTC leak protection
  fingerprint: { platform: 'Win32', hardwareConcurrency: 8 },
});

Generate Fingerprints On-Demand

import { generateFingerprint, getFingerprintScripts } from '@aitofy/browser-profiles';

// Generate a realistic fingerprint
const fp = generateFingerprint({
  platform: 'macos',
  gpu: 'apple',
  screen: 'retina',
  language: 'ja-JP',
});

console.log(fp.userAgent);      // Mozilla/5.0 (Macintosh...
console.log(fp.webgl.renderer); // ANGLE (Apple, Apple M1 Pro...

// Use with any page
const scripts = getFingerprintScripts(fp);
await page.evaluateOnNewDocument(scripts);

Standalone Chrome Launch

import { launchChromeStandalone } from '@aitofy/browser-profiles';
import puppeteer from 'puppeteer-core';

// Launch Chrome without profile management
const { wsEndpoint, close } = await launchChromeStandalone({
  headless: false,
  proxy: { type: 'http', host: 'proxy.com', port: 8080 },
});

const browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });
await close();

Inject Your Own Puppeteer

import puppeteer from 'rebrowser-puppeteer-core';
import { withPuppeteer } from '@aitofy/browser-profiles/puppeteer';

// Use your own puppeteer instance
const { browser, page } = await withPuppeteer({
  profile: 'my-profile',
  puppeteer, // ← Inject here
});

📁 Profile Management

Profiles are saved locally to ~/.aitofy/browser-profiles/ and persist between sessions.

import { BrowserProfiles } from '@aitofy/browser-profiles';

const profiles = new BrowserProfiles();

// ===== CREATE =====
const profile = await profiles.create({
  name: 'Facebook Account 1',
  proxy: { type: 'http', host: 'proxy.example.com', port: 8080 },
  tags: ['facebook', 'marketing'],
});

// ===== LIST ALL =====
const allProfiles = await profiles.list();
console.log(`Total: ${allProfiles.length} profiles`);

// ===== LIST BY TAG =====
const fbProfiles = await profiles.list({ tags: ['facebook'] });

// ===== GET BY ID =====
const myProfile = await profiles.get(profile.id);

// ===== UPDATE =====
await profiles.update(profile.id, {
  name: 'Facebook Account 1 - Updated',
  proxy: { type: 'socks5', host: 'new-proxy.com', port: 1080 },
});

// ===== LAUNCH BROWSER =====
const { wsEndpoint, close } = await profiles.launch(profile.id);
// ... do automation ...
await close();

// ===== DUPLICATE =====
const cloned = await profiles.duplicate(profile.id, 'Facebook Account 2');

// ===== EXPORT / IMPORT =====
const json = await profiles.export(profile.id);
const imported = await profiles.import(json);

// ===== DELETE =====
await profiles.delete(profile.id);

📂 Groups (Organization)

// Create groups
const group = await profiles.createGroup('TikTok Shop', 'All TikTok accounts');

// Move profile to group
await profiles.moveToGroup(profile.id, group.id);

// List groups
const groups = await profiles.listGroups();

// List profiles in group
const tikTokProfiles = await profiles.list({ groupId: group.id });

ExTower Integration

import { ExTowerClient } from '@aitofy/browser-profiles/extower';
import puppeteer from 'puppeteer-core';

const client = new ExTowerClient({
  baseUrl: 'http://localhost:50325', // default
});

// Create profile in ExTower
const { id } = await client.createProfile({
  name: 'TikTok Shop 1',
  proxy: {
    type: 'http',
    host: 'proxy.example.com',
    port: 8080,
  },
});

// Launch browser
const { puppeteer: wsEndpoint } = await client.launchBrowser(id);

// Connect Puppeteer
const browser = await puppeteer.connect({
  browserWSEndpoint: wsEndpoint,
});

const page = await browser.newPage();
await page.goto('https://seller-us.tiktok.com');

📖 API Reference

BrowserProfiles

Main class for managing browser profiles.

const profiles = new BrowserProfiles({
  storagePath: '~/.aitofy/browser-profiles',  // Default: ~/.aitofy/browser-profiles
  chromePath: '/path/to/chrome',              // Custom Chrome path (optional)
  defaultTimezone: 'UTC',                     // Default timezone for new profiles
});

Default storage locations:

  • macOS: ~/.aitofy/browser-profiles
  • Linux: ~/.aitofy/browser-profiles
  • Windows: C:\Users\<user>\.aitofy\browser-profiles

Methods

| Method | Description | |--------|-------------| | create(config) | Create a new profile (supports custom ID via config.id) | | get(id) | Get profile by ID | | getByName(name) | Get profile by name (case-insensitive) | | getByIdOrName(idOrName) | Get profile by ID or name | | list(options?) | List all profiles | | update(id, updates) | Update profile | | delete(id) | Delete profile | | launch(id, options?) | Launch browser by profile ID | | launchByName(name, options?) | Launch browser by profile name | | launchByIdOrName(idOrName, options?) | Launch browser by ID or name | | close(id) | Close running browser | | closeAll() | Close all browsers | | duplicate(id) | Duplicate profile | | export(id) | Export to JSON | | import(json) | Import from JSON |

Profile Configuration

interface ProfileConfig {
  id?: string;                     // Custom profile ID (auto-generated if omitted)
  name: string;                    // Profile name
  proxy?: ProxyConfig;             // Proxy settings
  timezone?: string;               // e.g., "America/New_York"
  cookies?: ProfileCookie[];       // Cookies to inject
  fingerprint?: FingerprintConfig; // Fingerprint settings
  startUrls?: string[];            // URLs to open on launch
  tags?: string[];                 // Tags for organization
  groupId?: string;                // Group for organization
}

Proxy Configuration

interface ProxyConfig {
  type: 'http' | 'https' | 'socks5';
  host: string;
  port: number | string;
  username?: string;
  password?: string;
}

Launch Options

interface LaunchOptions {
  headless?: boolean;              // Run headless (default: false)
  chromePath?: string;             // Custom Chrome path
  args?: string[];                 // Additional Chrome args
  extensions?: string[];           // Extension paths to load
  defaultViewport?: { width: number; height: number } | null;
  slowMo?: number;                 // Slow down by ms
  timeout?: number;                // Launch timeout
}

🔒 Anti-Detect Features

All fingerprint protections are hardcoded and injected via CDP - no extensions required!

🤖 Automation Detection Bypass

Comprehensive protection against bot detection:

| Check | Status | Description | |-------|--------|-------------| | navigator.webdriver | ✅ Hidden | Returns undefined | | window.chrome | ✅ Faked | Complete chrome object | | chrome.runtime | ✅ Faked | Runtime object present | | chrome.csi() | ✅ Faked | Timing function | | chrome.loadTimes() | ✅ Faked | Page load metrics | | navigator.plugins | ✅ Faked | 3 default Chrome plugins | | navigator.connection | ✅ Faked | 4G connection info | | navigator.getBattery() | ✅ Faked | Battery status API | | navigator.permissions | ✅ Mocked | Permission query handling |

WebRTC Leak Protection

Automatically prevents WebRTC from leaking your real IP address. Works even when using proxy!

Canvas Fingerprint Protection

Adds random noise to canvas data to prevent tracking.

WebGL Fingerprint Protection

Spoofs WebGL parameters and adds noise to buffer data:

  • Randomizes vendor/renderer strings
  • Spoofs GPU parameters
  • Adds noise to WebGL buffer data

AudioContext Fingerprint Protection

Adds tiny noise to audio data without affecting audio quality.

Timezone Spoofing

const profile = await profiles.create({
  name: 'US Profile',
  timezone: 'America/New_York', // Browser reports this timezone
});

Navigator Spoofing

Customize browser navigator properties:

const profile = await profiles.create({
  name: 'Custom Profile',
  fingerprint: {
    language: 'en-US',
    platform: 'Win32',
    hardwareConcurrency: 8,
    deviceMemory: 16,
  },
});

Proxy with Authentication

Handles authenticated proxies transparently:

const profile = await profiles.create({
  name: 'With Auth Proxy',
  proxy: {
    type: 'http',
    host: 'proxy.example.com',
    port: 8080,
    username: 'user',       // ✅ Auth handled automatically
    password: 'password',
  },
});

✅ Verified Test Results

Tested on 2026-01-08 with the following fingerprint testing sites:

| Property | Spoofed Value | Verification | |----------|---------------|--------------| | Timezone | America/New_York | ✅ BrowserScan | | Platform | Win32 | ✅ BrowserLeaks | | CPU Cores | 8 | ✅ PixelScan | | Device Memory | 16GB | ✅ PixelScan | | Language | en-US | ✅ All sites | | WebRTC IP | Hidden | ✅ BrowserLeaks | | Webdriver | Hidden | ✅ All sites | | Plugins | 5 | ✅ BrowserLeaks | | Connection API | 4g | ✅ Verified | | Chrome Object | Complete | ✅ Verified | | Chrome.csi | Present | ✅ Verified | | Chrome.loadTimes | Present | ✅ Verified |

Sites tested:

🆚 Comparison

| Feature | browser-profiles | AdsPower | Multilogin | puppeteer-extra | |---------|-----------------|----------|------------|-----------------| | Open Source | ✅ MIT | ❌ Paid | ❌ Paid | ✅ MIT | | Price | FREE | $9-50/mo | $99-199/mo | Free | | Anti-Detect Score | 95% | 100% | 100% | ~80% | | Profile Storage | ✅ | ✅ | ✅ | ❌ | | Proxy Auth | ✅ | ✅ | ✅ | ❌ | | Auto Timezone | ✅ | ✅ | ✅ | ❌ | | WebRTC Protection | ✅ | ✅ | ✅ | ⚠️ Basic | | Canvas Noise | ✅ | ✅ | ✅ | ⚠️ Basic | | TypeScript | ✅ | ❌ | ❌ | ⚠️ Partial | | npm Package | ✅ | ❌ | ❌ | ✅ | | Puppeteer Integration | ✅ | ✅ | ⚠️ | ✅ | | Playwright Integration | ✅ | ❌ | ⚠️ | ✅ | | Cloud Sync | 🔜 Coming | ✅ | ✅ | ❌ | | GUI | ❌ | ✅ | ✅ | ❌ |

Why 95% vs 100%?

AdsPower and Multilogin achieve 100% by using modified Chromium binaries. Our library uses standard Chrome with JS injection, which has a fundamental ~5% detection limitation on advanced sites like BrowserScan.

For most use cases (social media, e-commerce, scraping), 95% is sufficient.

🔐 Security

Data Storage

  • All data stored locally at ~/.aitofy/browser-profiles/
  • Zero telemetry - no data sent to any server
  • Profile configs stored as JSON files
  • Chrome user data (passwords, autofill) encrypted by Chrome itself

Future: E2E Encryption (Coming Soon)

const profiles = new BrowserProfiles({
  encryption: {
    enabled: true,
    masterKey: 'your-secret-key',
  }
});
  • AES-256-GCM encryption for sensitive data
  • Optional cloud sync with end-to-end encryption
  • Only you (or people you share the key with) can decrypt

🆕 v0.2.5: Full Type Support

Native Type Re-exports

Starting from v0.2.5, PuppeteerPage and PlaywrightPage are native types re-exported from puppeteer-core and playwright. You now have access to ALL APIs directly:

import { withPuppeteer, PuppeteerPage } from '@aitofy/browser-profiles';

const { page, close } = await withPuppeteer({ profile: 'my-profile' });

// ✅ Full API access - no workarounds needed!
await page.setRequestInterception(true);
page.on('request', (req) => {
    if (req.resourceType() === 'image') {
        req.abort();
    } else {
        req.continue();
    }
});

const cookies = await page.cookies();
await page.setCookie({ name: 'session', value: '123', domain: '.example.com' });

await close();

For Playwright:

import { withPlaywright, PlaywrightPage } from '@aitofy/browser-profiles';

const { page, close } = await withPlaywright({ profile: 'my-profile' });

// ✅ Full Playwright API!
await page.route('**/*', (route) => {
    if (route.request().resourceType() === 'image') {
        route.abort();
    } else {
        route.continue();
    }
});

await page.reload();
await page.waitForTimeout(1000);

await close();

Exported Types

All commonly used types are re-exported for convenience:

| Puppeteer Types | Playwright Types | |-----------------|------------------| | PuppeteerPage | PlaywrightPage | | PuppeteerBrowser | PlaywrightBrowser | | HTTPRequest | PlaywrightContext | | HTTPResponse | PlaywrightRequest | | Cookie | PlaywrightResponse, Route |

🤝 Contributing

Contributions are welcome! Please read our Contributing Guide.

📄 License

MIT © Aitofy