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

@perfectghy/ways

v1.0.2

Published

WAYS — Worker, Are You Sleeping? — detects open Developer Tools with 100% reliability, across all platforms. Most common evasion tricks won't work. (Disabling JS? Sure, but then DevTools are pointless too.)

Downloads

486

Readme

WAYS

Worker, Are You Sleeping?

A cross-platform JavaScript library that detects whether browser Developer Tools are open, using a multi-worker heartbeat mechanism with randomized self-healing countermeasures.

GitHub: https://github.com/PerfectMYGHY/WAYS
npm: https://www.npmjs.com/package/@perfectghy/ways

Table of Contents

中文版

Overview

WAYS is a TypeScript library designed to detect if Developer Tools are open in a browser. Unlike traditional detection methods that rely on single-point debugger statements or timing attacks, WAYS employs a distributed, self-healing worker pool that makes bypassing the detection extremely difficult for an average user.

The library is stable across all major browsers and platforms because it uses only standard Web APIs. No hacks, no non-standard features, no browser-specific exploits.

How It Works

WAYS operates on a simple but effective principle: workers send heartbeat messages to the main thread at regular intervals. If a worker is suspended by a debugger statement, its heartbeat stops, and the main thread detects the interruption.

Architecture

  1. Worker Pool Management

    • The library maintains a dynamic set of Web Workers.
    • Each worker runs a tight loop: setInterval(() => { debugger; postMessage("alive"); }, 100).
    • Under normal conditions, each worker sends a heartbeat every 100 milliseconds.
  2. Heartbeat Monitoring

    • The main thread tracks the last heartbeat time for each worker.
    • A worker is considered "asleep" if its last heartbeat was received more than 200 milliseconds ago.
    • The getIsOpenedDevTools() method returns true if any worker is asleep.
  3. Self-Healing and Randomization

    • Every 3 seconds, the library randomly dismisses a subset of workers and recruits new ones to replace them.
    • This process is protected by a mutex to ensure consistency between dismissal and recruitment.
    • The randomization makes it difficult for an attacker to establish a stable debugging environment.
  4. Graceful Termination

    • When stopDetecting() is called, the detection loop exits.
    • All remaining workers are properly dismissed and terminated, preventing resource leaks.
  5. Mutex-Protected Operations

    • Both the worker reset operation and the detection read operation are guarded by a mutex.
    • This ensures that the worker pool is never read while it is being modified, preventing inconsistent states.

Installation

Using npm:

npm install @perfectghy/ways

Using yarn:

yarn add @perfectghy/ways

Usage

Basic Example

import WAYS from '@perfectghy/ways';

// Create an instance
const detector = new WAYS();

// Set the worker script URL (the library provides ways.worker.js)
await detector.setWorkerAddress('/path/to/ways.worker.js');

// Start detection
detector.startDetecting();

// Check if DevTools is open
const isOpen = await detector.getIsOpenedDevTools();
console.log('DevTools open:', isOpen);

// Stop detection when done
detector.stopDetecting();

HTML Example (Standalone)

A complete HTML test page is provided below. This example demonstrates how to include the library via script tag and use it to detect DevTools in real time.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WAYS Test Page</title>
</head>
<body>
    <script src="dist/ways.js"></script>
    <p>I can detect whether you have opened the console, with a delay no greater than 200ms.</p>
    <p id="result">Detecting...</p>
    <script>
        const ways = new WAYS();
        ways.setWorkerAddress("dist/ways.worker.js")
            .then(() => {
                ways.startDetecting();
                const displayer = document.getElementById("result");
                setInterval(async () => {
                    const opened = await ways.getIsOpenedDevTools();
                    if (opened) {
                        displayer.innerHTML = "Caught you! You opened Developer Tools!";
                    } else {
                        displayer.innerHTML = "You haven't opened Developer Tools yet. Go ahead!";
                    }
                }, 50);
            });
    </script>
</body>
</html>

Explanation of the example:

| Line | Description | |------|-------------| | <script src="dist/ways.js"></script> | Loads the compiled WAYS library from the distribution directory. | | const ways = new WAYS(); | Creates a new detector instance. | | ways.setWorkerAddress("dist/ways.worker.js") | Configures the worker script location (the library provides this file) and verifies it is accessible. | | ways.startDetecting() | Starts the worker pool and the detection loop. | | setInterval(async () => { ... }, 50) | Polls the detection status every 50 milliseconds. | | await ways.getIsOpenedDevTools() | Returns true if any worker is asleep, indicating DevTools is open. | | displayer.innerHTML = ... | Updates the page to reflect the current detection status. |

The example checks for DevTools every 50 milliseconds, providing near-instant feedback. When DevTools is opened, the detection triggers within at most 200 milliseconds due to the worker heartbeat interval.

