devtools-protection.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /**
  2. * 开发者工具保护
  3. * 检测开发者工具是否打开,如果打开则循环执行 debugger 阻止调试
  4. */
  5. // 检测开发者工具是否打开
  6. function detectDevTools(): boolean {
  7. try {
  8. // 方法1: 检测窗口尺寸差异(最可靠的方法)
  9. const widthThreshold = 160; // 开发者工具最小宽度
  10. const heightThreshold = 160; // 开发者工具最小高度
  11. const widthDiff = window.outerWidth - window.innerWidth;
  12. const heightDiff = window.outerHeight - window.innerHeight;
  13. if (widthDiff > widthThreshold || heightDiff > heightThreshold) {
  14. return true;
  15. }
  16. // 方法2: 检测 debugger 执行时间(最准确的方法)
  17. const start = performance.now();
  18. debugger; // 这个 debugger 用于检测,不会被移除
  19. const end = performance.now();
  20. const timeDiff = end - start;
  21. // 如果 debugger 被跳过(开发者工具关闭),时间差会很小(通常 < 1ms)
  22. // 如果 debugger 暂停(开发者工具打开),时间差会很大(通常 > 100ms)
  23. // 降低阈值以提高检测灵敏度
  24. if (timeDiff > 10) {
  25. return true;
  26. }
  27. // 方法3: 检测控制台对象
  28. let devtoolsDetected = false;
  29. const element = document.createElement('div');
  30. Object.defineProperty(element, 'id', {
  31. get: function () {
  32. devtoolsDetected = true;
  33. return '';
  34. }
  35. });
  36. // 使用 console 来触发 getter(仅在开发者工具打开时)
  37. try {
  38. console.log(element);
  39. console.clear();
  40. } catch (e) {
  41. // 忽略错误
  42. }
  43. if (devtoolsDetected) {
  44. return true;
  45. }
  46. // 方法4: 检测控制台是否被重写(开发者工具打开时)
  47. const devtoolsRegex = /./;
  48. // @ts-expect-error - 动态添加属性
  49. devtoolsRegex.toString = function () {
  50. // @ts-expect-error - 动态添加属性
  51. this.opened = true;
  52. };
  53. console.log('%c', devtoolsRegex);
  54. // @ts-expect-error - 检查动态添加的属性
  55. if (devtoolsRegex.opened) {
  56. return true;
  57. }
  58. } catch (e) {
  59. // 如果检测过程中出错,默认返回 false
  60. return false;
  61. }
  62. return false;
  63. }
  64. // 开发者工具保护主函数
  65. export function initDevToolsProtection(): void {
  66. // 可以通过环境变量控制是否启用
  67. // 生产环境默认启用,开发环境可以通过 VITE_ENABLE_ANTI_DEBUG=true 来启用测试
  68. const isProduction = import.meta.env.MODE === 'production';
  69. const enableAntiDebug = import.meta.env.VITE_ENABLE_ANTI_DEBUG === 'true' || isProduction;
  70. if (!enableAntiDebug) {
  71. return;
  72. }
  73. let devToolsOpen = false;
  74. let debuggerInterval: number | null = null;
  75. // 立即执行一次检测
  76. const initialCheck = detectDevTools();
  77. if (initialCheck) {
  78. devToolsOpen = true;
  79. debuggerInterval = window.setInterval(() => {
  80. debugger; // 循环执行 debugger
  81. }, 50); // 更频繁的 debugger,每 50ms 一次
  82. }
  83. // 循环检测开发者工具(更频繁的检测)
  84. const checkInterval = setInterval(() => {
  85. const isOpen = detectDevTools();
  86. if (isOpen && !devToolsOpen) {
  87. // 开发者工具刚打开
  88. devToolsOpen = true;
  89. // 开始循环执行 debugger(更频繁)
  90. if (debuggerInterval === null) {
  91. debuggerInterval = window.setInterval(() => {
  92. debugger; // 循环执行 debugger
  93. }, 50); // 每 50ms 执行一次,更激进
  94. }
  95. } else if (!isOpen && devToolsOpen) {
  96. // 开发者工具关闭了
  97. devToolsOpen = false;
  98. // 停止循环 debugger
  99. if (debuggerInterval !== null) {
  100. clearInterval(debuggerInterval);
  101. debuggerInterval = null;
  102. }
  103. }
  104. }, 500); // 每 500ms 检测一次,更频繁
  105. // 页面卸载时清理
  106. window.addEventListener('beforeunload', () => {
  107. clearInterval(checkInterval);
  108. if (debuggerInterval !== null) {
  109. clearInterval(debuggerInterval);
  110. }
  111. });
  112. // 额外的检测:监听窗口大小变化
  113. let lastWidth = window.innerWidth;
  114. let lastHeight = window.innerHeight;
  115. window.addEventListener('resize', () => {
  116. const currentWidth = window.innerWidth;
  117. const currentHeight = window.innerHeight;
  118. // 如果窗口尺寸变化很大,可能是开发者工具打开/关闭
  119. if (Math.abs(currentWidth - lastWidth) > 200 || Math.abs(currentHeight - lastHeight) > 200) {
  120. // 重新检测
  121. const isOpen = detectDevTools();
  122. if (isOpen && !devToolsOpen) {
  123. devToolsOpen = true;
  124. if (debuggerInterval === null) {
  125. debuggerInterval = window.setInterval(() => {
  126. debugger; // 循环执行 debugger
  127. }, 50); // 更频繁的 debugger
  128. }
  129. }
  130. }
  131. lastWidth = currentWidth;
  132. lastHeight = currentHeight;
  133. });
  134. }