Devtools-detector checks whether the console is opened

usage

import { addListener, launch } from 'devtools-detector';
const view = document.createElement('div');
document.body.appendChild(view);

// 1. add listener
addListener(
  isOpen= >
    (view.innerText = isOpen
      ? 'devtools status: open'
      : 'devtools status: close'));// 2. launch detect
launch();

Copy the code

instantiation

const defaultDetector = new DevtoolsDetector({
  // Checks are performed in the same order as checker
  checkers: [
    checkers.elementIdChecker,
    checkers.regToStringChecker,
    checkers.functionToStringChecker,
    checkers.depRegToStringChecker,
    checkers.debuggerChecker,
  ],
});
Copy the code

Use five methods to check if the console is open

addListener

Queue the callback function

addListener(listener: DevtoolsDetectorListener) {
    this._listeners.push(listener);
  }
Copy the code

launch

Start listening

launch() {
    if (this._detectLoopDelay <= 0) {
      this.setDetectDelay(500);
    }
    if (this._detectLoopStopped) {
      this._detectLoopStopped = false;
      this._detectLoop(); }}Copy the code

_detectLoopDelay: Detection interval

_detectLoopStopped: Whether to stop checking, call launch several times or trigger the timer once

stop

stop() {
    if (!this._detectLoopStopped) {
      this._detectLoopStopped = true;
      clearTimeout(this._timer); }}Copy the code

Clear timer according to _detectLoopStopped

_detectLoop

private async _detectLoop() {
  let isOpen = false;
  let checkerName = ' ';

  for (const checker of this._checkers) {
    // Check browser compatibility
    const isEnable = await checker.isEnable();
    if (isEnable) {
      checkerName = checker.name;
      isOpen = await checker.isOpen();
    }

    // If either checker returns true, devTools is enabled
    if (isOpen) {
      break; }}if(isOpen ! =this._isOpen) {
    this._isOpen = isOpen;
    this._broadcast({
      isOpen,
      checkerName,
    });
  }

  if (this._detectLoopDelay > 0) {
    this._timer = setTimeout(() = > this._detectLoop(), this._detectLoopDelay);
  } else {
    this.stop(); }}Copy the code

Analysis of five inspection methods

checkers: [
  checkers.elementIdChecker,
  checkers.regToStringChecker,
  checkers.functionToStringChecker,
  checkers.depRegToStringChecker,
  checkers.debuggerChecker,
],
Copy the code

elementIdChecker

How it works: Use the get method of the defineProperty hijacking element to check if the console is open. Every time the console opens, the get method fires

const ele = createElement('div');
let isOpen = false;

Object.defineProperty(ele, 'id', {
  get() {
    isOpen = true;
    return elementIdChecker.name;
  },
  configurable: true});// Browser compatibility
match({
  /** Matches all browsers */
  includes: [true].excludes: [isIE, isEdge, isFirefox],
});
Copy the code

regToStringChecker

Principle: Rewrite regular expression toString method implementation. Every time the console opens, the toString method of the regular expression is triggered

const reg = / /;
let isOpen = false;

reg.toString = () = > {
  isOpen = true;
  return regToStringChecker.name;
};
// Browser compatibility
match({
  /** Matches all browsers */
  includes: [true]./** exclude webkit */
  excludes: [isWebkit],
});
Copy the code

functionToStringChecker

Principle: Rewrite function toString method implementation. Every time the console opens, the toString method is triggered


function devtoolsTestFunction() {
  // nothing todo
}

let count = 0;

devtoolsTestFunction.toString = () = > {
  count++;

  return ' ';
};

export const functionToStringChecker: DevtoolsStatusChecker = {
  name: 'function-to-string'.async isOpen(): Promise<boolean> {
    count = 0;

    log(devtoolsTestFunction);
    clear();

    return count === 2;
  },
  async isEnable(): Promise<boolean> {
    return match({
      /** Matches all browsers */
      includes: [true]./** Excludes Firefox and Chrome */ on iPad or iPhone
      excludes: [
        isFirefox,
        // Chrome on iPad or iPhone(isIpad || isIphone) && isChrome, ], }); }};Copy the code

Return count === 2; Instead of return count === 1;

depRegToStringChecker

DepRegToStringChecker and regToStringChecker don’t see the difference

It’s the browser for judgment

debuggerChecker

By opening the console, a debugger breakpoint effect is triggered by a function that executes immediately. Check whether the execution time exceeds 100ms. If the execution time exceeds 100ms, the console is opened

function now() {
  if (performance) {
    return performance.now();
  } else {
    return Date.now(); }}export const debuggerChecker: DevtoolsStatusChecker = {
  name: 'debugger-checker'.async isOpen(): Promise<boolean> {
    const startTime = now();

    // tslint:disable-next-line:no-empty only-arrow-functions
    (function () {}.constructor('debugger') ());return now() - startTime > 100;
  },
  async isEnable(): Promise<boolean> {
    return true; }};Copy the code