├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── release-drafter.yml └── workflows │ ├── eb-docs.yml │ └── eb-release.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.cn.md ├── README.md ├── babel.config.js ├── dist └── disable-devtool.0.3.7.min.js ├── package.json ├── pnpm-lock.yaml ├── scripts ├── build │ ├── build.js │ ├── docs │ │ ├── 404.html │ │ ├── build-docs.js │ │ └── index.html │ ├── rollup.config.js │ └── utils.js ├── dev.js ├── dev │ ├── iframe.html │ ├── index.html │ └── index.ts ├── helper │ ├── .npmignore │ └── tool.js ├── local │ └── iframe.html ├── purge-cdn.js ├── push-release.js ├── test │ ├── debug.html │ ├── index.js │ └── testcdn.html ├── todo.md ├── version.en.md └── version.md ├── src ├── detector │ ├── detector.ts │ ├── index.ts │ └── sub-detector │ │ ├── date-to-string.ts │ │ ├── debug-lib.ts │ │ ├── debugger.ts │ │ ├── define-id.ts │ │ ├── func-to-string.ts │ │ ├── performance.ts │ │ ├── reg-to-string.ts │ │ └── size.ts ├── index.ts ├── main.ts ├── plugins │ ├── ignore.ts │ └── script-use.ts ├── type.d.ts ├── utils │ ├── close-window.ts │ ├── config.ts │ ├── enum.ts │ ├── interval.ts │ ├── key-menu.ts │ ├── log.ts │ ├── md5.ts │ ├── open-state.ts │ └── util.ts └── version.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | scripts/dev/*.js 3 | *.html 4 | **/libs/*.js 5 | npm 6 | docs 7 | output 8 | *.min.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-07-25 08:31:19 4 | * @Description: Coding something 5 | */ 6 | module.exports = { 7 | parser: '@typescript-eslint/parser', 8 | plugins: ['@typescript-eslint'], 9 | 'globals': { 10 | 'globalThis': true, 11 | }, 12 | env: { 13 | 'browser': true, 14 | 'es6': true, 15 | 'node': true, 16 | 'commonjs': true, 17 | }, 18 | 'parserOptions': { 19 | 'sourceType': 'module' // ts 中使用 es 模块 20 | }, 21 | 'rules': { 22 | 'no-var': 'error', 23 | // 优先使用 interface 而不是 type 24 | '@typescript-eslint/consistent-type-definitions': [ 25 | 'error', 26 | 'interface' 27 | ], 28 | '@typescript-eslint/no-unused-vars': 'error', // 使用 ts 未使用变量的规则 比如枚举类型在es中会报错 29 | 'no-extend-native': 0, 30 | 'no-new': 0, 31 | 'no-useless-escape': 0, 32 | 'no-useless-constructor': 0, 33 | 'no-trailing-spaces': ['error', {'skipBlankLines': true}], 34 | 'indent': ['error', 2, { 35 | 'SwitchCase': 1 36 | }], 37 | 'space-infix-ops': ['error', {'int32Hint': false}], 38 | 'space-before-function-paren': ['error', { 39 | 'anonymous': 'always', 40 | 'named': 'always', 41 | 'asyncArrow': 'always' 42 | }], 43 | 'semi': ['error', 'always'], 44 | 'comma-dangle': 0, 45 | 'no-console': 0, 46 | 'no-debugger': 0, 47 | 'id-length': 0, 48 | 'eol-last': 0, 49 | 'object-curly-spacing': ['error', 'never'], 50 | 'arrow-spacing': 'error', 51 | 'no-multiple-empty-lines': 'error', 52 | 'spaced-comment': 'error', 53 | 'quotes': ['error', 'single', {'allowTemplateLiterals': true}], 54 | 'no-unreachable': 'error', 55 | 'keyword-spacing': 'error', 56 | 'space-before-blocks': 'error', 57 | 'semi-spacing': 'error', 58 | 'comma-spacing': 'error', 59 | 'key-spacing': 'error', 60 | 'no-undef': 'error', 61 | 'prefer-const': ['error', { 62 | 'destructuring': 'any', 63 | 'ignoreReadBeforeAssign': false 64 | }] 65 | } 66 | }; -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: disable-devtool 6 | ko_fi: theajack 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | template: | 4 | # What's Changed 5 | 6 | ### [Version Log](https://github.com/theajack/disable-devtool/blob/master/scripts/version.en.md) 7 | 8 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 9 | 10 | categories: 11 | # - title: 'Breaking' 12 | # label: 'type: feat' 13 | - title: 'New' 14 | label: 'type: feat' 15 | - title: 'Bug Fixes' 16 | label: 'type: fix' 17 | - title: 'Documentation' 18 | label: 'type: docs' 19 | - title: 'Other changes' 20 | collapse-after: 5 21 | 22 | version-resolver: 23 | major: 24 | labels: 25 | - 'type: feat' 26 | # minor: 27 | # labels: 28 | # - 'type: feat' 29 | patch: 30 | labels: 31 | - 'type: fix' 32 | - 'type: docs' 33 | 34 | exclude-labels: 35 | - 'skip-changelog' 36 | -------------------------------------------------------------------------------- /.github/workflows/eb-docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | push: 4 | tags: 5 | - d*.*.* 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Setup Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '16' 20 | registry-url: https://registry.npmjs.org 21 | 22 | - name: Build docs # build npm 23 | run: npm run build:docs -- latest 24 | 25 | - name: Pages # github pages 26 | uses: JamesIves/github-pages-deploy-action@v4.3.0 27 | with: 28 | branch: gh-pages 29 | folder: docs -------------------------------------------------------------------------------- /.github/workflows/eb-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - v*.*.* 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Setup Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '16' 20 | registry-url: https://registry.npmjs.org 21 | 22 | - name: Determine npm cache directory 23 | id: npm-cache 24 | run: | 25 | echo "::set-output name=dir::$(npm config get cache)" 26 | 27 | - name: Restore npm cache 28 | uses: actions/cache@v3 29 | with: 30 | path: ${{ steps.npm-cache.outputs.dir }} 31 | key: ${{ runner.os }}-node-${{ hashFiles('**/pnpm-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-node- 34 | 35 | - name: Npm Install 36 | run: | 37 | npm install pnpm@7.9.3 -g 38 | pnpm install 39 | 40 | - name: Version 41 | id: version 42 | run: | 43 | tag=${GITHUB_REF/refs\/tags\//} 44 | version=${tag#v} 45 | echo "::set-output name=version::${version}" 46 | 47 | - name: Build # build npm 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 50 | run: | 51 | npm run build -- ${{ steps.version.outputs.version }} 52 | git config --global user.email "1506282385@qq.com" 53 | git config --global user.name "theajack" 54 | git checkout master 55 | git add . 56 | git commit -m "CI: push v${{ steps.version.outputs.version }}" 57 | git push 58 | 59 | - name: Build Docs 60 | run: npm run build:docs -- ${{ steps.version.outputs.version }} 61 | 62 | - name: Pages # github pages 63 | uses: JamesIves/github-pages-deploy-action@v4.3.0 64 | with: 65 | branch: gh-pages 66 | folder: docs 67 | 68 | - name: Release # release 69 | uses: release-drafter/release-drafter@v5 70 | with: 71 | version: ${{ steps.version.outputs.version }} 72 | publish: true 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 75 | 76 | - name: Publish # npm publish 77 | env: 78 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }} 79 | run: | 80 | cd npm 81 | npm publish 82 | 83 | - name: Purge # purge cdn 84 | run: npm run purge -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /output 3 | .pnpm-debug.log 4 | /npm 5 | /docs 6 | bundle.js 7 | bundle.js.map -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | registry=https://registry.npm.taobao.org/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 - present 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.cn.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 |

7 | 8 |

9 | 10 | stars 11 | 12 | 13 | forks 14 | 15 | 16 | version 17 | 18 | 19 | downloads 20 | 21 | 22 | jsdelivr 23 | 24 | issue 25 |

26 |

27 | 28 | author 29 | 30 | 31 | license 32 | 33 | Size 34 | TopLang 35 | 36 | test 37 | visitors 38 | 39 |

40 | 41 |

🚀 一行代码搞定禁用web开发者工具

42 | 43 | **[English](https://github.com/theajack/disable-devtool/blob/master/README.md) | [在线试用](https://theajack.github.io/disable-devtool) | [更新日志](https://github.com/theajack/disable-devtool/blob/scripts/helper/version.md) | [Gitee](https://gitee.com/theajack/disable-devtool) | [留言板](https://theajack.github.io/message-board?app=disable-devtool) | QQ交流群: 720626970** 44 | 45 | ---- 46 | 47 | 开源维护不易,如果您有经济条件的话,可以捐赠给作者一杯咖啡 48 | 49 |

50 | 51 | test 52 | 53 | 54 | test 55 | 56 | 57 | test 58 | 59 |

60 | 61 | ---- 62 | 63 | ## 0. 赞助商 64 | 65 |

66 | 67 | alins 68 | 69 |

Alins - 极致优雅的UI框架

70 |

71 | 72 | ## 1. 快速使用 73 | 74 | ### 1.1 npm 引用 75 | 76 | ``` 77 | npm i disable-devtool 78 | ``` 79 | 80 | ```js 81 | import DisableDevtool from 'disable-devtool'; 82 | 83 | DisableDevtool(); 84 | ``` 85 | 86 | ### 1.2 script属性配置 87 | 88 | ```html 89 | 90 | ``` 91 | 92 | 或者通过版本引用: 93 | 94 | ```html 95 | 96 | 97 | 98 | 99 | ``` 100 | 101 | ### 1.3 误触发问题定位帮助 102 | 103 | ---- 104 | 105 |
106 | 如果您在使用过程中遇到问题,请点开我 107 | 108 | 因为设备、浏览器、运行环境众多,难免会有一些该库无法兼容的场景,此部分用于开发者自查问题,然后细节反馈到 issues,以帮助我们定位和解决bug 109 | 110 | #### 1.3.1 探测器被错误地触发 111 | 112 | 对于部分情况下,如果没有打开控制台但是页面没关闭了或是跳转走了的问题,原因是某个探测器被错误地触发了,请使用以下代码定位是哪个探测器被误触发了: 113 | 114 | ```js 115 | DisableDevtool({ 116 | ondevtoolopen: (type) => { 117 | const info = 'devtool opened!; type =' + type; 118 | alert(info); 119 | // 如果担心阻塞页面 请使用 console.warn(info); 并打开控制台查看 120 | }, 121 | }) 122 | ``` 123 | 124 | 上述代码在使用脚本引用时需要这样使用 125 | 126 | ```html 127 | 128 | 136 | ``` 137 | 138 | #### 1.3.2 探测器没有被触发 139 | 140 | 当通过任意方式打开了devtool,但是页面没有正确的关闭或者跳转时,首先请尝试打印以下内容,看看探测器是否在正常工作 141 | 142 | ```js 143 | console.log(DisableDevtool.isRunning); 144 | ``` 145 | 146 | 如果返回的是true,那么这就是一个不兼容的问题,原因是所有探测器都没有被触发,这种情况是比较棘手的,目前来看没有通用的定位方法 147 | 148 | 请提交一个issue,尽可能详细地提供您使用的浏览器版本、设备型号及版本、运行环境,最好是有截图或者演示地址,我们后续会进行相应的问题排查 149 | 150 |
151 | 152 | ---- 153 | 154 | ## 2.功能 155 | 156 | disable-devtool 可以禁用所有一切可以进入开发者工具的方法,防止通过开发者工具进行的 ‘代码搬运’ 157 | 158 | 该库有以下特性: 159 | 160 | 1. 支持可配置是否禁用右键菜单 161 | 2. 禁用 f12 和 ctrl+shift+i 等快捷键 162 | 3. 支持识别从浏览器菜单栏打开开发者工具并关闭当前页面 163 | 4. 开发者可以绕过禁用 (url参数使用tk配合md5加密) 164 | 5. 多种监测模式,支持几乎所有浏览器(IE,360,qq浏览器,FireFox,Chrome,Edge...) 165 | 6. 高度可配置、使用极简、体积小巧 166 | 7. 支持npm引用和script标签引用(属性配置) 167 | 8. 识别真移动端与浏览器开发者工具设置插件伪造的移动端,为移动端节省性能 168 | 9. 支持识别开发者工具关闭事件 169 | 10. 支持可配置是否禁用选择、复制、剪切、粘贴功能 170 | 11. 支持识别 eruda 和 vconsole 调试工具 171 | 12. 支持挂起和恢复探测器工作 172 | 13. 支持配置ignore属性,用以自定义控制是否启用探测器 173 | 14. 支持配置iframe中所有父页面的开发者工具禁用 174 | 175 | ## 3. 使用 176 | 177 | ### 3.1 npm使用时的配置参数 178 | 179 | 推荐使用这种方式安装使用,使用script脚本可以被代理单独拦截掉从而无法执行 180 | 181 | 安装 disable-devtool 182 | 183 | ``` 184 | npm i disable-devtool 185 | ``` 186 | 187 | ```js 188 | import DisableDevtool from 'disable-devtool'; 189 | 190 | DisableDevtool(options); 191 | ``` 192 | 193 | #### 3.1.1 返回值 194 | 195 | 返回值 DisableDevtool 的返回值为如下类型 196 | 197 | ```ts 198 | interface IDDResult { 199 | success: boolean; // 表示是否正常启用 200 | reason: string; // 未正常启用的原因 201 | } 202 | ``` 203 | 204 | #### 3.1.2 参数 205 | 206 | options中的参数与说明如下: 207 | 208 | ```ts 209 | interface IConfig { 210 | md5?: string; // 绕过禁用的md5值,详情见3.2,默认不启用绕过禁用 211 | url?: string; // 关闭页面失败时的跳转页面,默认值为localhost 212 | tkName?: string; // 绕过禁用时的url参数名称,默认为 ddtk 213 | ondevtoolopen?(type: DetectorType, next: Function): void; // 开发者面板打开的回调,启用时url参数无效,type 为监测模式,详见3.5, next函数是关闭当前窗口 214 | ondevtoolclose?(): void; // 开发者面板关闭的回调 215 | interval?: number; // 定时器的时间间隔 默认200ms 216 | disableMenu?: boolean; // 是否禁用右键菜单 默认为true 217 | stopIntervalTime?: number; // 在移动端时取消监视的等待时长 218 | clearIntervalWhenDevOpenTrigger?: boolean; // 是否在触发之后停止监控 默认为false, 在使用ondevtoolclose时该参数无效 219 | detectors?: Array; // 启用的检测器 检测器详情见 3.5 默认为全部,建议使用全部 220 | clearLog?: boolean; // 是否每次都清除log 221 | disableSelect?: boolean; // 是否禁用选择文本 默认为false 222 | disableCopy?: boolean; // 是否禁用复制 默认为false 223 | disableCut?: boolean; // 是否禁用剪切 默认为false 224 | disablePaste: boolean; // 是否禁用粘贴 默认为false 225 | ignore?: (string|RegExp)[] | null | (()=>boolean); // 某些情况忽略禁用 226 | disableIframeParents?: boolean; // iframe中是否禁用所有父窗口 227 | timeOutUrl?: string; // 关闭页面超时跳转的url,默认为https://theajack.github.io/disable-devtool/404.html?h=${encodeURIComponent(location.host)} 228 | rewriteHTML: string; // 检测到打开之后重写页面 229 | } 230 | 231 | enum DetectorType { 232 | Unknown = -1, 233 | RegToString = 0, // 根据正则检测 234 | DefineId, // 根据dom id检测 235 | Size, // 根据窗口尺寸检测 236 | DateToString, // 根据Date.toString 检测 237 | FuncToString, // 根据Function.toString 检测 238 | Debugger, // 根据断点检测,仅在ios chrome 真机情况下有效 239 | Performance, // 根据log大数据性能检测 240 | DebugLib, // 检测第三方调试工具 eruda 和 vconsole 241 | }; 242 | ``` 243 | 244 | ### 3.2 md5 与 tk 绕过禁用 245 | 246 | 该库中使用 key 与 md5 配合的方式使得开发者可以在线上绕过禁用。 247 | 248 | 流程如下: 249 | 250 | 先指定一个 key a(该值不要记录在代码中),使用 md5 加密得到一个值 b,将b作为 md5 参数传入,开发者在访问 url 的时候只需要带上url参数 ddtk=a,便可以绕过禁用。 251 | 252 | disableDevtool对象暴露了 md5 方法,可供开发者加密时使用: 253 | 254 | ```js 255 | DisableDevtool.md5('xxx'); 256 | ``` 257 | 258 | ### 3.3 script使用属性配置 259 | 260 | ```html 261 | 276 | ``` 277 | 278 | 注: 279 | 280 | 1. 如希望自动禁用,属性配置时必须要带上 `disable-devtool-auto` 属性 281 | 2. 属性配置都是可选的,字段与3.1中一致,区别是将驼峰形式改成横线分割 282 | 3. 该script标签建议放在body最底部 283 | 4. detectors 需要使用空格分割,如 detectors='1 2 3' 284 | 285 | ### 3.4 script不使用属性配置 286 | 287 | ```html 288 | 289 | 294 | ``` 295 | 296 | ### 3.5 监测模式 297 | 298 | Disable-Devtool 有以下几种监测模式, DisableDevtool.DetectorType 为所有的监测模式枚举 299 | 300 | ```ts 301 | enum DetectorType { 302 | Unknown = -1, 303 | RegToString = 0, // 根据正则检测 304 | DefineId, // 根据dom id检测 305 | Size, // 根据窗口尺寸检测 // 0.3.5版本后该探测器默认不启用 306 | DateToString, // 根据Date.toString 检测 307 | FuncToString, // 根据Function.toString 检测 308 | Debugger, // 根据断点检测,仅在ios chrome 真机情况下有效 309 | Performance, // 根据log大数据性能检测 310 | DebugLib, // 检测第三方调试工具 311 | }; 312 | ``` 313 | 314 | ondevtoolopen 事件的回调参数就是被触发的监测模式 315 | 316 | 可以在 ondevtoolopen 里执行业务逻辑,比如做数据上报、用户行为分析等 317 | 318 | ```ts 319 | DisableDevtool({ 320 | ondevtoolopen(type, next){ 321 | alert('Devtool opened with type:' + type); 322 | next(); 323 | } 324 | }); 325 | ``` 326 | 327 | ### 3.6 其他 API 328 | 329 | #### 3.6.1 isRunning 330 | 331 | 用于获取 DisableDevtool 是否正在运行中 (挂起或忽略状态也视为运行中,因为可以动态开启) 332 | 333 | ```js 334 | DisableDevtool.isRunning; 335 | ``` 336 | 337 | #### 3.6.2 isSuspend 338 | 339 | 用于获取或设置 DisableDevtool 是否被挂起 (挂起状态所有的禁用都将暂时失效) 340 | 341 | ```js 342 | DisableDevtool.isSuspend = true; 343 | DisableDevtool.isSuspend = false; 344 | ``` 345 | 346 | #### 3.6.3 config.ignore 347 | 348 | ignore 用于自定义某些忽略的场景 349 | 350 | 1. 传入数组 351 | 352 | 传入数组是支持 字符串和正则表达式,表示匹配链接中是否含有传入的内容,使用如下 353 | 354 | ```js 355 | DisableDevtool({ 356 | ignore: [ 357 | '/user/login', // 当链接中含有该内容时禁用暂时被忽略 358 | /\/user\/[0-9]{6}/, // 当链接匹配该正则时禁用暂时被忽略 359 | ] 360 | }); 361 | ``` 362 | 363 | 2. 传入函数 364 | 365 | 传入函数表示自定义判断条件,返回一个bool类型,使用如下 366 | 367 | ```js 368 | DisableDevtool({ 369 | ignore: () => { 370 | return userType === 'admin'; // 当是管理员时忽略禁用 371 | } 372 | }); 373 | ``` 374 | 375 | #### 3.6.4 version 376 | 377 | 用于获取 DisableDevtool 版本号 378 | 379 | ```js 380 | DisableDevtool.version; 381 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 |

7 | 8 |

9 | 10 | stars 11 | 12 | 13 | forks 14 | 15 | 16 | version 17 | 18 | 19 | downloads 20 | 21 | 22 | jsdelivr 23 | 24 | issue 25 |

26 |

27 | 28 | author 29 | 30 | 31 | license 32 | 33 | Size 34 | TopLang 35 | 36 | test 37 | visitors 38 |

39 | 40 |

🚀 One line of code to disable web developer tools

41 | 42 | **[中文](https://github.com/theajack/disable-devtool/blob/master/README.cn.md) | [Online Trial](https://theajack.github.io/disable-devtool) | [Changelog](https://github.com/theajack/disable-devtool/blob/master/scripts/version.md) | [Gitee](https://gitee.com/theajack/disable-devtool) | [Message Board](https://theajack.github.io/message-board?app=disable-devtool) | QQ Group: 720626970** 43 | 44 | ---- 45 | 46 |

47 | 48 | test 49 | 50 | 51 | test 52 | 53 | 54 | test 55 | 56 |

57 | 58 | ---- 59 | 60 | ## 0. Sponsor 61 | 62 |

63 | 64 | alins 65 | 66 | 67 | alins 68 | 69 | 70 | alins 71 | 72 |

73 | 74 | ## 1. Quick use 75 | 76 | ### 1.1 npm reference 77 | 78 | ``` 79 | npm i disable-devtool 80 | ``` 81 | 82 | ```js 83 | import DisableDevtool from 'disable-devtool'; 84 | 85 | DisableDevtool(); 86 | ``` 87 | 88 | ### 1.2 script attribute configuration 89 | 90 | ```html 91 | 92 | ``` 93 | 94 | Or cite by version: 95 | 96 | ```html 97 | 98 | 99 | 100 | 101 | ``` 102 | 103 | ### 1.3 False trigger problem location help 104 | 105 | ---- 106 | 107 |
108 | If you have problems during use, please click on me 109 | 110 | Because there are many devices, browsers, and operating environments, it is inevitable that there will be some scenarios where the library is incompatible, and this part is used for developers to check the problem by themselves, and then feedback the details to issues to help us locate and solve bugs 111 | 112 | #### 1.3.1 The probe was triggered incorrectly 113 | 114 | In some cases, if the console is not opened but the page does not close or the jump is away, because a probe is triggered by error, use the following code to locate which probe was triggered by mistake: 115 | 116 | ```js 117 | DisableDevtool({ 118 | ondevtoolopen: (type) => { 119 | const info = 'devtool opened!; type =' + type; 120 | alert(info); 121 | // If you are worried about blocking the page, use console.warn(info); and open the console to view 122 | }, 123 | }) 124 | ``` 125 | 126 | The above code needs to be used this when using script references 127 | 128 | ```html 129 | 130 | 138 | ``` 139 | 140 | #### 1.3.2 The probe is not triggered 141 | 142 | When devtool is opened in any way, but the page does not close or jump correctly, first try printing the following to see if the detector is working properly 143 | 144 | ```js 145 | console.log(DisableDevtool.isRunning); 146 | ``` 147 | 148 | If it returns true, then this is an incompatibility problem because none of the probes are triggered, which is tricky, and there is currently no universal way to locate it 149 | 150 | Please submit an issue, as detailed as possible with the browser version, device model and version, operating environment, preferably a screenshot or demo address, we will troubleshoot the corresponding problem later 151 | 152 |
153 | 154 | ---- 155 | 156 | ## 2. Function 157 | 158 | disable-devtool disables all access to the devtools, preventing 'code porting' via the devtools 159 | 160 | The library has the following features: 161 | 162 | 1. Support configurable whether to disable the right-click menu 163 | 2. Disable shortcut keys such as f12 and ctrl+shift+i 164 | 3. Support recognition to open developer tools from browser menu bar and close the current page 165 | 4. Developers can bypass the disable (url parameters are encrypted with tk and md5) 166 | 5. Multiple monitoring modes, support almost all browsers (IE, 360, qq browser, FireFox, Chrome, Edge...) 167 | 6. Highly configurable, minimalist to use, compact 168 | 7. Support npm reference and script tag reference (property configuration) 169 | 8. Identify the real mobile terminal and the browser developer tool to set the plug-in forged mobile terminal to save performance for the mobile terminal 170 | 9. Support for identifying developer tools close events 171 | 10. Support configurable whether to disable selection, copy, cut, paste function 172 | 11. Support to identify eruda and vconsole debugging tools 173 | 12. Support suspending and resuming probe work 174 | 13. Support configuring ignore attributes to customize whether to enable probes 175 | 14. Support for configuring all parent pages in iframes to be disabled 176 | 177 | ## 3. Use 178 | 179 | ### 3.1 Configuration parameters when using npm 180 | 181 | It is recommended to use this method of installation and use, and the script script can be intercepted by the agent separately and cannot be executed 182 | 183 | install disable-devtool 184 | 185 | ``` 186 | npm i disable-devtool 187 | ``` 188 | 189 | ```js 190 | import DisableDevtool from 'disable-devtool'; 191 | 192 | DisableDevtool(options); 193 | ``` 194 | 195 | #### 3.1.1 Return value 196 | 197 | Return value DisableDevtool The return value is of the following type 198 | 199 | ```ts 200 | interface IDDResult { 201 | success: boolean; Indicates whether it is enabled normally 202 | reason: string; The reason why it was not properly enabled 203 | } 204 | ``` 205 | 206 | #### 3.1.2 parameters 207 | 208 | The parameters and descriptions in options are as follows: 209 | 210 | ```ts 211 | declare interface IConfig { 212 | md5?: string; // bypass disabled md5 value, see 3.2 for details, bypass disabled by default 213 | url?: string; // Jump page when closing the page fails, the default value is localhost 214 | tkName?: string; // bypass url parameter name when disabled, default is ddtk 215 | ondevtoolopen?(type: DetectorType, next: Function): void; // The callback for opening the developer panel, the url parameter is invalid when enabled, the type is monitoring mode, see 3.5 for details, the next function is to close the current window 216 | ondevtoolclose?(): void; // callback for developer panel close 217 | interval?: number; // timer interval default 200ms 218 | disableMenu?: boolean; // Whether to disable the right-click menu Default is true 219 | stopIntervalTime?: number; // Waiting time to cancel monitoring on mobile 220 | clearIntervalWhenDevOpenTrigger?: boolean; // Whether to stop monitoring after triggering the default is false, this parameter is invalid when using ondevtoolclose 221 | detectors?: Array; // Enabled detectors See 3.5 for details of detectors. The default is all, it is recommended to use all 222 | clearLog?: boolean; // Whether to clear the log every time 223 | disableSelect?: boolean; // Whether to disable selection text Default is false 224 | disableCopy?: boolean; // Whether to disable copying, default is false 225 | disableCut?: boolean; // Whether to disable cutting, default is false 226 | disablePaste: boolean; // Whether to disable paste, default is false 227 | ignore?: (string| RegExp)[] | null | (()=>boolean); // Some cases ignore the disablement 228 | disableIframeParents?: boolean; // Whether all parent windows are disabled in the iframe 229 | timeOutUrl?: string; // Turn off URLs that page timeouts forward towards, the default is https://theajack.github.io/disable-devtool/404.html?h=${encodeURIComponent(location.host)} 230 | rewriteHTML?: string; // Detecting the rewriting page after opening 231 | } 232 | 233 | enum DetectorType { 234 | Unknown = -1, 235 | RegToString = 0, // Check according to regular 236 | DefineId, // detect based on dom id 237 | Size, // Detect based on window size // After version 0.3.5, this probe is not enabled by default 238 | DateToString, // check against Date.toString 239 | FuncToString, // check according to Function.toString 240 | Debugger, // According to breakpoint detection, it is only valid in the case of ios chrome real machine 241 | Performance, // Performance detection based on log big data 242 | DebugLib, // Detect third-party debugging tools eruda and vconsole 243 | }; 244 | ``` 245 | 246 | ### 3.2 md5 and tk bypass disabled 247 | 248 | The way in which the key is used in conjunction with md5 in this library allows developers to bypass the ban online. 249 | 250 | The process is as follows: 251 | 252 | First specify a key a (the value should not be recorded in the code), use md5 encryption to obtain a value b, and pass in b as the md5 parameter. When accessing the url, the developer only needs to bring the url parameter ddtk=a, then you can Bypass disable. 253 | 254 | The disableDevtool object exposes the md5 method, which can be used by developers when encrypting: 255 | 256 | ```js 257 | DisableDevtool.md5('xxx'); 258 | ``` 259 | 260 | ### 3.3 script uses attribute configuration 261 | 262 | ```html 263 | 278 | ``` 279 | 280 | Note: 281 | 282 | 1. If you want to disable it automatically, you must include the `disable-devtool-auto` property when configuring the property 283 | 2. The attribute configuration is optional, and the fields are the same as in 3.1, the difference is that the hump form is changed to a horizontal line. 284 | 3. The script tag is recommended to be placed at the bottom of the body 285 | 4. Detectors need to be separated by spaces, such as detectors='1 2 3' 286 | 287 | ### 3.4 script does not use attribute configuration 288 | 289 | ```html 290 | 291 | 296 | ``` 297 | 298 | ### 3.5 Monitoring Mode 299 | 300 | Disable-Devtool has the following monitoring modes, DisableDevtool.DetectorType enumerates all monitoring modes 301 | 302 | ```ts 303 | enum DetectorType { 304 | Unknown = -1, 305 | RegToString = 0, // Check according to regular 306 | DefineId, // detect based on dom id 307 | Size, // Detect based on window size 308 | DateToString, // check against Date.toString 309 | FuncToString, // check according to Function.toString 310 | Debugger, // According to breakpoint detection, it is only valid in the case of ios chrome real machine 311 | Performance, // Performance detection based on log big data 312 | DebugLib, // Detect third-party debugging tools 313 | }; 314 | ``` 315 | 316 | The callback parameter of the ondevtoolopen event is the triggered monitoring mode 317 | 318 | You can execute business logic in OndevtoolOpen, such as data reporting, user behavior analysis, etc 319 | 320 | ```ts 321 | DisableDevtool({ 322 | ondevtoolopen(type, next){ 323 | alert('Devtool opened with type:' + type); 324 | next(); 325 | } 326 | }); 327 | ``` 328 | 329 | ### 3.6 Additional APIs 330 | 331 | #### 3.6.1 isRunning 332 | 333 | Used to get whether DisableDevtool is running (the pending or ignore state is also considered running because it can be turned on dynamically) 334 | 335 | ```js 336 | DisableDevtool.isRunning; 337 | ``` 338 | 339 | #### 3.6.2 isSuspend 340 | 341 | Used to get or set whether DisableDevtool is suspended (suspended state, all disabled will be temporarily disabled) 342 | 343 | ```js 344 | DisableDevtool.isSuspend = true; 345 | DisableDevtool.isSuspend = false; 346 | ``` 347 | 348 | #### 3.6.3 config.ignore 349 | 350 | ignore is used to customize certain ignored scenarios 351 | 352 | 1. Pass in the array 353 | 354 | The incoming array is supported by strings and regular expressions that indicate whether the matching link contains the incoming content, using the following 355 | 356 | ```js 357 | DisableDevtool({ 358 | ignore: [ 359 | '/user/login', // Disabled is temporarily ignored when the link contains this content 360 | /\/user\/[0-9]{6}/, // When a link matches that regular, disabling is temporarily ignored 361 | ] 362 | }); 363 | ``` 364 | 365 | 2. Pass in the function 366 | 367 | The passing function represents a custom judgment condition and returns a bool type, as follows 368 | 369 | ```js 370 | DisableDevtool({ 371 | ignore: () => { 372 | return userType === 'admin'; // Disable is ignored when you are an administrator 373 | } 374 | }); 375 | ``` 376 | 377 | #### 3.6.4 version 378 | 379 | Get DisableDevtool version 380 | 381 | ```js 382 | DisableDevtool.version; 383 | ``` 384 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | useBuiltIns: 'entry', 7 | targets: { 8 | esmodules: true, 9 | ie: 11, 10 | }, 11 | }, 12 | ], 13 | '@babel/preset-typescript', 14 | ], 15 | }; 16 | 17 | -------------------------------------------------------------------------------- /dist/disable-devtool.0.3.7.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DisableDevtool=t()}(this,function(){"use strict";function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,i=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,r=!0,u=!1;return{s:function(){i=i.call(e)},n:function(){var e=i.next();return r=e.done,e},e:function(e){u=!0,o=e},f:function(){try{r||null==i.return||i.return()}finally{if(u)throw o}}}}function t(){if(d.url)window.location.href=d.url;else if(d.rewriteHTML)try{document.documentElement.innerHTML=d.rewriteHTML}catch(e){document.documentElement.innerText=d.rewriteHTML}else{try{window.opener=null,window.open("","_self"),window.close(),window.history.back()}catch(e){console.log(e)}setTimeout(function(){window.location.href=d.timeOutUrl||"https://theajack.github.io/disable-devtool/404.html?h=".concat(encodeURIComponent(location.host))},500)}}var d={md5:"",ondevtoolopen:t,ondevtoolclose:null,url:"",timeOutUrl:"",tkName:"ddtk",interval:500,disableMenu:!0,stopIntervalTime:5e3,clearIntervalWhenDevOpenTrigger:!1,detectors:[0,1,3,4,5,6,7],clearLog:!0,disableSelect:!1,disableCopy:!1,disableCut:!1,disablePaste:!1,ignore:null,disableIframeParents:!0,seo:!0,rewriteHTML:""},U=["detectors","ondevtoolclose","ignore"];function q(e){var t,n=0>5]|=128<>>9<<4)]=t;for(var n=1732584193,i=-271733879,o=-1732584194,r=271733878,u=0;u>5]|=(e.charCodeAt(i/P)&n)<>2]>>o%4*8+4&15)+n.charAt(t[o>>2]>>o%4*8&15);return i}function x(e,t,n,i,o,r){return C((t=C(C(t,e),C(i,r)))<>>32-o,n)}function E(e,t,n,i,o,r,u){return x(t&n|~t&i,e,t,o,r,u)}function j(e,t,n,i,o,r,u){return x(t&i|n&~i,e,t,o,r,u)}function I(e,t,n,i,o,r,u){return x(t^n^i,e,t,o,r,u)}function L(e,t,n,i,o,r,u){return x(n^(t|~i),e,t,o,r,u)}function C(e,t){var n=(65535&e)+(65535&t);return(e>>16)+(t>>16)+(n>>16)<<16|65535&n}var _=function(){n(t,k);var e=l(t);function t(){return i(this,t),e.call(this,{type:O.RegToString,enabled:p.qqBrowser||p.firefox})}return u(t,[{key:"init",value:function(){var t=this;this.lastTime=0,this.reg=/./,h(this.reg),this.reg.toString=function(){var e;return p.qqBrowser?(e=(new Date).getTime(),t.lastTime&&e-t.lastTime<100?t.onDevToolOpen():t.lastTime=e):p.firefox&&t.onDevToolOpen(),""}}},{key:"detect",value:function(){h(this.reg)}}]),t}(),oe=function(){n(t,k);var e=l(t);function t(){return i(this,t),e.call(this,{type:O.DefineId})}return u(t,[{key:"init",value:function(){var e=this;this.div=document.createElement("div"),this.div.__defineGetter__("id",function(){e.onDevToolOpen()}),Object.defineProperty(this.div,"id",{get:function(){e.onDevToolOpen()}})}},{key:"detect",value:function(){h(this.div)}}]),t}(),re=function(){n(t,k);var e=l(t);function t(){return i(this,t),e.call(this,{type:O.Size,enabled:!p.iframe&&!p.edge})}return u(t,[{key:"init",value:function(){var e=this;this.checkWindowSizeUneven(),window.addEventListener("resize",function(){setTimeout(function(){e.checkWindowSizeUneven()},100)},!0)}},{key:"detect",value:function(){}},{key:"checkWindowSizeUneven",value:function(){var e=function(){if(ue(window.devicePixelRatio))return window.devicePixelRatio;var e=window.screen;return!(ue(e)||!e.deviceXDPI||!e.logicalXDPI)&&e.deviceXDPI/e.logicalXDPI}();if(!1!==e){var t=20010*this.maxPrintTime&&this.onDevToolOpen()}}]),t}(),se=(e(A={},O.RegToString,_),e(A,O.DefineId,oe),e(A,O.Size,re),e(A,O.DateToString,ce),e(A,O.FuncToString,ae),e(A,O.Debugger,le),e(A,O.Performance,fe),e(A,O.DebugLib,Y),A);var R=Object.assign(function(e){function t(){var e=0 2 | 3 | 4 | 5 | 6 | Not allowed 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 147 | 148 | 152 | 153 | 154 | 155 | 158 | 159 | 163 | 164 | 165 | 166 | 170 | 172 | 173 | 174 | 175 | 176 | 179 | 180 | 181 | 182 | 183 | 184 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 196 | 197 | 201 | 202 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
What Are You Looking For
215 |
From <Me>?
220 | 237 | 238 | -------------------------------------------------------------------------------- /scripts/build/docs/build-docs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-28 00:48:05 4 | * @Description: Coding something 5 | */ 6 | const {read, write, mkdirDir, copyFile} = require('../utils'); 7 | 8 | function modIndexHtmlVersion () { 9 | const version = process.argv[2] || 'latest'; 10 | const html = read('@scripts/build/docs/index.html'); 11 | const res = html.match(new RegExp(`https://cdn.jsdelivr.net/npm/disable-devtool@.*/disable-devtool.min.js#use`)); 12 | mkdirDir('@docs'); 13 | if (res) { 14 | copyFile('@.gitignore', '@docs/.gitignore'); 15 | copyFile('@scripts/build/docs/404.html', '@docs/404.html'); 16 | write('@docs/index.html', html.replace(res[0], `https://cdn.jsdelivr.net/npm/disable-devtool@${version}/disable-devtool.min.js#use`)); 17 | } 18 | } 19 | 20 | modIndexHtmlVersion(); -------------------------------------------------------------------------------- /scripts/build/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Disable web developer tools with one line 8 | 25 | 26 | 27 | 28 |

Disable-devtool

29 |

30 |

31 | Now that the devtool on this page has been disabled, use the ?ddtk=dd url parameter to un-disable (ddtk value is configurable) 32 |

33 | 34 |

🚀 Disable web developer tools with one line

35 | 36 |

37 | 38 | stars 39 | 40 | 41 | forks 42 | 43 | 44 | version 45 | 46 | 47 | downloads 48 | 49 | 50 | jsdelivr 51 | 52 | issue 53 |

54 |

55 | 56 | author 57 | 58 | 59 | license 60 | 61 | Size 62 | TopLang 63 | Dependent 64 | test 65 |

66 | 67 |

68 | 69 | Github 70 | | 中文文档 71 | | English Docs 72 | | Version Log 73 | | Message Board 74 | 75 |

76 | 77 | 78 |

79 | Open source maintenance is not easy, if you have the financial means, you can donate the author for a cup of coffee 80 |

81 | 82 |

83 | 84 | test 85 | 86 | 87 | test 88 | 89 | 90 | test 91 | 92 |

93 | 94 |

1. Quick use

95 | 96 | 97 |

1.1 npm reference

98 | 99 |

npm i disable-devtool

100 | 101 |

102 | import disableDevtool from'disable-devtool'; 103 | disableDevtool(); 104 |

105 | 106 |

1.2 script attribute configuration

107 | 108 |

<script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool'></script>

109 | 110 |

Or use cdn with version:

111 | 112 |

<!--Use a specific version--> 113 | <script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool@x.x.x'></script> 114 | <!--Use latest version--> 115 | <script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool@latest'></script>

116 | 117 |
118 | TOOL: 119 | 120 | 121 | 122 |
123 | 124 | 129 | 130 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /scripts/build/rollup.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-08-03 20:40:33 4 | * @Description: Coding something 5 | */ 6 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 7 | import {babel} from '@rollup/plugin-babel'; 8 | import dts from 'rollup-plugin-dts'; 9 | import typescript from 'rollup-plugin-typescript2'; 10 | import yaml from '@rollup/plugin-yaml'; 11 | import commonjs from '@rollup/plugin-commonjs'; 12 | import {uglify} from 'rollup-plugin-uglify'; 13 | import packageInfo from '../../package.json'; 14 | 15 | const { 16 | resolveRootPath, 17 | } = require('./utils'); 18 | 19 | const extensions = ['.ts', '.d.ts', '.js']; 20 | 21 | const inputFile = resolveRootPath('src/index.ts'); 22 | 23 | const config = [ 24 | { 25 | // 编译typescript, 生成 js 文件 26 | input: inputFile, 27 | output: { 28 | file: resolveRootPath('npm/disable-devtool.min.js'), 29 | format: 'umd', 30 | name: 'DisableDevtool', 31 | }, 32 | plugins: [ 33 | uglify(), 34 | commonjs(), 35 | yaml(), 36 | typescript(), 37 | nodeResolve({ 38 | extensions, 39 | }), 40 | babel({ 41 | exclude: 'node_modules/**', 42 | extensions, 43 | }), 44 | ], 45 | // sourceMap: true, 46 | external: packageInfo.dependencies, 47 | }, 48 | { 49 | // 生成 .d.ts 类型声明文件 50 | input: inputFile, 51 | output: { 52 | file: resolveRootPath('npm/index.d.ts'), 53 | format: 'es', 54 | }, 55 | plugins: [dts()], 56 | }, 57 | ]; 58 | 59 | export default config; 60 | 61 | 62 | -------------------------------------------------------------------------------- /scripts/build/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-08-03 20:41:31 4 | * @Description: Coding something 5 | */ 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const childProcess = require('child_process'); 9 | 10 | function resolveRootPath (str) { 11 | return path.resolve(__dirname, `../../${str}`); 12 | } 13 | 14 | function upcaseFirstLetter (str) { 15 | if (typeof str !== 'string' || !str) return ''; 16 | return str[0].toUpperCase() + str.substr(1); 17 | } 18 | 19 | function writeJsonIntoFile (filePath, pkg) { 20 | fs.writeFileSync(transfromFilePath(filePath), JSON.stringify(pkg, null, 4), 'utf8'); 21 | } 22 | 23 | function writeStringIntoFile (filePath, str) { 24 | fs.writeFileSync(transfromFilePath(filePath), str, 'utf8'); 25 | } 26 | 27 | function copyFile (src, dest) { 28 | fs.copyFileSync(transfromFilePath(src), transfromFilePath(dest)); 29 | } 30 | 31 | function transfromFilePath (filePath) { 32 | if (filePath[0] === '@') { 33 | return resolveRootPath(filePath.substr(1)); 34 | } 35 | return filePath; 36 | } 37 | 38 | function mkdirDir (filePath) { 39 | filePath = transfromFilePath(filePath); 40 | if (!fs.existsSync(filePath)) { 41 | console.log('mkdirSync', filePath); 42 | fs.mkdirSync(filePath); 43 | } 44 | } 45 | 46 | function clearDirectory (dirPath) { 47 | dirPath = transfromFilePath(dirPath); 48 | if (!fs.existsSync(dirPath)) return; 49 | clearDirectoryBase(dirPath); 50 | } 51 | 52 | function clearDirectoryBase (dirPath) { 53 | const files = fs.readdirSync(dirPath); 54 | files.forEach((file) => { 55 | const filePath = `${dirPath}/${file}`; 56 | const stat = fs.statSync(filePath); 57 | if (stat.isDirectory()) { 58 | clearDirectoryBase(filePath); 59 | fs.rmdirSync(filePath); 60 | } else { 61 | fs.unlinkSync(filePath); 62 | } 63 | }); 64 | }; 65 | 66 | function buildPackageJson (extract = {}) { 67 | const pkg = require(resolveRootPath('package.json')); 68 | 69 | const attrs = [ 70 | 'name', 'version', 'description', 'main', 'unpkg', 71 | 'jsdelivr', 'typings', 'repository', 'keywords', 72 | 'author', 'license', 'bugs', 'homepage', 'dependencies', 73 | 'publishConfig', 74 | ]; 75 | 76 | const npmPkg = {}; 77 | 78 | attrs.forEach(key => { 79 | npmPkg[key] = pkg[key] || ''; 80 | }); 81 | 82 | for (const key in extract) { 83 | npmPkg[key] = extract[key]; 84 | } 85 | 86 | mkdirDir('@npm'); 87 | writeJsonIntoFile('@npm/package.json', npmPkg); 88 | } 89 | 90 | async function exec (cmd) { 91 | return new Promise(resolve => { 92 | childProcess.exec(cmd, function (error, stdout, stderr) { 93 | if (error) { 94 | resolve({success: false, stdout, stderr}); 95 | } else { 96 | resolve({ 97 | success: true, 98 | stdout, 99 | stderr 100 | }); 101 | } 102 | }); 103 | }); 104 | } 105 | 106 | module.exports = { 107 | copyFile, 108 | resolveRootPath, 109 | transfromFilePath, 110 | upcaseFirstLetter, 111 | writeJsonIntoFile, 112 | writeStringIntoFile, 113 | buildPackageJson, 114 | clearDirectory, 115 | mkdirDir, 116 | read: function (file) { 117 | return fs.readFileSync(transfromFilePath(file), 'utf8'); ; 118 | }, 119 | write: function (file, txt) { 120 | fs.writeFileSync(transfromFilePath(file), txt, 'utf8'); 121 | }, 122 | exec, 123 | }; 124 | -------------------------------------------------------------------------------- /scripts/dev.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-08-03 20:37:59 4 | * @Description: Coding something 5 | */ 6 | const {build} = require('esbuild'); 7 | const {resolve} = require('path'); 8 | const {yamlPlugin} = require('esbuild-plugin-yaml'); 9 | const {dtsPlugin} = require('esbuild-plugin-d.ts'); 10 | 11 | const outfile = resolve(__dirname, './dev/bundle.js'); 12 | build({ 13 | entryPoints: [resolve(__dirname, './dev/index.ts')], 14 | outfile, 15 | bundle: true, 16 | sourcemap: true, 17 | format: 'cjs', 18 | globalName: 'LernaDemo', 19 | platform: 'browser', 20 | // plugins: 21 | // format === 'cjs' || pkg.buildOptions?.enableNonBrowserBranches 22 | // ? [nodePolyfills.default()] 23 | // : undefined, 24 | // define: { 25 | // __COMMIT__: '"dev"', 26 | // __VERSION__: `"${pkg.version}"`, 27 | // }, 28 | plugins: [ 29 | yamlPlugin(), 30 | dtsPlugin(), 31 | ], 32 | watch: { 33 | onRebuild (error) { 34 | if (!error) console.log(`rebuilt: ${outfile}`); 35 | }, 36 | }, 37 | }).then(() => { 38 | console.log(`watching: ${outfile}`); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /scripts/dev/iframe.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Document 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/dev/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Document 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Test disable select
20 | 21 | 22 | -------------------------------------------------------------------------------- /scripts/dev/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 21:46:40 4 | * @Description: Coding something 5 | */ 6 | import disableDevtool from '../../src'; 7 | // import disableDevtool from '../../npm'; 8 | 9 | // window.addEventListener('popstate', function (event) { 10 | // event.preventDefault(); 11 | // }); 12 | disableDevtool({ 13 | md5: '0b9e05caf5000360ec1c263335bd83fe', // ddtk 14 | // url: 'https://www.qq.com', 15 | ondevtoolopen: (type, next) => { 16 | // window.location.href = 'https://www.qq.com'; 17 | document.body.innerHTML = 'devtool opened!; type =' + type; 18 | // next(); 19 | // console.log(next); 20 | }, 21 | ondevtoolclose: () => { 22 | // window.location.href = 'https://www.qq.com'; 23 | 24 | document.body.innerHTML = 'devtool closed!;'; 25 | // next(); 26 | // console.log(next); 27 | }, 28 | clearIntervalWhenDevOpenTrigger: true, 29 | interval: 1000, 30 | // tkName: 'ddtk', 31 | // disableMenu: false, 32 | // clearLog: false, 33 | // disableCopy: true, 34 | // disableSelect: true, 35 | // disablePaste: true, 36 | // url: 'https://www.baidu.com' 37 | // detectors: [disableDevtool.DetectorType.DATE_TO_STRING], 38 | 39 | // ignore: ['aaaa', /[xy]+/], 40 | // ignore: () => window.ignore === undefined 41 | }); 42 | document.addEventListener('click', () => { 43 | const div = document.createElement('div'); 44 | div.innerText = `isOpen = ${disableDevtool.isDevToolOpened()}`; 45 | document.body.appendChild(div); 46 | // alert(disableDevtool.isDevToolOpened()); 47 | 48 | 49 | // disableDevtool.isSuspend = !disableDevtool.isSuspend; 50 | // alert(disableDevtool.isSuspend); 51 | }); 52 | 53 | console.log(disableDevtool.version); 54 | console.log(disableDevtool.md5('xx')); 55 | 56 | 57 | // import {log} from '../src/log'; 58 | 59 | // setTimeout(() => { 60 | // log(111); 61 | // debugger; 62 | // }, 3000); 63 | 64 | 65 | // window.log = log; 66 | 67 | // window.addEventListener('keydown', (e) => { 68 | // const keyCode = e.keyCode || e.which; 69 | // if ( keyCode === 123 ) { 70 | // alert('xxxxxx'); 71 | // } 72 | // }); -------------------------------------------------------------------------------- /scripts/helper/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | pnpm-lock.yaml -------------------------------------------------------------------------------- /scripts/helper/tool.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process'); 2 | 3 | async function exec (cmd) { 4 | return new Promise((resolve) => { 5 | childProcess.exec(cmd, (error, stdout, stderr) => { 6 | if (error) { 7 | console.log(error); 8 | resolve({success: false, stdout, stderr}); 9 | } else { 10 | resolve({ 11 | success: true, 12 | stdout, 13 | stderr, 14 | }); 15 | } 16 | }); 17 | }); 18 | } 19 | 20 | module.exports = { 21 | exec, 22 | }; 23 | -------------------------------------------------------------------------------- /scripts/local/iframe.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Document 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/purge-cdn.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-28 19:52:23 4 | * @Description: Coding something 5 | */ 6 | const https = require('https'); 7 | 8 | https.get(`https://purge.jsdelivr.net/npm/disable-devtool/disable-devtool.min.js`, () => { 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /scripts/push-release.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-04-06 09:14:55 4 | * @LastEditors: Please set LastEditors 5 | * @LastEditTime: 2022-09-28 22:02:53 6 | * @FilePath: /cnchar/helper/push-release.js 7 | * @Description: Coding something 8 | */ 9 | 10 | const {exec} = require('./build/utils'); 11 | 12 | async function delTag (tagName) { 13 | await exec(`git tag -d ${tagName}`); 14 | await exec(`git push origin :refs/tags/${tagName}`); 15 | } 16 | 17 | // 版本发布 del 表示需要覆盖上一个版本 一般不需要 18 | // node ./helper/push-tag.js vx.x.x 19 | // node ./helper/push-tag.js vx.x.x no-del 20 | 21 | // npm run release -- vx.x.x 22 | // npm run release -- vx.x.x no-del 23 | 24 | async function main () { 25 | const argv = process.argv.slice(2); 26 | 27 | const tagName = argv[0]; 28 | if (argv[1] !== 'no-del') { 29 | console.log(`Start delete tag ${tagName}...`); 30 | await delTag(tagName); 31 | } 32 | console.log(`Start create tag ${tagName}...`); 33 | await exec(`git tag -m "version ${tagName}" ${tagName} master`); 34 | console.log(`Start push tag ${tagName}...`); 35 | await exec('git push --tags'); 36 | console.log('Finished!'); 37 | } 38 | 39 | main(); -------------------------------------------------------------------------------- /scripts/test/debug.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Document 13 | 14 | 15 |
16 | 23 | 24 | 69 | 70 | -------------------------------------------------------------------------------- /scripts/test/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-28 01:11:39 4 | * @Description: Coding something 5 | */ 6 | import dd from '../../npm'; 7 | 8 | dd({ 9 | 10 | }); -------------------------------------------------------------------------------- /scripts/test/testcdn.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Disable Devtool Demo 13 | 14 | 15 | 16 | 17 | 22 | 23 | 28 | 31 | 32 | -------------------------------------------------------------------------------- /scripts/todo.md: -------------------------------------------------------------------------------- 1 | 9 | 1. 问题:ios真机chrome 浏览器 data-to-string 和 func-to-string两个detector与开发者工具表现一致 10 | 11 | 2. Windows qq浏览器最新版本 mac safari可能会误伤 问题 -------------------------------------------------------------------------------- /scripts/version.en.md: -------------------------------------------------------------------------------- 1 | 6 | # Version Log: 7 | 8 | ## 0.3.7 9 | 10 | 1. Fix the movement of the mobile browser long press and not pop up the copy and other buttons 11 | 2. Add REWRITEHTML configuration 12 | 3. Remove CleardDinterval calls in Closewindow 13 | 14 | ## 0.3.6 15 | 16 | 1. Fix The new version of Google Lighthouse is not released 17 | 2. Fix The mobile debugging tool is allowed after 5s 18 | 3. Set the interval default time to 500ms 19 | 20 | ## 0.3.5 21 | 22 | 1. Fix the problem that the debugging mode in the PC iOS mobile terminal does not work 23 | 2. Remove the default enablement of sizeDetector 24 | 3. Add timeOutUrl to handle the jump that closes the page timeout 25 | 4. Add disableDevtool to repeatedly enable judgment and increase the return value 26 | 5. Optimize the judgment of seobot 27 | 28 | ## 0.3.4 29 | 30 | Fix false detection issue in iOS Edge browser 31 | 32 | ## 0.3.3 33 | 34 | 1. Fixed the issue that an error may be reported in a non-browser environment 35 | 2. Add SEO protection function, and add config.seo parameter to control whether it needs to be enabled, the default is true 36 | 37 | ## 0.3.2 38 | 39 | 1. Add the isRunning property to return whether the detector is running 40 | 2. Add the isSuspend property to set Detector Hang or Detect whether it is suspended 41 | 3. Add the ignore configuration parameter, users matching certain URLs do not enable detectors 42 | 4. Fixed the bug that the parent window can open the console in the iframe, and added the disableIframeParents parameter to control the behavior 43 | 5. Fixed the issue that the default redirect link is invalid 44 | 45 | ## 0.3.1 46 | 47 | 1. Fixed false recognition in ios Chrome 48 | 49 | ## 0.3.0 50 | 51 | 1. ts reconstruction, using esbuild and rollback 52 | 2. Add the disablePaste parameter to disable the paste function 53 | 3. Add third-party debugging tools eruda and vconsole 54 | 55 | ## 0.2.6 56 | 1. Add the performance mode and fix the bug that the new version of chrome is incompatible 57 | 58 | ## 0.2.5 59 | 1. Add disableSelect, disableCopy, disableCut configuration 60 | 61 | ## 0.2.4 62 | 1. Fix the case of accidental injury under iosEdge 63 | 64 | ## 0.2.3 65 | 1. Opening the sidebar under edge will cause accidental injury, so disable sizeDetector under edge 66 | 2. Increase the clearLog parameter to control whether the console is required for each situation, the default is true 67 | 68 | ## 0.2.1 - 0.2.2 69 | 1. Add ondevtoolclose configuration 70 | 2. Add isDevToolOpened api 71 | 3. Fix the accidental injury problem of ios mobile chrome 72 | 4. Added a debug page 73 | 74 | ## 0.1.12 75 | 1. Add the second parameter next of config.ondevtoolopen 76 | 2. Add shortcut key operations for prohibiting review elements and saving webpages 77 | 3. Refactor the code 78 | 4. Fix the problem that ie cannot cache the methods on the console 79 | 80 | ## 0.1.11 81 | 1. Fix the accidental injury caused by the third-party hack console.log method 82 | 83 | ## 0.1.10 84 | 1. Fix the problem that sizeDetector accidentally hurts in browser zoom mode 85 | 86 | ## 0.1.9 87 | 1. Fix the bug of accidental injury in IFrame 88 | 89 | ## 0.1.8 90 | 1. Disable macos option+commond+i 91 | 2. Delete some debug code and useless code 92 | 3. Modify the event model 93 | 94 | ## 0.1.6 - 0.1.7 95 | 1. Add DateToString monitoring type 96 | 2. Add FuncToString monitoring type 97 | 3. Add detectors configuration 98 | 4. Fix ios 15 accidental injury problem 99 | 100 | ## 0.1.5 101 | 1. Remove the log-time monitoring type (because of inaccuracy) 102 | 103 | ## 0.1.4 104 | 1. Add detector, add multiple monitoring modes 105 | 2. Use logTime mode to get the bottom line, compatible with mac, linux 106 | 3. Add clearIntervalWhenDevOpenTrigger parameter 107 | 4. ondevtoolopen adds monitoring mode callback parameter 108 | 109 | ## 0.1.3 110 | 1. Fix the bug that disableMenu parameter is invalid 111 | 112 | ## 0.1.2 113 | 1. Add the judgment of the document to adapt to the server-side rendering npm call 114 | 115 | ## 0.1.1 116 | 1. Increase the delay of jumping to the default page after history.back 117 | 2. Optimize ondeltoolopen logic 118 | 119 | ## 0.1.0 120 | 1. Fix the invalid problem under firefox and qq browsers 121 | 2. Enable disableMenu configuration 122 | 3. Remove internal debug logic 123 | 4. Add the default 404 page to jump to 124 | 125 | ## 0.0.6 126 | 1. For tag attribute configuration, remove the id='disable-devtool' condition and use the disable-devtool-auto attribute 127 | 2. Modify the readme 128 | 129 | ## 0.0.5 130 | 1. Optimize onDevToolOpen event trigger logic 131 | 132 | ## 0.0.4 133 | 1. Modify the webpack packaging configuration 134 | 135 | ## 0.0.3 136 | 1. Solve the problem that native methods such as alert will affect the debug timing. 137 | 2. Solve the page and the background will affect the debug timing. 138 | 3. Compatible with ie, the disableMenu parameter is invalid under ie, because the right button under ie will block the main process and cannot monitor 139 | 4. Increase config.stopIntervalTime to indicate the waiting time for canceling monitoring on the mobile terminal 140 | 5. Optimize the logic for judging the opening of developer tools 141 | 142 | ## 0.0.2 143 | 1. Solve the bug of invalid cdn file 144 | 145 | ## 0.0.1 146 | 1. Support configurable whether to disable the right-click menu 147 | 2. Disable f12 and ctrl+shift+i shortcuts 148 | 3. Support recognition to open developer tools from browser menu bar and close the current page 149 | 4. Developers can bypass the disable (url parameters are encrypted with tk and md5) 150 | 5. Support almost all browsers 151 | 6. Highly configurable 152 | 7. Minimal use and small size (only 7kb) 153 | 8. Support npm reference and script tag reference (property configuration) -------------------------------------------------------------------------------- /scripts/version.md: -------------------------------------------------------------------------------- 1 | 6 | # Version Log: 7 | 8 | ## 0.3.8 9 | 10 | 1. 修复nodejs中引入报错的问题(SSR) 11 | 12 | ## 0.3.7 13 | 14 | 1. 修复移动端浏览器长按不弹出复制等按钮 15 | 2. 增加 rewriteHTML 配置 16 | 3. 移除 closeWindow 中的 clearDDInterval 调用 17 | 18 | ## 0.3.6 19 | 20 | 1. fix google lighthouse 新版本没有放行 21 | 2. fix 移动端调试工具5s之后被放行 22 | 3. 将interval默认时间设置为500ms 23 | 24 | ## 0.3.5 25 | 26 | 1. 修复pc端ios移动端中调试模式不起作用的问题 27 | 2. 去除sizeDetector的默认启用 28 | 3. 增加timeOutUrl,用来处理关闭页面超时的跳转 29 | 4. 增加disableDevtool重复启用判断,增加返回值 30 | 5. 优化seobot的判断 31 | 32 | ## 0.3.4 33 | 34 | 1. 修复ios edge浏览器中的误检测问题 35 | 36 | ## 0.3.3 37 | 38 | 1. 修复在非浏览器环境引入可能报错的问题 39 | 2. 增加seo保护功能,并增加 config.seo 参数,用来控制是否需要开启,默认为true 40 | 41 | ## 0.3.2 42 | 43 | 1. 增加 isRunning 属性,返回检测器是否正在运行 44 | 2. 增加 isSuspend 属性,设置检测器挂起 或 检测是否被挂起 45 | 3. 增加 ignore 配置参数,用户匹配某些url不启用检测器 46 | 4. 修复iframe中,父窗口可以打开控制台的bug,并新增disableIframeParents参数控制该行为 47 | 5. 修复默认跳转链接失效的问题 48 | 49 | ## 0.3.1 50 | 51 | 1. 修复ios chrome中的误识别 52 | 53 | ## 0.3.0 54 | 55 | 1. ts重构,使用esbuild与rollup 56 | 2. 增加 disablePaste 参数,禁用粘贴功能 57 | 3. 增加 检测第三方调试工具 eruda和vconsole 58 | 59 | ## 0.2.6 60 | 1. 增加performance模式,修复新版本chrome不兼容的bug 61 | 62 | ## 0.2.5 63 | 1. 增加disableSelect, disableCopy, disableCut配置 64 | 65 | ## 0.2.4 66 | 1. 修复iosEdge下会误伤的情况 67 | 68 | ## 0.2.3 69 | 1. edge 下打开侧边栏会误伤,所以禁用edge下的sizeDetector 70 | 2. 增加clearLog参数,控制是否需要每次情况控制台,默认为true 71 | 72 | ## 0.2.1 - 0.2.2 73 | 1. 增加 ondevtoolclose 配置 74 | 2. 增加 isDevToolOpened api 75 | 3. 修复 ios mobile chrome 的误伤问题 76 | 4. 增加了一个debug页面 77 | 78 | ## 0.1.12 79 | 1. 增加 config.ondevtoolopen 第二个参数 next 80 | 2. 增加 禁止审查元素和保存网页的快捷键操作 81 | 3. 重构代码 82 | 4. 修复ie不能缓存console上的方法导致的问题 83 | 84 | ## 0.1.11 85 | 1. 修复第三方 hack console.log方法 导致的误伤 86 | 87 | ## 0.1.10 88 | 1. 修复sizeDetector在浏览器缩放模式下误伤的问题 89 | 90 | ## 0.1.9 91 | 1. 修复IFrame中误伤的bug 92 | 93 | ## 0.1.8 94 | 1. 禁用 macos option+commond+i 95 | 2. 删除部分调试代码与无用代码 96 | 3. 修改事件模型 97 | 98 | ## 0.1.6 - 0.1.7 99 | 1. 增加 DateToString 监测类型 100 | 2. 增加 FuncToString 监测类型 101 | 3. 增加 detectors 配置 102 | 4. 修复ios 15误伤问题 103 | 104 | ## 0.1.5 105 | 1. 去掉log-time监测类型(因为不准确) 106 | 107 | ## 0.1.4 108 | 1. 增加 detector,增加多种监测模式 109 | 2. 使用logTime模式兜底,兼容mac,linux 110 | 3. 增加 clearIntervalWhenDevOpenTrigger 参数 111 | 4. ondevtoolopen 增加 监测模式 回调参数 112 | 113 | ## 0.1.3 114 | 1. 修复 disableMenu 参数无效的bug 115 | 116 | ## 0.1.2 117 | 1. 加上document的判断 以适应服务端渲染 npm 调用 118 | 119 | ## 0.1.1 120 | 1. 增加history.back 之后跳转默认页的延迟 121 | 2. 优化ondeltoolopen 逻辑 122 | 123 | ## 0.1.0 124 | 1. 修复firefox和qq浏览器下无效的问题 125 | 2. 启用 disableMenu 配置 126 | 3. 去除内部debug逻辑 127 | 4. 增加默认跳转的404页面 128 | 129 | ## 0.0.6 130 | 1. 对于标签属性配置,移除id='disable-devtool' 条件,使用 disable-devtool-auto属性 131 | 2. 修改readme 132 | 133 | ## 0.0.5 134 | 1. 优化onDevToolOpen事件触发逻辑 135 | 136 | ## 0.0.4 137 | 1. 修改 webpack 打包配置 138 | 139 | ## 0.0.3 140 | 1. 解决alert等原生方法会影响debug计时导致 141 | 2. 解决页面且后台会影响debug计时导致 142 | 3. 兼容ie,disableMenu参数在ie下无效,因为ie下右键会阻塞主进程,且无法监听 143 | 4. 增加config.stopIntervalTime 表示在移动端时取消监视的等待时长 144 | 5. 优化判断开发者工具打开的逻辑 145 | 146 | ## 0.0.2 147 | 1. 解决cdn文件无效的bug 148 | 149 | ## 0.0.1 150 | 1. 支持可配置是否禁用右键菜单 151 | 2. 禁用 f12 和 ctrl+shift+i 快捷键 152 | 3. 支持识别从浏览器菜单栏打开开发者工具并关闭当前页面 153 | 4. 开发者可以绕过禁用 (url参数使用tk配合md5加密) 154 | 5. 支持几乎所有浏览器 155 | 6. 高度可配置 156 | 7. 使用极简、体积小巧 (仅7kb) 157 | 8. 支持npm引用和script标签引用(属性配置) -------------------------------------------------------------------------------- /src/detector/detector.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 22:10:25 4 | * @Description: Coding something 5 | */ 6 | import {closeWindow} from 'src/utils/close-window'; 7 | import {config} from 'src/utils/config'; 8 | import {DetectorType} from 'src/utils/enum'; 9 | import {clearDDInterval, clearDDTimeout, registInterval} from 'src/utils/interval'; 10 | import {markDevToolOpenState} from 'src/utils/open-state'; 11 | 12 | export interface IDetectorConfig { 13 | type: DetectorType; 14 | enabled?: boolean; 15 | } 16 | 17 | export abstract class Detector { 18 | type: DetectorType = DetectorType.Unknown; 19 | enabled: boolean = true; 20 | 21 | constructor ({ 22 | type, enabled = true 23 | }: IDetectorConfig) { 24 | this.type = type; 25 | this.enabled = enabled; 26 | if (this.enabled) { 27 | registInterval(this); 28 | this.init(); 29 | } 30 | } 31 | 32 | onDevToolOpen () { 33 | console.warn(`You don't have permission to use DEVTOOL!【type = ${this.type}】`); 34 | if (config.clearIntervalWhenDevOpenTrigger) { 35 | clearDDInterval(); 36 | } 37 | clearDDTimeout(); 38 | config.ondevtoolopen(this.type, closeWindow); 39 | markDevToolOpenState(this.type); 40 | } 41 | 42 | init () {}; 43 | abstract detect (time: number): void; 44 | } -------------------------------------------------------------------------------- /src/detector/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: theajack 3 | * @Date: 2021-07-24 23:16:34 4 | * @LastEditor: theajack 5 | * @LastEditTime: 2022-09-28 21:15:17 6 | * @Description: Coding something 7 | */ 8 | 9 | import {config} from '../utils/config'; 10 | import RegToStringDetector from './sub-detector/reg-to-string'; 11 | import DefineIdDetector from './sub-detector/define-id'; 12 | import SizeDetector from './sub-detector/size'; 13 | import DateToStringDetector from './sub-detector/date-to-string'; 14 | import FuncToStringDetector from './sub-detector/func-to-string'; 15 | import DebuggerDetector from './sub-detector/debugger'; 16 | import PerformanceDetector from './sub-detector/performance'; 17 | import DebugLibDetector from './sub-detector/debug-lib'; 18 | 19 | import {DetectorType} from '../utils/enum'; 20 | 21 | const Detectors = { 22 | [DetectorType.RegToString]: RegToStringDetector, 23 | [DetectorType.DefineId]: DefineIdDetector, 24 | [DetectorType.Size]: SizeDetector, 25 | [DetectorType.DateToString]: DateToStringDetector, 26 | [DetectorType.FuncToString]: FuncToStringDetector, 27 | [DetectorType.Debugger]: DebuggerDetector, 28 | [DetectorType.Performance]: PerformanceDetector, 29 | [DetectorType.DebugLib]: DebugLibDetector, 30 | }; 31 | 32 | export function initDetectors () { 33 | const typeArray = config.detectors === 'all' ? 34 | Object.keys(Detectors) : config.detectors; 35 | 36 | typeArray.forEach(type => { 37 | const DetectorClass = Detectors[type as Exclude]; 38 | new DetectorClass(); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/detector/sub-detector/date-to-string.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 23:05:40 4 | * @Description: Coding something 5 | */ 6 | 7 | import {clearLog, log} from '../../utils/log'; 8 | import {Detector} from '../detector'; 9 | import {DetectorType} from 'src/utils/enum'; 10 | import {IS} from 'src/utils/util'; 11 | 12 | export default class extends Detector { 13 | 14 | date: Date; 15 | count: 0; 16 | 17 | constructor () { 18 | super({ 19 | type: DetectorType.DateToString, 20 | enabled: !IS.iosChrome && !IS.iosEdge, // iosChrome 中会有bug 21 | }); 22 | } 23 | 24 | init () { 25 | this.count = 0; 26 | this.date = new Date(); 27 | this.date.toString = () => { 28 | this.count ++; 29 | return ''; 30 | }; 31 | } 32 | 33 | detect () { 34 | this.count = 0; 35 | log(this.date); 36 | clearLog(); 37 | if (this.count >= 2) { 38 | this.onDevToolOpen(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/detector/sub-detector/debug-lib.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-28 20:56:01 4 | * @Description: 识别第三方开发者工具 5 | */ 6 | 7 | import {Detector} from '../detector'; 8 | import {DetectorType} from 'src/utils/enum'; 9 | 10 | export default class extends Detector { 11 | 12 | constructor () { 13 | super({ 14 | type: DetectorType.DebugLib 15 | }); 16 | } 17 | 18 | init () {} 19 | 20 | detect () { 21 | if ( 22 | // eruda 检测 23 | (window as any).eruda?._devTools?._isShow === true || 24 | // vconsole 检测 25 | (!!(window as any)._vcOrigConsole && !!window.document.querySelector('#__vconsole.vc-toggle')) 26 | ) { 27 | this.onDevToolOpen(); 28 | } 29 | } 30 | static isUsing () { 31 | return !!(window as any).eruda || !!(window as any)._vcOrigConsole; 32 | } 33 | } -------------------------------------------------------------------------------- /src/detector/sub-detector/debugger.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2021-11-15 22:26:57 4 | /* 5 | * @Author: tackchen 6 | * @Date: 2022-09-27 23:05:40 7 | * @Description: Coding something 8 | */ 9 | 10 | import {Detector} from '../detector'; 11 | import {DetectorType} from 'src/utils/enum'; 12 | import {now, IS} from 'src/utils/util'; 13 | 14 | export default class extends Detector { 15 | 16 | constructor () { 17 | super({ 18 | type: DetectorType.Debugger, 19 | enabled: IS.iosChrome || IS.iosEdge, 20 | }); 21 | } 22 | 23 | detect () { 24 | const date = now(); 25 | (() => {debugger;})(); 26 | if (now() - date > 100) { 27 | this.onDevToolOpen(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/detector/sub-detector/define-id.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2021-11-15 22:26:57 4 | /* 5 | * @Author: tackchen 6 | * @Date: 2022-09-27 23:05:40 7 | * @Description: Coding something 8 | */ 9 | 10 | import {Detector} from '../detector'; 11 | import {DetectorType} from 'src/utils/enum'; 12 | import {log} from 'src/utils/log'; 13 | 14 | export default class extends Detector { 15 | div: HTMLElement; 16 | 17 | constructor () { 18 | super({ 19 | type: DetectorType.DefineId, 20 | }); 21 | } 22 | 23 | init () { 24 | this.div = document.createElement('div'); 25 | (this.div as any).__defineGetter__('id', () => { 26 | this.onDevToolOpen(); 27 | }); 28 | Object.defineProperty(this.div, 'id', { 29 | get: () => { 30 | this.onDevToolOpen(); 31 | }, 32 | }); 33 | } 34 | 35 | detect () { 36 | log(this.div); 37 | } 38 | } -------------------------------------------------------------------------------- /src/detector/sub-detector/func-to-string.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2021-11-15 22:26:57 4 | * @LastEditors: Please set LastEditors 5 | * @LastEditTime: 2023-02-17 22:08:31 6 | * @FilePath: /disable-devtool/src/detector/func-to-string.js 7 | * @Description: Coding something 8 | */ 9 | 10 | import {Detector} from '../detector'; 11 | import {DetectorType} from 'src/utils/enum'; 12 | import {clearLog, log} from 'src/utils/log'; 13 | import {IS} from 'src/utils/util'; 14 | 15 | export default class extends Detector { 16 | count: number; 17 | func: Function; 18 | 19 | constructor () { 20 | super({ 21 | type: DetectorType.FuncToString, 22 | enabled: (!IS.iosChrome && !IS.iosEdge), 23 | }); 24 | } 25 | 26 | init () { 27 | this.count = 0; 28 | this.func = () => {}; 29 | this.func.toString = () => { 30 | this.count ++; 31 | return ''; 32 | }; 33 | } 34 | 35 | detect () { 36 | this.count = 0; 37 | log(this.func); 38 | clearLog(); 39 | if (this.count >= 2) { 40 | this.onDevToolOpen(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/detector/sub-detector/performance.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 20:23:12 4 | * @Description: Coding something 5 | */ 6 | 7 | import {Detector} from '../detector'; 8 | import {DetectorType} from 'src/utils/enum'; 9 | import {clearLog, log, table} from 'src/utils/log'; 10 | import {calculateTime, IS, createLargeObjectArray} from 'src/utils/util'; 11 | 12 | export default class extends Detector { 13 | largeObjectArray: any; 14 | maxPrintTime: number; 15 | 16 | constructor () { 17 | super({ 18 | type: DetectorType.Performance, 19 | enabled: IS.chrome || !IS.mobile 20 | }); 21 | } 22 | 23 | init () { 24 | this.maxPrintTime = 0; 25 | this.largeObjectArray = createLargeObjectArray(); 26 | } 27 | 28 | detect () { 29 | const tablePrintTime = calculateTime(() => {table(this.largeObjectArray);}); 30 | const logPrintTime = calculateTime(() => {log(this.largeObjectArray);}); 31 | this.maxPrintTime = Math.max(this.maxPrintTime, logPrintTime); 32 | 33 | clearLog(); 34 | 35 | if (tablePrintTime === 0 || this.maxPrintTime === 0) return false; 36 | 37 | if (tablePrintTime > this.maxPrintTime * 10) { 38 | this.onDevToolOpen(); 39 | } 40 | } 41 | 42 | }; -------------------------------------------------------------------------------- /src/detector/sub-detector/reg-to-string.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 20:23:12 4 | * @Description: Coding something 5 | */ 6 | 7 | import {Detector} from '../detector'; 8 | import {DetectorType} from 'src/utils/enum'; 9 | import {log} from 'src/utils/log'; 10 | import {IS} from 'src/utils/util'; 11 | 12 | export default class extends Detector { 13 | lastTime: number; 14 | reg: RegExp; 15 | 16 | constructor () { 17 | super({ 18 | type: DetectorType.RegToString, 19 | enabled: (IS.qqBrowser || IS.firefox), 20 | }); 21 | } 22 | 23 | init () { 24 | this.lastTime = 0; 25 | this.reg = /./; 26 | log(this.reg); 27 | this.reg.toString = () => { 28 | if (IS.qqBrowser) { // ! qq浏览器在控制台没有打开的时候也会触发 打开的时候会连续触发两次 使用这个来判断 29 | const time = new Date().getTime(); 30 | if (this.lastTime && time - this.lastTime < 100) { 31 | this.onDevToolOpen(); 32 | } else { 33 | this.lastTime = time; 34 | } 35 | } else if (IS.firefox) { 36 | this.onDevToolOpen(); 37 | } 38 | return ''; 39 | }; 40 | } 41 | 42 | detect () { 43 | log(this.reg); 44 | } 45 | }; -------------------------------------------------------------------------------- /src/detector/sub-detector/size.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 20:23:12 4 | * @Description: Coding something 5 | */ 6 | 7 | import {Detector} from '../detector'; 8 | import {DetectorType} from 'src/utils/enum'; 9 | import {IS} from 'src/utils/util'; 10 | import {clearDevToolOpenState} from 'src/utils/open-state'; 11 | 12 | 13 | export default class extends Detector { 14 | 15 | constructor () { 16 | super({ 17 | type: DetectorType.Size, 18 | enabled: (!IS.iframe && !IS.edge) 19 | }); 20 | } 21 | 22 | init () { 23 | this.checkWindowSizeUneven(); 24 | window.addEventListener('resize', () => { 25 | setTimeout(() => { 26 | this.checkWindowSizeUneven(); 27 | }, 100); 28 | }, true); 29 | } 30 | 31 | detect () { 32 | } 33 | 34 | private checkWindowSizeUneven () { 35 | const screenRatio = countScreenZoomRatio(); 36 | if (screenRatio === false) { // 如果获取不到屏幕缩放尺寸 则不启用sizeDetector 37 | return true; 38 | } 39 | const widthUneven = window.outerWidth - window.innerWidth * screenRatio > 200; // 调大一点防止误伤 40 | const heightUneven = window.outerHeight - window.innerHeight * screenRatio > 300; // 调大一点防止误伤 41 | if (widthUneven || heightUneven) { 42 | this.onDevToolOpen(); 43 | return false; 44 | } 45 | clearDevToolOpenState(this.type); 46 | return true; 47 | } 48 | }; 49 | 50 | function countScreenZoomRatio () { 51 | if (checkExist(window.devicePixelRatio)) { 52 | return window.devicePixelRatio; 53 | } 54 | const screen = window.screen as any; 55 | if (!checkExist(screen)) { 56 | return false; 57 | } 58 | if (screen.deviceXDPI && screen.logicalXDPI) { 59 | return screen.deviceXDPI / screen.logicalXDPI; 60 | } 61 | return false; 62 | }; 63 | 64 | function checkExist (v: any) { 65 | return typeof v !== 'undefined' && v !== null; 66 | } 67 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {disableDevtool} from './main'; 2 | 3 | export default disableDevtool; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 22:49:01 4 | * @Description: Coding something 5 | */ 6 | import './utils/log'; 7 | import {disableKeyAndMenu} from './utils/key-menu'; 8 | import {initInterval} from './utils/interval'; 9 | import {getUrlParam, initIS, IS} from './utils/util'; 10 | import {mergeConfig, config} from './utils/config'; 11 | import md5 from './utils/md5'; 12 | import version from './version'; 13 | import {initDetectors} from './detector/index'; 14 | import {DetectorType} from './utils/enum'; 15 | import {isDevToolOpened} from './utils/open-state'; 16 | import {IConfig, IDisableDevtool} from './type'; 17 | import {initLogs} from './utils/log'; 18 | import {checkScriptUse} from './plugins/script-use'; 19 | 20 | export const disableDevtool: IDisableDevtool = Object.assign(((opts?: Partial) => { 21 | const r = (reason = '') => ({success: !reason, reason}); 22 | if (disableDevtool.isRunning) return r('already running'); 23 | initIS(); // ! 首先初始化env 24 | initLogs(); // 然后初始化log 25 | mergeConfig(opts); 26 | // 被 token 绕过 或者 27 | if (checkTk()) return r('token passed'); 28 | // 开启了保护seo 并且 是seobot 29 | if ((config.seo && IS.seoBot)) return r('seobot'); 30 | disableDevtool.isRunning = true; 31 | initInterval(disableDevtool); 32 | disableKeyAndMenu(disableDevtool); 33 | initDetectors(); 34 | return r(); 35 | }), { 36 | isRunning: false, 37 | isSuspend: false, 38 | md5, 39 | version, 40 | DetectorType, 41 | isDevToolOpened, 42 | }); 43 | 44 | function checkTk () { 45 | if (!config.md5) return false; 46 | // 启用了 md5 47 | const tk = getUrlParam(config.tkName); 48 | return md5(tk) === config.md5; // 命中tk 49 | } 50 | 51 | const options = checkScriptUse(); 52 | if (options) { 53 | disableDevtool(options); 54 | } -------------------------------------------------------------------------------- /src/plugins/ignore.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: chenzhongsheng 3 | * @Date: 2023-02-15 22:36:17 4 | * @Description: Coding something 5 | */ 6 | import {config} from '../utils/config'; 7 | 8 | let lastUrl = ''; 9 | let lastIgnored = false; 10 | 11 | export function isIgnored () { 12 | const {ignore} = config; 13 | if (!ignore) return false; 14 | 15 | if (typeof ignore === 'function') { 16 | return ignore(); 17 | } 18 | 19 | if (ignore.length === 0) return false; 20 | 21 | const href = location.href; 22 | if (lastUrl === href) return lastIgnored; 23 | lastUrl = href; 24 | 25 | let result = false; 26 | 27 | for (const item of ignore) { 28 | if (typeof item === 'string') { 29 | if (href.indexOf(item) !== -1) { 30 | result = true; 31 | break; 32 | } 33 | } else { 34 | if (item.test(href)) { 35 | result = true; 36 | break; 37 | } 38 | } 39 | } 40 | lastIgnored = result; 41 | return result; 42 | } -------------------------------------------------------------------------------- /src/plugins/script-use.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: chenzhongsheng 3 | * @Date: 2023-02-17 22:37:16 4 | * @Description: Coding something 5 | */ 6 | 7 | export function checkScriptUse () { 8 | if ('undefined' === typeof window || !window.document) return null; 9 | const dom = document.querySelector('[disable-devtool-auto]'); 10 | if (!dom) { 11 | return null; 12 | } 13 | 14 | const boolAttrs = [ 15 | 'disable-menu', 'disable-select', 'disable-copy', 16 | 'disable-cut', 'disable-paste', 'clear-log' 17 | ]; 18 | 19 | const intAttrs = ['interval']; 20 | 21 | const json: Record = {}; 22 | [ 23 | 'md5', 'url', 'tk-name', 'detectors', 24 | ...boolAttrs, ...intAttrs 25 | ].forEach(name => { 26 | let value: any = dom.getAttribute(name); 27 | if (value !== null) { 28 | if (intAttrs.indexOf(name) !== -1) { 29 | value = parseInt(value); 30 | } else if (boolAttrs.indexOf(name) !== -1) { 31 | value = value === 'false' ? false : true; 32 | } else if (name === 'detector') { 33 | if (value !== 'all') { 34 | value = value.split(' '); 35 | } 36 | } 37 | json[formatName(name)] = value; 38 | } 39 | }); 40 | return json; 41 | } 42 | 43 | function formatName (name: string) { 44 | if (name.indexOf('-') === -1) { 45 | return name; 46 | } 47 | let flag = false; 48 | return name.split('').map(c => { 49 | if (c === '-') { 50 | flag = true; 51 | return ''; 52 | } 53 | if (flag) { 54 | flag = false; 55 | return c.toUpperCase(); 56 | } 57 | return c; 58 | }).join(''); 59 | } -------------------------------------------------------------------------------- /src/type.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: chenzhongsheng 3 | * @Date: 2023-02-15 22:38:19 4 | * @Description: Coding something 5 | */ 6 | 7 | import {DetectorType} from './utils/enum'; 8 | 9 | export interface IConfig { 10 | md5: string; // 绕过禁用的md5值,详情见3.2,默认不启用绕过禁用 11 | url: string; // 关闭页面失败时的跳转页面,默认值为localhost 12 | timeOutUrl: string; // 关闭页面超时跳转的url 13 | tkName: string; // 绕过禁用时的url参数名称,默认为 ddtk 14 | ondevtoolopen(type: DetectorType, next: Function): void; // 开发者面板打开的回调,启用时url参数无效 15 | ondevtoolclose: Function | null; 16 | interval: number; // 定时器的时间间隔 默认200ms 17 | disableMenu: boolean; // 是否禁用右键菜单 默认为true 18 | stopIntervalTime: number; // 在移动端时取消监视的等待时长 19 | clearIntervalWhenDevOpenTrigger: boolean; // 是否在触发之后停止监控 20 | detectors: DetectorType[] | 'all'; // 启用的监测器 默认为全部 21 | clearLog: boolean; // 是否每次都清除log 22 | disableSelect: boolean; // 是否禁用选择文本 默认为false 23 | disableCopy: boolean; // 是否禁用复制 默认为false 24 | disableCut: boolean; // 是否禁用剪切 默认为false 25 | disablePaste: boolean; // 是否禁用粘贴 默认为false 26 | ignore: (string|RegExp)[] | null | (()=>boolean); // 某些情况忽略禁用 27 | disableIframeParents: boolean; // iframe中是否禁用所有父窗口,默认 true 28 | seo: boolean; // 是否启用对seo进行保护,默认 true 29 | rewriteHTML: string; // 检测到打开之后重写页面 30 | } 31 | 32 | export interface IDisableDevtool { 33 | (opts?: Partial): {success:boolean, reason:string}; 34 | isRunning: boolean; 35 | isSuspend: boolean; 36 | md5: (v: string) => string; 37 | version: string; 38 | DetectorType: typeof DetectorType; 39 | isDevToolOpened: ()=>boolean; 40 | } -------------------------------------------------------------------------------- /src/utils/close-window.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2021-12-24 15:14:06 4 | * @LastEditors: Please set LastEditors 5 | * @LastEditTime: 2023-12-22 09:57:58 6 | * @FilePath: /disable-devtool/src/close-window.js 7 | * @Description: Coding something 8 | */ 9 | import {config} from './config'; 10 | // import {clearDDInterval} from './interval'; 11 | 12 | export function closeWindow () { 13 | // clearDDInterval(); 14 | if (config.url) { 15 | window.location.href = config.url; 16 | } else if (config.rewriteHTML) { 17 | try { 18 | document.documentElement.innerHTML = config.rewriteHTML; 19 | } catch (e) { 20 | // for 'TrustedHTML' assignment 21 | document.documentElement.innerText = config.rewriteHTML; 22 | } 23 | } else { 24 | try { 25 | window.opener = null; 26 | window.open('', '_self'); 27 | // 需要是由js跳转到这个页面才可以关闭这个页面 28 | window.close(); 29 | window.history.back(); 30 | } catch (e) { 31 | console.log(e); 32 | } 33 | setTimeout(() => { 34 | // 否则执行跳转到 url 35 | window.location.href = config.timeOutUrl || `https://theajack.github.io/disable-devtool/404.html?h=${encodeURIComponent(location.host)}`; 36 | }, 500); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 22:16:40 4 | * @Description: Coding something 5 | */ 6 | import {IConfig} from '../type'; 7 | import {closeWindow} from './close-window'; 8 | 9 | export const config: IConfig = { 10 | md5: '', 11 | ondevtoolopen: closeWindow, // ondevtoolopen 优先级高于 url 12 | ondevtoolclose: null, // ondevtoolclose 监听 13 | url: '', 14 | timeOutUrl: '', 15 | tkName: 'ddtk', 16 | interval: 500, 17 | disableMenu: true, // 是否禁用右键菜单 18 | stopIntervalTime: 5000, // 在移动端时取消监视的等待时长 19 | clearIntervalWhenDevOpenTrigger: false, // 是否在触发之后停止监控 20 | detectors: [0, 1, 3, 4, 5, 6, 7], // 'all', ! 默认去掉sizeDetector 因为会误伤 21 | clearLog: true, 22 | disableSelect: false, 23 | disableCopy: false, 24 | disableCut: false, 25 | disablePaste: false, 26 | ignore: null, 27 | disableIframeParents: true, 28 | seo: true, 29 | rewriteHTML: '', 30 | }; 31 | 32 | const MultiTypeKeys = ['detectors', 'ondevtoolclose', 'ignore']; 33 | 34 | export function mergeConfig (opts: Partial = {}) { 35 | 36 | for (const key in config) { 37 | const k = key as keyof IConfig; 38 | if ( 39 | typeof opts[k] !== 'undefined' && 40 | (typeof config[k] === typeof opts[k] || MultiTypeKeys.indexOf(k) !== -1) 41 | ) { 42 | (config as any)[k] = opts[k]; 43 | } 44 | } 45 | checkConfig(); 46 | } 47 | 48 | function checkConfig () { 49 | if ( 50 | typeof config.ondevtoolclose === 'function' && 51 | config.clearIntervalWhenDevOpenTrigger === true 52 | ) { 53 | config.clearIntervalWhenDevOpenTrigger = false; 54 | console.warn('【DISABLE-DEVTOOL】clearIntervalWhenDevOpenTrigger 在使用 ondevtoolclose 时无效'); 55 | } 56 | } -------------------------------------------------------------------------------- /src/utils/enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 22:10:55 4 | * @Description: Coding something 5 | */ 6 | 7 | export enum DetectorType { 8 | Unknown = -1, 9 | RegToString = 0, // 根据正则检测 10 | DefineId, // 根据dom id检测 1 11 | Size, // 根据窗口尺寸检测 2 12 | DateToString, // 根据Date.toString 检测 3 13 | FuncToString, // 根据Function.toString 检测 4 14 | Debugger, // 根据断点检测,仅在ios chrome 真机情况下有效 5 15 | Performance, // 根据log大数据性能检测 6 16 | DebugLib, // 检测第三方调试工具 17 | }; -------------------------------------------------------------------------------- /src/utils/interval.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 22:16:40 4 | * @Description: Coding something 5 | */ 6 | import {IDisableDevtool} from '../type'; 7 | import {Detector} from '../detector/detector'; 8 | import {config} from './config'; 9 | import {clearLog} from './log'; 10 | import {clearDevToolOpenState, checkOnDevClose} from './open-state'; 11 | import {hackAlert, IS, onPageShowHide} from './util'; 12 | import {isIgnored} from 'src/plugins/ignore'; 13 | import DebugLib from 'src/detector/sub-detector/debug-lib'; 14 | 15 | let interval: any = 0, timer: any = 0; 16 | const calls: Detector[] = []; 17 | let time = 0; 18 | 19 | export function initInterval (dd: IDisableDevtool) { 20 | let _pause = false; 21 | const pause = () => {_pause = true;}; 22 | const goon = () => {_pause = false;}; 23 | hackAlert(pause, goon); // 防止 alert等方法触发了debug延迟计算 24 | onPageShowHide(goon, pause); // 防止切后台触发了debug延迟计算 25 | 26 | interval = window.setInterval(() => { 27 | if (dd.isSuspend || _pause || isIgnored()) return; 28 | for (const detector of calls) { 29 | clearDevToolOpenState(detector.type); 30 | detector.detect(time++); 31 | }; 32 | clearLog(); 33 | checkOnDevClose(); 34 | }, config.interval); 35 | // stopIntervalTime 之后判断 如果不是pc去掉定时器interval,为了优化移动端的性能 36 | // 如果控制面板被打开了该定时器timer会被清除 37 | timer = setTimeout(() => { 38 | if (!IS.pc && !DebugLib.isUsing()) { 39 | clearDDInterval(); 40 | } 41 | }, config.stopIntervalTime); 42 | } 43 | 44 | export function registInterval (detector: Detector) { 45 | calls.push(detector); 46 | } 47 | 48 | export function clearDDInterval () { 49 | window.clearInterval(interval); 50 | } 51 | 52 | export function clearDDTimeout () { 53 | window.clearTimeout(timer); 54 | } -------------------------------------------------------------------------------- /src/utils/key-menu.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-09-27 22:16:40 4 | * @Description: Coding something 5 | */ 6 | import {IDisableDevtool} from '../type'; 7 | import {isIgnored} from '../plugins/ignore'; 8 | import {config} from './config'; 9 | import {IS} from './util'; 10 | 11 | let isSuspend = () => false; 12 | 13 | export function disableKeyAndMenu (dd: IDisableDevtool) { 14 | isSuspend = () => dd.isSuspend; 15 | 16 | const top = window.top; 17 | let parent = window.parent; 18 | disableTarget(window); 19 | if (!config.disableIframeParents || !top || !parent || top === window) return; 20 | while (parent !== top) { 21 | disableTarget(parent); 22 | parent = parent.parent; 23 | } 24 | disableTarget(top); 25 | } 26 | 27 | function disableTarget (target: Window) { 28 | // let key1 = 'shiftKey', key2 = 'ctrlKey'; 29 | // 'metaKey'; // mac 的 commond 30 | // 'altKey'; // mac 的 option 31 | const KEY = {J: 74, I: 73, U: 85, S: 83, F12: 123}; 32 | 33 | // 禁用 ctrl + shift + i/j 34 | const isOpenDevToolKey = IS.macos ? 35 | ((e: KeyboardEvent, code: number) => (e.metaKey && e.altKey && (code === KEY.I || code === KEY.J))) : 36 | ((e: KeyboardEvent, code: number) => (e.ctrlKey && e.shiftKey && (code === KEY.I || code === KEY.J))); 37 | 38 | const isViewSourceCodeKey = IS.macos ? 39 | ((e: KeyboardEvent, code: number) => (e.metaKey && e.altKey && code === KEY.U) || (e.metaKey && code === KEY.S)) : 40 | ((e: KeyboardEvent, code: number) => (e.ctrlKey && (code === KEY.S || code === KEY.U))); 41 | 42 | target.addEventListener('keydown', (e) => { 43 | e = e || target.event; 44 | const keyCode = e.keyCode || e.which; 45 | if ( 46 | keyCode === KEY.F12 || // 禁用f12 47 | isOpenDevToolKey(e, keyCode) || // 禁用 ctrl + shift + i 48 | isViewSourceCodeKey(e, keyCode) // 禁用 ctrl + u 和 ctrl + s 查看和保存源码 49 | ) { 50 | return preventEvent(target, e); 51 | } 52 | }, true); 53 | 54 | disableMenu(target); 55 | disableSelect(target); 56 | disableCopy(target); 57 | disableCut(target); 58 | disablePaste(target); 59 | } 60 | 61 | function disableMenu (target: Window) { 62 | if (config.disableMenu) { 63 | target.addEventListener('contextmenu', (e: Event & {pointerType: string}) => { 64 | if (e.pointerType === 'touch') return; 65 | return preventEvent(target, e); 66 | }); 67 | } 68 | } 69 | function disableSelect (target: Window) { 70 | if (config.disableSelect) { 71 | addPreventListener(target, 'selectstart'); 72 | } 73 | } 74 | function disableCopy (target: Window) { 75 | if (config.disableCopy) { 76 | addPreventListener(target, 'copy'); 77 | } 78 | } 79 | function disableCut (target: Window) { 80 | if (config.disableCut) { 81 | addPreventListener(target, 'cut'); 82 | } 83 | } 84 | function disablePaste (target: Window) { 85 | if (config.disablePaste) { 86 | addPreventListener(target, 'paste'); 87 | } 88 | } 89 | function addPreventListener (target: Window, name: string) { 90 | target.addEventListener(name, (e: Event) => { 91 | return preventEvent(target, e); 92 | }); 93 | } 94 | 95 | function preventEvent (target: Window, e: Event) { 96 | if (isIgnored() || isSuspend()) return; 97 | e = e || target.event; 98 | e.returnValue = false; 99 | e.preventDefault(); 100 | return false; 101 | } -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2021-12-24 13:18:23 4 | * @Description: Coding something 5 | */ 6 | 7 | import {config} from './config'; 8 | import {IS} from './util'; 9 | 10 | export let log: (...data: any[]) => void; 11 | export let table: (...data: any[]) => void; 12 | let clear: () => void; 13 | 14 | export function initLogs () { 15 | const console = window.console || { 16 | log: function () { 17 | return; 18 | }, 19 | table: function () { 20 | return; 21 | }, 22 | clear: function () { 23 | return; 24 | } 25 | }; 26 | if (IS.ie) { 27 | // ie 不支持缓存使用 log等方法 28 | log = (...args: any[]) => {return console.log(...args);}; 29 | table = (...args: any[]) => {return console.table(...args);}; 30 | clear = () => {return console.clear();}; 31 | } else { 32 | log = console.log; 33 | table = console.table; 34 | clear = console.clear; 35 | } 36 | } 37 | 38 | export function clearLog () { 39 | if (config.clearLog) 40 | clear(); 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/md5.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Configurable variables. You may need to tweak these to be compatible with 3 | * the server-side, but the defaults work in most cases. 4 | */ 5 | const hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 6 | const chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 7 | 8 | 9 | /* 10 | * These are the functions you'll usually want to call 11 | * They take string arguments and return either hex or base-64 encoded strings 12 | */ 13 | function hex_md5 (s: string) { return binl2hex(core_md5(str2binl(s), s.length * chrsz));} 14 | 15 | /* 16 | * Calculate the MD5 of an array of little-endian words, and a bit length 17 | */ 18 | function core_md5 (x: any, len: number) 19 | { 20 | /* append padding */ 21 | x[len >> 5] |= 0x80 << ((len) % 32); 22 | x[(((len + 64) >>> 9) << 4) + 14] = len; 23 | 24 | 25 | let a = 1732584193; 26 | let b = -271733879; 27 | let c = -1732584194; 28 | let d = 271733878; 29 | 30 | 31 | for (let i = 0; i < x.length; i += 16) 32 | { 33 | const olda = a; 34 | const oldb = b; 35 | const oldc = c; 36 | const oldd = d; 37 | 38 | 39 | a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936); 40 | d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586); 41 | c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819); 42 | b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); 43 | a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897); 44 | d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); 45 | c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); 46 | b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983); 47 | a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); 48 | d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); 49 | c = md5_ff(c, d, a, b, x[i + 10], 17, -42063); 50 | b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); 51 | a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); 52 | d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101); 53 | c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); 54 | b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); 55 | 56 | 57 | a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510); 58 | d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); 59 | c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713); 60 | b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302); 61 | a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691); 62 | d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083); 63 | c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335); 64 | b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848); 65 | a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438); 66 | d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); 67 | c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961); 68 | b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); 69 | a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); 70 | d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784); 71 | c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); 72 | b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); 73 | 74 | 75 | a = md5_hh(a, b, c, d, x[i + 5], 4, -378558); 76 | d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); 77 | c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); 78 | b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556); 79 | a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); 80 | d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); 81 | c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632); 82 | b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); 83 | a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174); 84 | d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222); 85 | c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979); 86 | b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189); 87 | a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487); 88 | d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835); 89 | c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520); 90 | b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651); 91 | 92 | 93 | a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844); 94 | d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); 95 | c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); 96 | b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055); 97 | a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); 98 | d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); 99 | c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523); 100 | b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); 101 | a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); 102 | d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744); 103 | c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); 104 | b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); 105 | a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070); 106 | d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); 107 | c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259); 108 | b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551); 109 | 110 | 111 | a = safe_add(a, olda); 112 | b = safe_add(b, oldb); 113 | c = safe_add(c, oldc); 114 | d = safe_add(d, oldd); 115 | } 116 | return Array(a, b, c, d); 117 | 118 | 119 | } 120 | 121 | 122 | /* 123 | * These functions implement the four basic operations the algorithm uses. 124 | */ 125 | function md5_cmn (q: number, a: any, b: any, x: any, s: any, t: any) 126 | { 127 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b); 128 | } 129 | function md5_ff (a: number, b: number, c: number, d: number, x: any, s: number, t: number) 130 | { 131 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 132 | } 133 | function md5_gg (a: number, b: number, c: number, d: number, x: any, s: number, t: number) 134 | { 135 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 136 | } 137 | function md5_hh (a: number, b: number, c: number, d: number, x: any, s: number, t: number) 138 | { 139 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 140 | } 141 | function md5_ii (a: number, b: number, c: number, d: number, x: any, s: number, t: number) 142 | { 143 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 144 | } 145 | 146 | 147 | /* 148 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 149 | * to work around bugs in some JS interpreters. 150 | */ 151 | function safe_add (x: number, y: number) 152 | { 153 | const lsw = (x & 0xFFFF) + (y & 0xFFFF); 154 | const msw = (x >> 16) + (y >> 16) + (lsw >> 16); 155 | return (msw << 16) | (lsw & 0xFFFF); 156 | } 157 | 158 | 159 | /* 160 | * Bitwise rotate a 32-bit number to the left. 161 | */ 162 | function bit_rol (num: number, cnt: number) 163 | { 164 | return (num << cnt) | (num >>> (32 - cnt)); 165 | } 166 | 167 | 168 | /* 169 | * Convert a string to an array of little-endian words 170 | * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. 171 | */ 172 | function str2binl (str: string) 173 | { 174 | const bin = Array(); 175 | const mask = (1 << chrsz) - 1; 176 | for (let i = 0; i < str.length * chrsz; i += chrsz) 177 | bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32); 178 | return bin; 179 | } 180 | 181 | /* 182 | * Convert an array of little-endian words to a hex string. 183 | */ 184 | function binl2hex (binarray: string | any[]) 185 | { 186 | const hex_tab = hexcase ? '0123456789ABCDEF' : '0123456789abcdef'; 187 | let str = ''; 188 | for (let i = 0; i < binarray.length * 4; i++) 189 | { 190 | str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) + 191 | hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 )) & 0xF); 192 | } 193 | return str; 194 | } 195 | 196 | 197 | export default hex_md5; -------------------------------------------------------------------------------- /src/utils/open-state.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tackchen 3 | * @Date: 2022-01-05 22:27:40 4 | * @LastEditors: Please set LastEditors 5 | * @LastEditTime: 2022-09-28 00:24:14 6 | * @FilePath: /disable-devtool/src/utils/open-state.js 7 | * @Description: Coding something 8 | */ 9 | 10 | import {DetectorType} from 'src/utils/enum'; 11 | import {config} from './config'; 12 | 13 | let isLastStateOpenedBool = false; 14 | 15 | const OpenState: { 16 | [prop in DetectorType]?: boolean; 17 | } = {}; 18 | 19 | export function markDevToolOpenState (type: DetectorType) { 20 | OpenState[type] = true; 21 | } 22 | 23 | export function clearDevToolOpenState (type: DetectorType) { 24 | OpenState[type] = false; 25 | } 26 | 27 | export function isDevToolOpened () { 28 | for (const key in OpenState) { 29 | if (OpenState[key as unknown as DetectorType]) { 30 | isLastStateOpenedBool = true; 31 | return true; 32 | } 33 | } 34 | isLastStateOpenedBool = false; 35 | return false; 36 | } 37 | 38 | export function checkOnDevClose () { 39 | if ( 40 | typeof config.ondevtoolclose === 'function' 41 | ) { 42 | const isLastOpen = isLastStateOpenedBool; // 缓存一下上一次结果 43 | if (!isDevToolOpened() && isLastOpen) { 44 | config.ondevtoolclose(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function now () { 4 | return new Date().getTime(); 5 | } 6 | 7 | export function calculateTime (func: Function) { 8 | const start = now(); 9 | func(); 10 | return now() - start; 11 | } 12 | 13 | export function getUrlParam (name: string) { 14 | let {search} = window.location; 15 | const {hash} = window.location; 16 | // # 在 ? 之前,即 http://localhost/#file?key=value,会导致 search 为空。 17 | if (search === '' && hash !== '') { 18 | // 为 search 补上前缀'?',以便后面的逻辑处理不变。 19 | search = `?${hash.split('?')[1]}`; 20 | } 21 | if (search !== '' && search !== undefined) { 22 | const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); 23 | const r = search.substr(1).match(reg); 24 | if (r != null) { 25 | return unescape(r[2]); 26 | } 27 | } 28 | return ''; 29 | } 30 | 31 | export function onPageShowHide ( 32 | onshow: ()=>void, 33 | onhide: ()=>void, 34 | ) { 35 | const doc = document as any; 36 | let hidden: string, state: string, visibilityChange; 37 | if (typeof doc.hidden !== 'undefined') { 38 | hidden = 'hidden'; 39 | visibilityChange = 'visibilitychange'; 40 | state = 'visibilityState'; 41 | } else if (typeof doc.mozHidden !== 'undefined') { 42 | hidden = 'mozHidden'; 43 | visibilityChange = 'mozvisibilitychange'; 44 | state = 'mozVisibilityState'; 45 | } else if (typeof doc.msHidden !== 'undefined') { 46 | hidden = 'msHidden'; 47 | visibilityChange = 'msvisibilitychange'; 48 | state = 'msVisibilityState'; 49 | } else if (typeof doc.webkitHidden !== 'undefined') { 50 | hidden = 'webkitHidden'; 51 | visibilityChange = 'webkitvisibilitychange'; 52 | state = 'webkitVisibilityState'; 53 | } 54 | const cb = function () { 55 | if (doc[state] === hidden) { 56 | onhide(); 57 | } else { 58 | onshow(); 59 | } 60 | }; 61 | doc.removeEventListener(visibilityChange, cb, false); 62 | doc.addEventListener(visibilityChange, cb, false); 63 | } 64 | 65 | export function hackAlert (before?: ()=>void, after?: ()=>void) { 66 | const _alert = window.alert; 67 | const _confirm = window.confirm; 68 | const _prompt = window.prompt; 69 | const mod = (fn: Function) => { 70 | return (...args: any[]) => { 71 | if (before) {before();} 72 | const result = fn(...args); 73 | if (after) {after();} 74 | return result; 75 | }; 76 | }; 77 | try { 78 | window.alert = mod(_alert); 79 | window.confirm = mod(_confirm); 80 | window.prompt = mod(_prompt); 81 | } catch (e) { 82 | 83 | } 84 | } 85 | 86 | export const IS = { 87 | iframe: false, 88 | pc: false, 89 | qqBrowser: false, 90 | firefox: false, 91 | macos: false, 92 | edge: false, 93 | oldEdge: false, 94 | ie: false, 95 | iosChrome: false, 96 | iosEdge: false, 97 | chrome: false, 98 | seoBot: false, 99 | mobile: false, 100 | }; 101 | 102 | export function initIS () { 103 | const ua = navigator.userAgent.toLowerCase(); 104 | 105 | const has = (name: string) => ua.indexOf(name) !== -1; 106 | 107 | const mobile = isMobile(); 108 | const iframe = !!window.top && window !== window.top; 109 | const pc = !mobile; 110 | const qqBrowser = has('qqbrowser'); 111 | const firefox = has('firefox'); 112 | const macos = has('macintosh'); 113 | const edge = has('edge'); 114 | const oldEdge = edge && !has('chrome'); 115 | const ie = oldEdge || has('trident') || has('msie'); 116 | const iosChrome = has('crios'); 117 | const iosEdge = has('edgios'); 118 | const chrome = has('chrome') || iosChrome; 119 | // google lighthouse ua中有 moto g power 120 | const seoBot = !mobile && /(googlebot|baiduspider|bingbot|applebot|petalbot|yandexbot|bytespider|chrome\-lighthouse|moto g power)/i.test(ua); 121 | 122 | Object.assign(IS, { 123 | iframe, pc, qqBrowser, firefox, macos, edge, oldEdge, 124 | ie, iosChrome, iosEdge, chrome, seoBot, mobile, 125 | }); 126 | } 127 | 128 | function isMobileByUa () { 129 | return /(iphone|ipad|ipod|ios|android)/i.test(navigator.userAgent.toLowerCase()); 130 | } 131 | 132 | function isMobile () { 133 | const {platform, maxTouchPoints} = navigator; 134 | if (typeof maxTouchPoints === 'number') { 135 | return maxTouchPoints > 1; 136 | } 137 | if (typeof platform === 'string') { 138 | const v = platform.toLowerCase(); 139 | if (/(mac|win)/i.test(v)) return false; 140 | else if (/(android|iphone|ipad|ipod|arch)/i.test(v)) return true; 141 | } 142 | return isMobileByUa(); 143 | } 144 | 145 | function createLargeObject () { 146 | const largeObject: Record = {}; 147 | for (let i = 0; i < 500; i++) { 148 | largeObject[`${i}`] = `${i}`; 149 | } 150 | return largeObject; 151 | } 152 | 153 | export function createLargeObjectArray () { 154 | const largeObject = createLargeObject(); 155 | const largeObjectArray = []; 156 | 157 | for (let i = 0; i < 50; i++) { 158 | largeObjectArray.push(largeObject); 159 | } 160 | 161 | return largeObjectArray; 162 | } -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | export default '0.3.8'; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "output", 5 | "sourceMap": false, 6 | "target": "es2016", 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "allowJs": true, 10 | "noUnusedLocals": true, 11 | "strictNullChecks": true, 12 | "noImplicitAny": true, 13 | "noImplicitThis": true, 14 | "experimentalDecorators": true, 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true, 17 | "removeComments": false, 18 | "jsx": "preserve", 19 | "lib": ["esnext", "dom"], 20 | "types": ["node"], 21 | "rootDir": ".", 22 | "noEmit": false, 23 | "paths": {} 24 | }, 25 | "include": [ 26 | ".eslintrc.js", 27 | "babel.config.js", 28 | "scripts/**/*", 29 | "src/**/*" 30 | ], 31 | "exclude": [ 32 | "./node_modules" 33 | ] 34 | } --------------------------------------------------------------------------------