├── .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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
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 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | ----
62 |
63 | ## 0. 赞助商
64 |
65 |
66 |
67 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
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 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | ----
59 |
60 | ## 0. Sponsor
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
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 |
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 |
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 |
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 | 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 |
85 |
86 |
87 |
88 |
89 |
90 |
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 | Generate md5 tk
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 | }
--------------------------------------------------------------------------------