Worker Script

The library provides the worker script (ways.worker.js) in the distribution package. You only need to reference it via setWorkerAddress(). The worker script implements the heartbeat mechanism:

// ways.worker.js (provided by the library)
setInterval(() => {
    debugger;
    postMessage("alive");
}, 100);

You do not need to create this file yourself; it is included in the npm package.

API Reference

Class: WAYS

Properties

| Property | Type | Default | Description | |----------|------|---------|-------------| | max_workers | number | 10 | Maximum number of workers in the pool | | dismissed_workers | number | 1 | Number of workers to dismiss and recruit in each reset cycle |

Methods

setWorkerAddress(workerAddress: string): Promise<void>

Sets the URL of the worker script. This method validates that the script is accessible before resolving.

  • workerAddress: The absolute or relative URL to the worker JavaScript file (typically ways.worker.js).
  • Returns: A promise that resolves when the worker script is successfully fetched, or rejects if the script cannot be loaded.
startDetecting(): void

Starts the detection loop. This method initializes the worker pool and begins the periodic reset cycle.

  • The method spawns max_workers workers immediately.
  • Every 3 seconds, it randomly dismisses dismissed_workers workers and recruits the same number of new workers.
  • The detection loop continues until stopDetecting() is called.
stopDetecting(): void

Stops the detection loop and dismisses all remaining workers.

  • The detection loop exits gracefully.
  • All workers in the pool are terminated via dismiss().
  • This prevents resource leaks and ensures proper cleanup.
getIsOpenedDevTools(): Promise<boolean>

Checks whether any worker is currently asleep.

  • Returns: true if any worker has not sent a heartbeat for more than 200 milliseconds, false otherwise.
  • This method is mutex-protected and will wait if a reset operation is in progress.
get lastError(): Error | undefined

Returns the last error encountered during detection, if any.

Class: WWorker

Internal class representing an individual worker. Not intended for direct use.

Properties

| Property | Type | Description | |----------|------|-------------| | sleeped | boolean | true if the worker's last heartbeat was more than 200ms ago |

Methods

dismiss(): void

Terminates the worker and sends a dismissal message.

Defense Mechanisms

WAYS employs multiple layers of defense that make bypassing the detection impractical for most users.

Multi-Worker Heartbeat

The library uses up to 10 simultaneous workers, each sending heartbeats independently. Disabling or killing a single worker does not stop the detection; the remaining workers will continue to report.

Random Self-Healing

Every 3 seconds, the worker pool is partially reset. Workers are randomly selected for dismissal and replaced with new workers. This has several effects:

  • An attacker cannot simply identify and disable all workers because new ones are constantly being created.
  • The randomization makes the detection pattern non-deterministic, frustrating attempts to reverse-engineer the behavior.
  • Each new worker starts its own independent heartbeat cycle, introducing fresh debugger breakpoints.

Debugger Proliferation

Each worker contains a debugger statement in its heartbeat loop. When DevTools is open with breakpoints enabled:

  • Every 100 milliseconds, each active worker hits a debugger statement.
  • The user must manually resume execution for each worker, repeatedly.
  • Because workers are reset every 3 seconds, new workers introduce additional debugger breakpoints.

Even with breakpoints disabled, the user must repeatedly click "Continue" to allow the workers to proceed, and the reset cycle reintroduces new breakpoints over time.

Graceful Termination

When stopDetecting() is called:

  • The detection loop exits immediately.
  • All workers are properly dismissed and terminated.
  • This ensures that no resources are leaked and the application can clean up without leaving orphaned workers.

Mutex Protection

Critical sections of the code are protected by a mutex, ensuring:

  • Worker pool modifications (dismissal and recruitment) are atomic.
  • Detection reads are isolated from ongoing modifications.
  • No race conditions can be exploited to read an inconsistent state.

Known Limitations

Breakpoint Disabling

If the user manually disables breakpoints in DevTools, the debugger statements in the workers will not pause execution. However:

  • The user must keep DevTools open and manually resume execution repeatedly.
  • The reset cycle introduces new workers with fresh debugger breakpoints every 3 seconds.
  • Most users will find this sufficiently frustrating to abandon debugging attempts.

JavaScript Disabled

If JavaScript is completely disabled, the library cannot execute at all. However, in that scenario, DevTools is also largely useless for debugging the application, as the page itself will not function.

Performance Impact

The library spawns up to 10 workers, each running a 100ms interval. This has a minimal performance footprint under normal conditions. However, when DevTools is open and breakpoints are enabled, the repeated breakpoints may cause noticeable UI lag as the user clicks "Continue" repeatedly.

License

This project is licensed under the MIT License. See the LICENSE file for details.