├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .release-it.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── rollup.config.mjs ├── src ├── adapt │ ├── index.js │ └── rs │ │ ├── index.js │ │ └── parseTaskTree.js ├── globalVarible.js ├── handle │ ├── batteryHandle.js │ ├── connectionHandle.js │ ├── cookieHandle.js │ ├── dateAndRandomHandle.js │ ├── evalHandle.js │ ├── eventHandle.js │ ├── funcHandle.js │ ├── index.js │ ├── intervalHandle.js │ ├── ovserver.js │ └── timeoutHandle.js ├── index.js ├── tools │ ├── addConstant.js │ ├── cacher.js │ ├── compressText.js │ ├── exit.js │ ├── getNativeProto.js │ ├── gvOper.js │ ├── index.js │ ├── monitor.js │ ├── runtime.js │ ├── setFunc.js │ ├── setObj.js │ └── timeout.js └── utils │ ├── index.js │ └── loopRun.js └── test ├── sdenv.test.js ├── tools.test.js └── utils.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parserOptions": { 3 | "ecmaVersion": 2020 4 | }, 5 | "root": true, 6 | "extends": "airbnb-base", 7 | "plugins": [ 8 | "jest" 9 | ], 10 | "env": { 11 | "jest/globals": true 12 | }, 13 | "globals": { 14 | "window": true 15 | }, 16 | "rules": { 17 | "semi": 0, 18 | "comma-dangle": 0, 19 | "no-param-reassign": 0, 20 | "func-names": 0, 21 | "no-underscore-dangle": 0, 22 | "object-curly-newline": 0, 23 | "no-console": 0, 24 | "no-plusplus": 0, 25 | "no-multi-assign": 0, 26 | "import/prefer-default-export": 0, 27 | "space-before-function-paren": 0, 28 | "new-cap": 0, 29 | "arrow-body-style": 0, 30 | "camelcase": 0, 31 | "consistent-return": 0, 32 | "no-restricted-syntax": 0, 33 | "guard-for-in": 0, 34 | "no-eval": 0, 35 | "no-extend-native": 0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | output/ 4 | npm-debug.log 5 | yarn-error.log 6 | .idea 7 | .vscode 8 | package-lock.json 9 | yarn.lock 10 | .history 11 | *.swp 12 | build/ 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /* 2 | !src/ 3 | !build/ 4 | -------------------------------------------------------------------------------- /.release-it.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | github: { 3 | release: true 4 | }, 5 | git: { 6 | commitMessage: "release: v${version}" 7 | }, 8 | npm: { 9 | publish: false 10 | }, 11 | hooks: { 12 | "after:bump": "echo 更新版本成功" 13 | }, 14 | plugins: { 15 | '@release-it/conventional-changelog': { 16 | preset: 'conventionalcommits', 17 | infile: 'CHANGELOG.md', 18 | sameFile: true, 19 | releaseRules: [ 20 | { type: 'feat', release: 'minor' }, 21 | { type: 'fix', release: 'patch' }, 22 | { type: 'docs', release: 'patch' }, 23 | { type: 'style', release: 'patch' }, 24 | { type: 'refactor', release: 'patch' }, 25 | { type: 'perf', release: 'patch' }, 26 | { type: 'test', release: 'patch' }, 27 | ], 28 | }, 29 | }, 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [1.3.0](https://github.com/pysunday/sdenv-extend/compare/1.2.0...1.3.0) (2024-04-15) 4 | 5 | 6 | ### Features 7 | 8 | * 1. 去除单例创建实例方式;2. 删除无用代码;3. 其它代码优化 ([f70ac4f](https://github.com/pysunday/sdenv-extend/commit/f70ac4f567276937f3af973b650a0eb6144b8e6c)) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * cjs文件版本去除 ([6f51887](https://github.com/pysunday/sdenv-extend/commit/6f51887a7ebcdea0a7693aaef5a6608a67f33dd0)) 14 | * 任务树日志默认关闭 ([f450553](https://github.com/pysunday/sdenv-extend/commit/f450553c1b2126ce5bfb5eb5d62412bc08564390)) 15 | * 命名一致 ([87cccf9](https://github.com/pysunday/sdenv-extend/commit/87cccf9c68d62bb0df5ecd1ae76ab920c9087f4c)) 16 | 17 | ## [1.2.0](https://github.com/pysunday/sdenv-extend/compare/1.1.0...1.2.0) (2024-03-11) 18 | 19 | 20 | ### Features 21 | 22 | * README更新、handle代码优化、打包逻辑优化 ([c4ad635](https://github.com/pysunday/sdenv-extend/commit/c4ad63546d3bc2410182205e61af3c1d35cbb685)) 23 | 24 | ## [1.1.0](https://github.com/pysunday/sdenv-extend/compare/1.0.0...1.1.0) (2024-03-07) 25 | 26 | 27 | ### Features 28 | 29 | * 适配chrome的修改,如定时器、dom监控、循环监控等 ([c34e4c3](https://github.com/pysunday/sdenv-extend/commit/c34e4c3b0ef4637d31bbed65a58c21da3ebd3ccc)) 30 | 31 | ## 1.0.0 (2024-02-26) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * load事件延后与定时器逻辑优化 ([759c442](https://github.com/pysunday/sdenv-extend/commit/759c4422e554ef7134ed9a4e50adddfe751737dc)) 37 | 38 | ## 1.0.1 (2024-01-12) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, pysunday 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](https://badge.fury.io/js/sdenv-extend.svg)](http://badge.fury.io/js/sdenv-extend) 2 | 3 | 用于sdenv补环境框架与真实浏览器加载网页的公共方法与共有处理. 4 | 5 | ## node端 6 | 7 | 1. 安装包:`npm install sdenv-extend` 8 | 2. 导入:`const SdenvExtend = require('sdenv-extend')` 9 | 3. 初始化(需要传入window对象):`window.sdenv = new SdenvExtend({ }, window)` 10 | 11 | ## 浏览器端 12 | 13 | 打包文件下载:`https://github.com/pysunday/sdenv-extend/releases`,文件名为sdenv-extend-iife的js文件即为浏览器端使用文件。 14 | 15 | 1. head内引入:`` 16 | 2. 初始化:`window.sdenv = new SdenvExtend(cfg)` 17 | 3. html页面中使用: 18 | ```javascript 19 | if (!window.sdenv) window.sdenv = new SdenvExtend(); 20 | ``` 21 | 4. 使用extend handle拓展方法,应该在网页第一处javascript执行前后添加,即执行html中javascript代码前的最后一处node执行处或者就在该javascript代码内,建议是在html中的第一处javascript代码内使用,如: 22 | ```javascript 23 | sdenv 24 | .getHandle('battery')('charging_success') 25 | .getHandle('eval')() 26 | ... 27 | .getHandle('func')(); 28 | ``` 29 | 30 | ## API 31 | 32 | ### extend handle拓展方法 33 | 34 | 在SdenvExtend实例化对象后通过链式方法getHandle添加,见上方浏览器端第四条 35 | 36 | #### `.getHandle('battery')(string | object)` 37 | 38 | 作用: 定义机器电量及充电状态,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getBattery) 39 | 40 | params: 41 | * string(预存配置):charging_success(正在充电,电量100%,该项默认)、discharging_success(未充电,电量100%)、charging_ing(正在充电)、discharging_ing(未充电) 42 | * object(自定义配置):如预存配置不满足使用需求则可传入自定义对象,参考浏览器端`navigator.getBattery().then(data => console.log(data))`打印结果 43 | 44 | 默认值: 45 | 46 | ```json 47 | { 48 | onchargingchange: null, 49 | onchargingtimechange: null, 50 | ondischargingtimechange: null, 51 | onlevelchange: null, 52 | charging: true, 53 | chargingTime: 0, 54 | dischargingTime: Infinity, 55 | level: 1, 56 | } 57 | ``` 58 | 59 | #### `.getHandle('connection')(object)` 60 | 61 | 作用:定义网络环境,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/connection) 62 | 63 | params: 64 | * object:参考浏览器端`navigator.connection`返回结果 65 | 66 | 默认值: 67 | 68 | ```json 69 | { 70 | downlink: 6.66, 71 | effectiveType: "4g", 72 | onchange: null, 73 | rtt: 0, 74 | saveData: false, 75 | } 76 | ``` 77 | 78 | #### `.getHandle('cookie')(object)` 79 | 80 | 作用:cookie值监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie) 81 | 82 | params: 83 | * object: 包含日志打印、回调、数据处理的钩子对象,可用属性(参考附录一):getLog、setLog、log、getCb、setCb、cb、parse 84 | 85 | #### `.getHandle('eval')(object)` 86 | 87 | 作用:eval方法调用监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) 88 | 89 | 该拓展应用后会将代码字符串中的debugger去除,如果我们自己想要断点可以使用sdDebugger代替 90 | 91 | params: 92 | * object: 包含日志打印、回调钩子对象,可用属性(参考附录一):log、cb 93 | 94 | eval执行固定值映射: 95 | * `!new function(){eval("this.a=1")}().a` -> false 96 | 97 | #### `.getHandle('func')(object)` 98 | 99 | 作用:Function方法调用监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) 100 | 101 | 该拓展应用后会将代码字符串中的debugger去除,如果我们自己想要断点可以使用sdDebugger代替 102 | 103 | params: 104 | * object: 包含日志打印、回调钩子对象,可用属性(参考附录一):log、cb 105 | 106 | #### `.getHandle('event')(object)` 107 | 108 | 作用:`addEventListener`方法的调用监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) 109 | 110 | params: 111 | * object: 包含日志打印、回调、事件过滤的钩子对象,可用属性(参考附录一):addLog、runLog、log、addCb、runCb、cb、filter 112 | 113 | #### `.getHandle('ovserver')(object)` 114 | 115 | 作用:`MutationObserver`方法的调用监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) 116 | 117 | params: 118 | * object: 包含日志打印、回调、事件过滤的钩子对象,可用属性(参考附录一):newLog、addLog、runLog、log、newCb、addCb、runCb、cb、filter 119 | 120 | #### `.getHandle('timeout')(object)` 121 | 122 | 作用:`setTimeout`方法的调用监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) 123 | 124 | params: 125 | * object: 包含日志打印、回调钩子对象,可用属性(参考附录一):log、cb、filter,除了附录一中记录的这三个属性还有一个特殊的time属性,当time属性不传则使用sdenv-extend内部封装的定时器,当time值为数字则使用自带定时器,且time的优先级大于调用时传入的time值 126 | 127 | #### `.getHandle('interval')(object)` 128 | 129 | 作用:`setInterval`方法的调用监控封装,[跳转MDN文档](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) 130 | 131 | params: 132 | * object: 包含日志打印、回调钩子对象,可用属性(参考附录一):log、cb、filter,除了附录一中记录的这三个属性还有一个特殊的time属性,当time属性不传则使用sdenv-extend内部封装的定时器,当time值为数字则使用自带定时器,且time的优先级大于调用时传入的time值 133 | 134 | #### `.getHandle('dateAndRandom')(object)` 135 | 136 | 作用:用于获取运行时时间值记录及固定时间值与随机数 137 | 138 | params: 139 | * object -> datas(object):指定上下文获取的运行时日期数据 140 | * object -> randomReturn(number):Math.random方法返回的数值 141 | 142 | 其中datas的值为指定上下文运行时的产物,可以在浏览器端运行时在断点的位置执行`sdenv.utils.getDateData(copy)`获取 143 | 144 | ### tools工具方法 145 | 146 | 待补充... 147 | 148 | ## 附录1: extend handle入参对象配置列表 149 | 150 | 属性名 | 类型 | 作用 | 默认值 151 | ------ | ---- | ---- | ------ 152 | getLog | boolean | 开启get日志 | - 153 | setLog | boolean | 开启set日志 | - 154 | addLog | boolean | 开启方法调用日志 | - 155 | runLog | boolean | 开启回调运行日志 | - 156 | newLog | boolean | 开启实例化过程日志 | - 157 | log | boolean | 同时开启get和set日志 | - 158 | getCb | function | get的回调 | - 159 | setCb | function | set的回调 | - 160 | addCb | function | 方法调用的回调 | - 161 | runCb | function | 回调运行的回调 | - 162 | newCb | function | 实例化过程的回调 | - 163 | cb | function | 同时设置get和set回调,回调里设置断点更友好 | - 164 | parse | function | set最终值时执行 | `(val) => val` 165 | filter | function | 返回false则抛弃该项代码运行 | `(...params) => true` 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sdenv-extend", 3 | "version": "1.3.1", 4 | "description": "sdenv补环境框架与浏览器网页环境的共有函数库", 5 | "main": "build/sdenv-extend-cjs.js", 6 | "module": "src/index.js", 7 | "scripts": { 8 | "test": "jest ./test/", 9 | "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand ./test/", 10 | "build": "rollup -c && gzip-size build/sdenv-extend-iife.js", 11 | "lint": "./node_modules/.bin/eslint src/* test/*", 12 | "release": "release-it" 13 | }, 14 | "pre-commit": [ 15 | "lint" 16 | ], 17 | "keywords": [ 18 | "sdenv", 19 | "sdenv-extend" 20 | ], 21 | "files": [ 22 | "package.json", 23 | "README.md", 24 | "src", 25 | "build", 26 | "CHANGELOG.md" 27 | ], 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/pysunday/sdenv-extend" 34 | }, 35 | "homepage": "https://github.com/pysunday/sdenv-extend#readme", 36 | "bugs": "https://github.com/pysunday/sdenv-extend/issues", 37 | "author": "pysunday", 38 | "license": "ISC", 39 | "devDependencies": { 40 | "@optimize-lodash/rollup-plugin": "^4.0.4", 41 | "@release-it/conventional-changelog": "^8.0.1", 42 | "@rollup/plugin-commonjs": "^25.0.7", 43 | "@rollup/plugin-node-resolve": "^15.2.3", 44 | "@rollup/plugin-terser": "^0.4.4", 45 | "babel-eslint": "^10.1.0", 46 | "eslint": "^8.56.0", 47 | "eslint-config-airbnb-base": "^15.0.0", 48 | "eslint-plugin-import": "^2.29.1", 49 | "eslint-plugin-jest": "^27.6.1", 50 | "gzip-size-cli": "^5.1.0", 51 | "jest": "^29.7.0", 52 | "release-it": "^17.0.1", 53 | "lodash-es": "^4.17.21", 54 | "rollup": "^4.9.4" 55 | }, 56 | "jest": { 57 | "moduleNameMapper": { 58 | "@/(.*)": "/$1", 59 | "@src/(.*)": "/src/$1" 60 | } 61 | }, 62 | "engines": { 63 | "node": ">=18" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import glob from 'glob'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | import terser from '@rollup/plugin-terser'; 7 | 8 | const watcher = (globs) => ({ 9 | buildStart () { 10 | for (const item of globs) { 11 | glob.sync(path.resolve(item)).forEach((filename) => { this.addWatchFile(filename) }) 12 | } 13 | } 14 | }) 15 | 16 | const updateVersion = () => ({ 17 | renderStart (outputOptions, inputOptions) { 18 | outputOptions.footer = () => `SdenvExtend.version = 'V${JSON.parse(fs.readFileSync('package.json', 'utf8')).version}'` 19 | } 20 | }) 21 | 22 | export default () => { 23 | const fileName = 'sdenv-extend'; 24 | return { 25 | input: 'src/index.js', 26 | output: [ 27 | { 28 | file: `build/${fileName}-cjs.js`, 29 | strict: false, 30 | format: 'cjs', 31 | }, 32 | { 33 | file: `build/${fileName}-iife.js`, 34 | format: 'iife', 35 | name: 'SdenvExtend', 36 | strict: false, 37 | plugins: [updateVersion()] 38 | }, 39 | { 40 | file: `build/${fileName}-iife.min.js`, 41 | format: 'iife', 42 | name: 'SdenvExtend', 43 | strict: false, 44 | plugins: [terser(), updateVersion()] 45 | } 46 | ], 47 | plugins: [ 48 | resolve({ moduleDirectories: ['node_modules'] }), 49 | commonjs(), 50 | watcher(['package.json']), 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/adapt/index.js: -------------------------------------------------------------------------------- 1 | export * as rs from './rs/index'; 2 | -------------------------------------------------------------------------------- /src/adapt/rs/index.js: -------------------------------------------------------------------------------- 1 | export * from './parseTaskTree'; 2 | -------------------------------------------------------------------------------- /src/adapt/rs/parseTaskTree.js: -------------------------------------------------------------------------------- 1 | // 修改任务树增加任务名称等信息 2 | function parse(val, keyMap = {}, deep = 0, deeps = [0], parent = null) { 3 | // 任务列表、主要子树、次要子树、监控值变化的配置 4 | if (keyMap._count === undefined) keyMap._count = 0; 5 | const { taskKey, oneKey, twoKey, monitor = {} } = keyMap; 6 | const str = val[taskKey]; 7 | val.parent = parent; 8 | val.str = str; 9 | val.val = {}; 10 | if (!str) { 11 | val.arr = []; 12 | val[taskKey] = []; 13 | } else { 14 | val.arr = str.split('').map((it) => it.charCodeAt()) 15 | if (keyMap._count > 0) val[taskKey] = [...val.arr]; // 最外层让瑞数自己处理 16 | } 17 | val.idx = `${deeps.join('>')}-${keyMap._count++}`; 18 | val[oneKey] = val[oneKey].map((it, idx) => { 19 | if (it) { 20 | parse.call(this, it, keyMap, deep + 1, [...deeps, 'one', idx], val); 21 | return this.getTools('monitor')(it, it.idx, { setLog: false, ...monitor }); 22 | } 23 | return it; 24 | }); 25 | val[twoKey] = val[twoKey].map((it, idx) => { 26 | if (it) { 27 | parse.call(this, it, keyMap, deep + 1, [...deeps, 'two', idx], val); 28 | return this.getTools('monitor')(it, it.idx, { setLog: false, ...monitor }); 29 | } 30 | return it; 31 | }) 32 | return val; 33 | } 34 | 35 | export const parseTaskTree = function(...params) { 36 | this.cache.runsObj = parse.call(this, ...params); // 任务树 37 | this.cache.runsArr = []; // 任务运行时队列 38 | } 39 | -------------------------------------------------------------------------------- /src/globalVarible.js: -------------------------------------------------------------------------------- 1 | export const gv = { 2 | adapt: {}, // 适配专用网站方法 3 | tools: {}, // 内部工具 4 | utils: {}, // 外部方法 5 | config: { 6 | proxyOpen: false, // 是否允许开启代理 7 | randomReturn: undefined, // 随机数值固定 8 | randomFixed: false, // 固定日期与随机数,文件:@/config/dateAndRandom.json 9 | // 强制setInterval的时间设置,如设置的足够大让执行函数不运行,其中:undefined表示通过框架拦截并计算运行时间,0表示不拦截且使用原时间 10 | timeInterval: undefined, 11 | timeTimeout: undefined, // 与timeInterval功能类似,区别是控制setTimeout 12 | isNode: typeof window === 'undefined', // node环境标识 13 | }, 14 | memory: { 15 | runinfo: { // 程序运行时间节点 16 | start: new Date().getTime(), // 代码开始运行 17 | isDied: false, // 窗口生命是否已经结束 18 | }, 19 | timeout: null, // 存放处理setTimeout/setInterval的类 20 | sdEval: eval, // node内置eval 21 | sdWindow: typeof window !== 'undefined' ? window : global, 22 | sdFunction: Function, 23 | sdDate: Date, 24 | sdMath: Math, 25 | SdenvExtend: null, // node环境缓存sdenv-extend插件,用于浏览器环境使用 26 | }, 27 | cache: { 28 | dynamicCode: [], // 缓存动态生成代码 29 | runloop: {}, // 缓存循环代码运行时 30 | /* 31 | 缓存循环执行顺序 32 | sdenv.cache.runsArr 33 | .map((it, num) => ({idx: it.idx, num, ...it.val})) 34 | .reduce((ans, it) => ( 35 | ans[it.idx] === undefined ? ans[it.idx] = [it] : ans[it.idx].push(it), ans 36 | ), {}) 37 | */ 38 | runsArr: [], 39 | runsObj: [], // 缓存循环递归树 40 | runtime: {}, // 存放运行时数据的代理 41 | monitor: {}, // 监控变量对象 42 | }, // 存储缓存数据 43 | datas: { 44 | dateAndRandom: {}, // 随机数相关数据 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /src/handle/batteryHandle.js: -------------------------------------------------------------------------------- 1 | import _round from 'lodash-es/round' 2 | 3 | /* 4 | charging: 是否正在充电, true表示正在充电 5 | chargingTime: 充满需要多久时间, 0表示已经充满,未充电总是为Infinity 6 | dischargingTime: 可以用多久,正在充电时总为Infinity 7 | level: 电量,1表示100%电量 8 | */ 9 | 10 | const level = _round(Math.random(), 2); // 电量 11 | const chargingTime = (1 - level) * 100 * 300; // 充满时间 12 | const dischargingTime = level * 100 * 443; // 可用时间 13 | 14 | const charging = { 15 | charging_success: { // 正在充电,电量100% 16 | charging: true, 17 | chargingTime: 0, 18 | dischargingTime: Infinity, 19 | level: 1, 20 | }, 21 | discharging_success: { // 未充电,电量100% 22 | charging: false, 23 | chargingTime: Infinity, 24 | dischargingTime: Infinity, 25 | level: 1, 26 | }, 27 | charging_ing: { // 正在充电 28 | charging: true, 29 | chargingTime, 30 | dischargingTime: Infinity, 31 | level, 32 | }, 33 | discharging_ing: { // 未充电 34 | charging: false, 35 | chargingTime: Infinity, 36 | dischargingTime: dischargingTime, 37 | level, 38 | } 39 | } 40 | 41 | export function batteryHandle(config) { 42 | if (typeof config === 'string') { 43 | config = charging[config] || {}; 44 | } else if (typeof config !== 'object') { 45 | config = {}; 46 | } 47 | const win = this.memory.sdWindow; 48 | win.navigator.getBattery = this.getTools('setNativeFuncName')(() => { 49 | return new Promise((resolve, reject) => { 50 | resolve({ 51 | onchargingchange: null, 52 | onchargingtimechange: null, 53 | ondischargingtimechange: null, 54 | onlevelchange: null, 55 | ...charging.charging_success, 56 | ...config, 57 | }) 58 | }); 59 | }, 'getBattery'); 60 | } 61 | 62 | export default batteryHandle; 63 | -------------------------------------------------------------------------------- /src/handle/connectionHandle.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash-es/get' 2 | 3 | /* 4 | downlink:设备下行速速,单位兆 5 | effectiveType:网络类型,如:"slow-2g"、"2g"、"3g"、"4g" 6 | rrt:设备的往返延时,单位毫秒 7 | saveData:是否为节流模式 8 | type:网络连接类型,有些浏览器有有些没有,如:"bluetooth"(蓝牙1)、"cellular"(蜂窝网络2)、"ethernet"(以太网3)、"none"(无网络连接)、"wifi"(wifi连接4)、"wimax5" 9 | */ 10 | 11 | export function connectionHandle(config) { 12 | const win = this.memory.sdWindow; 13 | if (typeof config !== 'object') { 14 | config = {}; 15 | } 16 | this.getTools('addConstants')(win.navigator.connection, { 17 | downlink: 6.66, 18 | effectiveType: "4g", 19 | onchange: null, 20 | rtt: 0, 21 | saveData: false, 22 | ...config, 23 | }); 24 | } 25 | 26 | export default connectionHandle 27 | -------------------------------------------------------------------------------- /src/handle/cookieHandle.js: -------------------------------------------------------------------------------- 1 | export function cookieHandle(config) { 2 | if (typeof config !== 'object') config = {}; 3 | const win = this.memory.sdWindow; 4 | const cookieGet = Object.getOwnPropertyDescriptor(win.Document.prototype, 'cookie').get; 5 | const cookieSet = Object.getOwnPropertyDescriptor(win.Document.prototype, 'cookie').set; 6 | const { 7 | getLog, // 开启get日志 8 | setLog, // 开启set日志 9 | log, 10 | getCb, // get的回调,设置的debugger更友好 11 | setCb, // set的回调,设置的debugger更友好 12 | cb, // 回调,设置的debugger更友好 13 | parse = (val) => val, 14 | } = config; 15 | Object.defineProperty(win.document, 'cookie', { 16 | configurable: false, 17 | enumerable: false, 18 | get() { 19 | const cookie = cookieGet.call(win.document); 20 | if (getLog || log) win.console.log(`【GET COOKIE】长:${cookie.length} 值:${cookie}`); 21 | (getCb || cb)?.(cookie); 22 | return cookie; 23 | }, 24 | set(val) { 25 | if (setLog || log) win.console.log(`【SET COOKIE】长:${val.length} 值:${val}`); 26 | (setCb || cb)?.(val); 27 | cookieSet.call(win.document, parse(val)); 28 | } 29 | }) 30 | } 31 | 32 | export default cookieHandle; 33 | -------------------------------------------------------------------------------- /src/handle/dateAndRandomHandle.js: -------------------------------------------------------------------------------- 1 | function DateAndRandom(sdenv, { datas }) { 2 | const win = sdenv.memory.sdWindow; 3 | this.sdenv = sdenv; 4 | if (datas && Array.isArray(datas._newdate) && Array.isArray(datas._newdate[0])) { 5 | datas._newdate = datas._newdate.reduce((ans, [val, num]) => ([...ans, ...new Array(num).fill(val)]), []); 6 | } 7 | this.data = datas || {}; 8 | if (this.data?.firstMap && Object.values(this.data.firstMap).some((it) => !it)) { 9 | throw new Error('日期首位配置错误请检查'); 10 | } 11 | this.runs = []; 12 | Object.assign(this, { 13 | _now: win.Date.now, 14 | _parse: win.Date.parse, 15 | _valueOf: win.Date.prototype.valueOf, 16 | _getTime: win.Date.prototype.getTime, 17 | _toString: win.Date.prototype.toString, 18 | _random: win.Math.random, 19 | }); 20 | } 21 | DateAndRandom.prototype.shift = function (name) { 22 | const { firstMap } = this.data; 23 | let val = this.data[name].shift(); 24 | if (typeof firstMap[name] === 'number') { 25 | val += firstMap[name]; 26 | } 27 | this.runs.push(val); 28 | return val; 29 | } 30 | DateAndRandom.prototype.wrapFun = function (funcName, def) { 31 | const name = `_${funcName}`; 32 | if (!this[name]) return undefined; 33 | if (def === undefined && !Array.isArray(this.data[name])) this.data[name] = []; 34 | const self = this; 35 | return this.sdenv.getTools('setNativeFuncName')(function() { 36 | if (def !== undefined) return def; 37 | if (self.data.firstMap?.[name]) { 38 | if (!self.data[name].length) { 39 | throw new Error(`DateAndRandom的${name}数据不够`); 40 | } 41 | return self.shift(name); 42 | } 43 | const val = self[name].call(this); 44 | self.data[name].push(val); 45 | return val 46 | }, funcName); 47 | } 48 | DateAndRandom.prototype.wrapClass = function (className, cla) { 49 | const win = this.sdenv.memory.sdWindow; 50 | const name = `_${className}`; 51 | if (!Array.isArray(this.data[name])) this.data[name] = []; 52 | const self = this; 53 | return this.sdenv.getTools('setNativeFuncName')(new Proxy(cla, { 54 | construct(target, argumentsList, newTarget) { 55 | if (self.data.firstMap?.[name]) { 56 | if (!self.data[name].length) { 57 | throw new Error(`DateAndRandom的${name}数据不够`); 58 | } 59 | return new cla(self.shift(name)); 60 | } 61 | const val = Reflect.construct(target, argumentsList, newTarget); 62 | self.data[name].push(win.Date.prototype.valueOf.call(val)); 63 | return val; 64 | }, 65 | apply(target, argumentsList, newTarget) { 66 | const val = Reflect.apply(target, argumentsList, newTarget); 67 | return val; 68 | } 69 | }), cla.name || className); 70 | } 71 | DateAndRandom.prototype.getData = function (copy) { 72 | const ret = Object.entries(this.data).reduce((ans, [key, val]) => { 73 | if (key === 'firstMap' || !val.length) return ans; 74 | if (key === '_random') { 75 | ans[key] = val; 76 | ans.firstMap[key] = true; 77 | return ans; 78 | } 79 | const first = val[0]; 80 | ans.firstMap[key] = first; 81 | if (key === '_newdate') { 82 | ans[key] = val.map((it) => it - first).reduce((arr, each) => { 83 | if (arr.length === 0 || arr[arr.length - 1][0] !== each) { 84 | arr.push([each, 1]); 85 | } else { 86 | arr[arr.length - 1][1] += 1; 87 | } 88 | return arr; 89 | }, []); 90 | } else { 91 | ans[key] = val.map((it) => it - first); 92 | } 93 | return ans; 94 | }, { firstMap: {} }); 95 | if (copy) { 96 | copy(JSON.stringify(ret)); 97 | console.log('日期与随机数数据复制成功'); 98 | } 99 | return ret; 100 | } 101 | 102 | export function dateAndRandomHandle(config) { 103 | if (typeof config !== 'object') config = {}; 104 | const win = this.memory.sdWindow; 105 | const { randomReturn = this.config.randomReturn } = config; 106 | const dateAndRandom = new DateAndRandom(this, config); 107 | this.getTools('addUtil')(dateAndRandom.getData.bind(dateAndRandom), 'getDateData'); 108 | win.Date = dateAndRandom.wrapClass('newdate', win.Date); 109 | win.Date.now = dateAndRandom.wrapFun('now'); 110 | win.Date.parse = dateAndRandom.wrapFun('parse'); 111 | win.Math.random = dateAndRandom.wrapFun('random', randomReturn); 112 | } 113 | 114 | export default dateAndRandomHandle; 115 | -------------------------------------------------------------------------------- /src/handle/evalHandle.js: -------------------------------------------------------------------------------- 1 | const evalmap = { 2 | '!new function(){eval("this.a=1")}().a': 'false', 3 | } 4 | 5 | export function evalHandle(config) { 6 | if (typeof config !== 'object') config = {}; 7 | const win = this.memory.sdWindow; 8 | const { cb, log } = config; 9 | const self = this; 10 | win.eval = this.getTools('setNativeFuncName')(new Proxy(win.eval, { 11 | apply(target, thisArg, params) { 12 | if (log) win.console.log(`【EVAL APPLY】参数:${params.map(it => self.getTools('compressText')(JSON.stringify(it)))}`); 13 | const new_params = params.map((param) => { 14 | if (typeof evalmap[param] === 'string') return evalmap[param]; 15 | if (param.includes('debugger')) { 16 | param = param.replace(/debugger/g, '') 17 | } 18 | if (param.includes('sdDebugger')) { 19 | param = param.replace(/sdDebugger/g, 'debugger'); 20 | } 21 | return param 22 | }); 23 | const dynamicCode = { 24 | type: 'eval', 25 | params, 26 | tar_params: new_params, 27 | } 28 | cb?.(dynamicCode); 29 | self.cache.dynamicCode.push({ ...dynamicCode }); 30 | return Reflect.apply(target, thisArg, new_params); 31 | }, 32 | }), 'eval'); 33 | } 34 | 35 | export default evalHandle; 36 | -------------------------------------------------------------------------------- /src/handle/eventHandle.js: -------------------------------------------------------------------------------- 1 | export function eventHandle(config) { 2 | const self = this; 3 | const win = this.memory.sdWindow; 4 | const delay = (ms) => new Promise(resolve => win.setTimeout(resolve, ms)) 5 | if (typeof config !== 'object') config = {}; 6 | const { addLog, runLog, log, addCb, runCb, cb, filter = () => true } = config; 7 | win.addEventListener = this.getTools('setNativeFuncName')(new Proxy(win.addEventListener, { 8 | apply: function (target, thisArg, params) { 9 | if (!filter || !filter(...params)) return; 10 | const [type, callback] = params; 11 | const funcStr = callback.param ? JSON.stringify(callback.param) : self.getTools('compressText')(callback.toString()); 12 | if (addLog || log) win.console.log(`【ADD EVENT】事件名:${type}, 方法:${funcStr}`); 13 | (addCb || cb)?.(...params); 14 | return Reflect.apply(target, thisArg, [ 15 | type, 16 | async () => { 17 | // load事件需要等下一次事件循环时执行: 延时为0的定时任务先执行 18 | if (type === 'load') await delay(0); 19 | (runCb || cb)?.(...params); 20 | if (runLog || log) win.console.log(`【RUN EVENT】事件名:${type}, 方法:${funcStr}`); 21 | callback(); 22 | }, 23 | ]); 24 | }, 25 | }), 'addEventListener'); 26 | } 27 | 28 | export default eventHandle; 29 | -------------------------------------------------------------------------------- /src/handle/funcHandle.js: -------------------------------------------------------------------------------- 1 | export function funcHandle(config) { 2 | const self = this; 3 | if (typeof config !== 'object') config = {}; 4 | const win = this.memory.sdWindow; 5 | const { log, cb } = config; 6 | win.Function = this.getTools('setNativeFuncName')(new Proxy(win.Function, { 7 | apply(target, thisArg, params) { 8 | const new_params = params.map((param) => { 9 | if (param.includes('debugger')) { 10 | param = param.replace(/debugger/g, '') 11 | } 12 | if (param.includes('sdDebugger')) { 13 | param = param.replace(/sdDebugger/g, 'debugger'); 14 | } 15 | return param 16 | }); 17 | if (log) win.console.log(`【FUNCTION APPLY】参数:${params.map(it => self.getTools('compressText')(JSON.stringify(it)))}`); 18 | const dynamicCode = { 19 | type: 'Function', 20 | params, 21 | tar_params: new_params, 22 | } 23 | cb?.(dynamicCode); 24 | self.cache.dynamicCode.push({ ...dynamicCode }); 25 | return Reflect.apply(target, thisArg, new_params); 26 | } 27 | }), 'Function'); 28 | win.Function.prototype.constructor = win.Function; 29 | this.getTools('setNativeFuncName')(win.Function.prototype, ''); 30 | } 31 | 32 | export default funcHandle; 33 | -------------------------------------------------------------------------------- /src/handle/index.js: -------------------------------------------------------------------------------- 1 | export * from './evalHandle'; 2 | export * from './funcHandle'; 3 | export * from './eventHandle'; 4 | export * from './cookieHandle'; 5 | export * from './batteryHandle'; 6 | export * from './connectionHandle'; 7 | export * from './intervalHandle'; 8 | export * from './timeoutHandle'; 9 | export * from './dateAndRandomHandle'; 10 | export * from './ovserver'; 11 | -------------------------------------------------------------------------------- /src/handle/intervalHandle.js: -------------------------------------------------------------------------------- 1 | export function intervalHandle(config) { 2 | const self = this; 3 | if (typeof config !== 'object') config = {}; 4 | const win = this.memory.sdWindow; 5 | const { log, cb, time, filter = () => true } = config; 6 | win.setInterval = this.getTools('setNativeFuncName')(new Proxy(win.setInterval, { 7 | apply: function (target, thisArg, params) { 8 | if (!filter || !filter(...params)) return; 9 | const [func, timeout] = params; 10 | const funcStr = func.param ? JSON.stringify(func.param) : self.getTools('compressText')(func.toString()); 11 | if (log) win.console.log(`【INTERVAL APPLY】增加setInterval事件,时间:${timeout}, 方法:${funcStr}`); 12 | if (time !== undefined) { 13 | if (typeof time !== 'number') throw new Error(`time配置如果存在值则必须是数字`); 14 | return Reflect.apply(target, thisArg, [ 15 | () => { 16 | if (log) win.console.log(`【INTERVAL RUN】setInterval执行,时间:${timeout},方法:${funcStr}`); 17 | func() 18 | if (log) win.console.log(`【INTERVAL RUNED】setInterval执行,时间:${timeout},方法:${funcStr}`); 19 | }, 20 | time || timeout, 21 | ]); 22 | } 23 | return self.getTools('addInterval')(func, timeout); 24 | }, 25 | }), 'setInterval'); 26 | } 27 | 28 | export default intervalHandle; 29 | -------------------------------------------------------------------------------- /src/handle/ovserver.js: -------------------------------------------------------------------------------- 1 | let cache = undefined; 2 | 3 | export function ovserverHandle(config) { 4 | const self = this; 5 | if (typeof config !== 'object') config = {}; 6 | const win = this.memory.sdWindow; 7 | const delay = (ms) => new Promise(resolve => win.setTimeout(resolve, ms)) 8 | if (!cache) { 9 | cache = win.MutationObserver; 10 | } 11 | const { newLog, addLog, runLog, log, addCb, runCb, newCb, cb, filter = () => true } = config; 12 | win.MutationObserver = this.getTools('setNativeFuncName')(new Proxy(cache, { 13 | construct: function (target, argArray, newTarget) { 14 | const [func] = argArray; 15 | const funcStr = func.param ? JSON.stringify(func.param) : self.getTools('compressText')(func.toString()); 16 | if (newLog || log) win.console.log(`【NEW OVSERVER】方法:${funcStr}`); 17 | (newCb || cb)?.(...argArray); 18 | const result = Reflect.construct(target, [async (...params) => { 19 | if (!filter || !filter(...argArray)) return; 20 | if (runLog || log) win.console.log(`【RUN OVSERVER】方法:${funcStr}`); 21 | (runCb || cb)?.(...params[0]); 22 | await delay(0); 23 | func(...params); 24 | }], newTarget); 25 | result.observe = new Proxy(result.observe, { 26 | apply: function (target, thisArg, params) { 27 | if (addLog || log) win.console.log(`【ADD OVSERVER】方法:${funcStr}`); 28 | (addCb || cb)?.(...params); 29 | return Reflect.apply(target, thisArg, params); 30 | } 31 | }) 32 | return result; 33 | }, 34 | }), 'MutationObserver'); 35 | } 36 | 37 | export default ovserverHandle; 38 | -------------------------------------------------------------------------------- /src/handle/timeoutHandle.js: -------------------------------------------------------------------------------- 1 | export function timeoutHandle(config) { 2 | const self = this; 3 | if (typeof config !== 'object') config = {}; 4 | const win = this.memory.sdWindow; 5 | const { log, cb, time, filter = () => true } = config; 6 | win.setTimeout = this.getTools('setNativeFuncName')(new Proxy(win.setTimeout, { 7 | apply: function (target, thisArg, params) { 8 | if (!filter || !filter(...params)) return; 9 | const [func, timeout] = params; 10 | const funcStr = func.param ? JSON.stringify(func.param) : self.getTools('compressText')(func.toString()); 11 | if (log) win.console.log(`【TIMEOUT APPLY】增加setTimeout事件,时间:${timeout}, 方法: ${funcStr}`); 12 | if (time !== undefined) { 13 | if (typeof time !== 'number') throw new Error(`time配置如果存在值则必须是数字`); 14 | return Reflect.apply(target, thisArg, [ 15 | () => { 16 | if (log) win.console.log(`【TIMEOUT RUN】setTimeout执行,时间:${timeout},方法:${funcStr}`); 17 | func() 18 | if (log) win.console.log(`【TIMEOUT RUNED】setTimeout执行,时间:${timeout},方法:${funcStr}`); 19 | }, 20 | time || timeout, 21 | ]); 22 | } 23 | return self.getTools('addTimeout')(func, timeout); 24 | }, 25 | }), 'setTimeout'); 26 | win.document.addEventListener('readystatechange', function() { 27 | if (log) win.console.log(`【READY STATE】${win.document.readyState}`); 28 | }); 29 | } 30 | 31 | export default timeoutHandle; 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import _merge from 'lodash-es/merge'; 2 | import { gv } from './globalVarible'; 3 | import { loopRunInit } from './utils/index'; 4 | import * as tools from './tools/index'; 5 | import * as adapt from './adapt/index' 6 | import * as handles from './handle/index'; 7 | 8 | 9 | export default class { 10 | constructor(config, win = undefined) { 11 | const self = this; 12 | Object.assign(this, _merge( 13 | {}, 14 | gv, 15 | config || {}, 16 | { 17 | tools, 18 | adapt, 19 | } 20 | )); 21 | this.setWindow(win); 22 | loopRunInit.call(this); 23 | this.bindThis(); 24 | } 25 | 26 | bindThis() { 27 | const that = this; 28 | function addBind(obj) { 29 | Object.entries(obj).forEach(([key, fun]) => { 30 | if (typeof fun !== 'function') { 31 | addBind(fun) 32 | } else { 33 | obj[key] = fun.bind(that); 34 | } 35 | }) 36 | } 37 | ['tools', 'adapt'].forEach(it => { 38 | addBind(this[it]); 39 | }); 40 | } 41 | 42 | setWindow(win) { 43 | if (!win) return; 44 | Object.assign(this.memory, { 45 | sdWindow: win, 46 | sdEval: win.eval, 47 | sdFunction: win.Function, 48 | sdDate: win.Date, 49 | sdMath: win.Math, 50 | }); 51 | if (this.config.isNode) { 52 | // 修改setFunc工具中的Function指向到window.Function 53 | // tools._setFuncInit(); 54 | Object.setPrototypeOf(win.window, win.Window.prototype); 55 | } 56 | } 57 | 58 | getHandle(name) { 59 | const handleName = `${name}Handle`; 60 | if (!handles[handleName]) return; 61 | return (...params) => { 62 | handles[handleName].call(this, ...params); 63 | return this; 64 | } 65 | } 66 | 67 | getTools(name) { 68 | if (!tools[name]) return; 69 | return (...params) => { 70 | return tools[name].call(this, ...params); 71 | } 72 | } 73 | 74 | getUtils(name) { 75 | if (!utils[name]) return; 76 | return (...params) => { 77 | return utils[name].call(this, ...params); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/tools/addConstant.js: -------------------------------------------------------------------------------- 1 | export function addConstant(object, property, value) { 2 | Object.defineProperty(object, property, { 3 | configurable: false, 4 | enumerable: true, 5 | writable: false, 6 | value 7 | }); 8 | } 9 | 10 | export function addConstants(Constructor, propertyMap) { 11 | for (const property in propertyMap) { 12 | const value = propertyMap[property]; 13 | addConstant(Constructor, property, value); 14 | } 15 | } 16 | 17 | export function mixin(target, source, allowKeys = []) { 18 | const keys = Reflect.ownKeys(source); 19 | for (let i = 0; i < keys.length; ++i) { 20 | if (keys[i] in target && !allowKeys.includes(keys[i])) { 21 | continue; 22 | } 23 | Object.defineProperty(target, keys[i], Object.getOwnPropertyDescriptor(source, keys[i])); 24 | } 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /src/tools/cacher.js: -------------------------------------------------------------------------------- 1 | import _get from 'lodash-es/get'; 2 | 3 | export function addRuntimeData(name, key, val) { 4 | // 添加运行时数据 5 | if (!this.cache.runtime[name]) this.cache.runtime[name] = {}; 6 | const runtimeData = this.cache.runtime[name]; 7 | if (!runtimeData[key]) runtimeData[key] = []; 8 | runtimeData[key].push(val); 9 | return val; 10 | } 11 | 12 | export function getRuntimeData(name, key) { 13 | return _get(this, ['cache.runtime', name, key] 14 | .filter((it) => it !== undefined) 15 | .join('.')); 16 | } 17 | -------------------------------------------------------------------------------- /src/tools/compressText.js: -------------------------------------------------------------------------------- 1 | export function compressText(text, max = 100) { 2 | if (typeof text !== 'string') text = `${text}`; 3 | if (text.length <= max) { 4 | return text; 5 | } 6 | return text.replace(/[\n\s\r]+/g, ' ').slice(0, max) + '......'; 7 | } 8 | -------------------------------------------------------------------------------- /src/tools/exit.js: -------------------------------------------------------------------------------- 1 | export function exit(params) { 2 | const win = this.memory.sdWindow; 3 | this.getTools('setDied')() 4 | if (params.url) win.onbeforeunload?.(params.url); 5 | } 6 | -------------------------------------------------------------------------------- /src/tools/getNativeProto.js: -------------------------------------------------------------------------------- 1 | import { setNativeFuncName } from './setFunc'; 2 | import { setNativeObjName } from './setObj'; 3 | 4 | export function getNativeProto(funcname, objname, attrs = {}) { 5 | // 自动生成原型函数并返回基于原始数据与该原型函数的实例化对象 6 | const func = setNativeFuncName(() => { 7 | throw new TypeError('Illegal constructor'); 8 | }, funcname); 9 | const obj = setNativeObjName({ 10 | __proto__: func, 11 | ...attrs, 12 | }, funcname); 13 | return [func, obj]; 14 | } 15 | -------------------------------------------------------------------------------- /src/tools/gvOper.js: -------------------------------------------------------------------------------- 1 | export function addUtil(func, name) { 2 | if (!func) debugger; 3 | this.utils[name || func.name] = func.bind(this); 4 | return func; 5 | } 6 | 7 | export function getUtil(name) { 8 | return this.utils[name]; 9 | } 10 | -------------------------------------------------------------------------------- /src/tools/index.js: -------------------------------------------------------------------------------- 1 | export * from './addConstant'; 2 | export * from './cacher'; 3 | export * from './compressText'; 4 | export * from './exit'; 5 | export * from './getNativeProto'; 6 | export * from './gvOper'; 7 | export * from './monitor'; 8 | export * from './runtime'; 9 | export * from './setFunc'; 10 | export * from './setObj'; 11 | export * from './timeout'; 12 | -------------------------------------------------------------------------------- /src/tools/monitor.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-debugger */ 2 | 3 | export function monitor(tar, name, config = {}) { 4 | const win = this.memory.sdWindow; 5 | const tools = this.tools; 6 | const { 7 | getLog, // 开启get日志 8 | setLog, // 开启set日志 9 | getKeys = [], // 触发get的debugger的键集合 10 | setKeys = [], // 触发set的debugger的键集合 11 | keys = [], // 触发debugger的键集合 12 | getCb, // get的回调,设置的debugger更友好 13 | setCb, // set的回调,设置的debugger更友好 14 | cb, // 回调,设置的debugger更友好 15 | parse = (key, val) => val, 16 | } = config; 17 | if (!this.cache.monitor[name]) this.cache.monitor[name] = []; 18 | tar.sdMonitorName = name; 19 | const newTar = new win.Proxy(tar, { 20 | get(target, property, receiver) { 21 | if (getLog) win.console.log(`${name} Getting ${property}`); 22 | if (getKeys.includes(property) || keys.includes(property)) debugger; 23 | (getCb || cb)?.(property, name); 24 | return Reflect.get(target, property, receiver); 25 | }, 26 | set(target, property, value, receiver) { 27 | const show = [ 28 | win, 29 | win.Math, 30 | win.navigation 31 | ].includes(value) && value.toString ? value.toString() : value 32 | if (setLog && !(Array.isArray(target) && property === 'length')) { 33 | win.console.log(`${name} Setting ${property} to ${tools.compressText(show)}`); 34 | } 35 | if (setKeys.includes(property) || keys.includes(property)) debugger; 36 | (setCb || cb)?.(property, value, name); 37 | return Reflect.set(target, property, parse(property, value), receiver); 38 | } 39 | }); 40 | this.cache.monitor[name].push(newTar); 41 | return newTar; 42 | } 43 | 44 | export function monitorFunction(tar, name, config = {}) { 45 | const win = this.memory.sdWindow; 46 | const { 47 | log, // 开启日志 48 | isDebugger, // 是否在调用时触发debugger 49 | cb, // 回调,设置的debugger更友好 50 | } = config; 51 | if (!this.cache.monitor[name]) this.cache.monitor[name] = []; 52 | const newTar = new win.Proxy(tar, { 53 | apply(target, thisArg, argArray) { 54 | const result = Reflect.apply(target, thisArg, argArray); 55 | if (log) win.console.log(`${name} Apply ${argArray}`); 56 | if (isDebugger) debugger; 57 | cb?.(argArray, result, name); 58 | return result; 59 | } 60 | }); 61 | this.cache.monitor[name].push(newTar); 62 | return newTar; 63 | } 64 | 65 | export function getMonitor(name) { 66 | if (name) return this.cache.monitor[name]; 67 | return this.cache.monitor 68 | } 69 | -------------------------------------------------------------------------------- /src/tools/runtime.js: -------------------------------------------------------------------------------- 1 | export function isAlive() { 2 | // 窗口是否还活跃 3 | return !this.memory.runinfo.isDied; 4 | } 5 | 6 | export function isDied() { 7 | // 窗口是否还活跃 8 | return this.memory.runinfo.isDied; 9 | } 10 | 11 | export function setDied() { 12 | // 设置窗口为死亡状态 13 | this.memory.runinfo.isDied = true; 14 | } 15 | -------------------------------------------------------------------------------- /src/tools/setFunc.js: -------------------------------------------------------------------------------- 1 | let originToString = Function.toString; 2 | const canToStrigArr = []; 3 | const toString = function() { 4 | if (canToStrigArr.includes(this.name)) { 5 | return `function ${this.name || ''}() { [native code] }`; 6 | } 7 | return originToString.call(this); 8 | } 9 | 10 | export const setFuncNative = function(func, name) { 11 | // 修改函数的toString方法返回native code标识 12 | if (!func) return undefined; 13 | func.name = name || func.name || ''; 14 | canToStrigArr.push(func.name); 15 | Object.defineProperty(func, 'toString', { 16 | enumerable: false, 17 | configurable: true, 18 | writable: true, 19 | value: toString, 20 | }); 21 | return func; 22 | }; 23 | 24 | export const setFuncName = function(func, name) { 25 | // 修改方法的name属性值 26 | Object.defineProperty(func, 'name', { 27 | configurable: true, 28 | enumerable: false, 29 | writable: false, 30 | value: name 31 | }); 32 | return func; 33 | } 34 | 35 | export const setNativeFuncName = function(func, name) { 36 | setFuncName(func, name); 37 | setFuncNative(func, name); 38 | return func; 39 | } 40 | setNativeFuncName(toString, 'toString'); 41 | 42 | export const _setFuncInit = function() { 43 | // 修改Function指向与toString原型链指向 44 | const win = this.memory.sdWindow; 45 | originToString = win.Function.toString; 46 | toString.__proto__ = win.Function.prototype; 47 | } 48 | -------------------------------------------------------------------------------- /src/tools/setObj.js: -------------------------------------------------------------------------------- 1 | export const setObjNative = function(obj, name) { 2 | // 修改函数的toString方法返回native code标识 3 | Object.defineProperty(obj, 'toString', { 4 | enumerable: false, 5 | configurable: true, 6 | writable: true, 7 | value() { 8 | return `[object ${name}]`; 9 | } 10 | }); 11 | return obj; 12 | }; 13 | 14 | export const setObjName = function(obj, name) { 15 | /* 16 | 修改toString的显示,如: 17 | window.navigator.webkitPersistentStorage.toString() === '[object DeprecatedStorageQuota]' 18 | */ 19 | Object.defineProperty(obj, Symbol.toStringTag, { 20 | configurable: true, 21 | enumerable: false, 22 | writable: false, 23 | value: name 24 | }); 25 | return obj; 26 | }; 27 | 28 | export const setNativeObjName = function(obj, name) { 29 | setObjName(obj, name); 30 | setObjNative(obj, name); 31 | return obj; 32 | } 33 | -------------------------------------------------------------------------------- /src/tools/timeout.js: -------------------------------------------------------------------------------- 1 | import { isDied, isAlive } from './runtime'; 2 | 3 | const currentTask = function () { 4 | this.cache = null; 5 | this.add = (item) => { 6 | if (!this.cache) return false; 7 | if (this.cache.task.includes(item)) return 8 | this.cache.task.push(item); 9 | } 10 | this.set = (task) => this.cache = task; 11 | this.get = () => this.cache; 12 | this.getTime = () => { 13 | if (!this.cache) return false; 14 | return this.cache.time; 15 | } 16 | this.getTask = (idx) => { 17 | if (!this.cache) return false; 18 | if (typeof idx === 'number') return this.cache.task[idx] 19 | return this.cache.task; 20 | } 21 | this.clear = () => this.cache = null; 22 | } 23 | 24 | class Timeout { 25 | constructor(sdenv) { 26 | this.sdenv = sdenv; 27 | this.index = []; 28 | this.timeouts = {}; 29 | this.lastOp = sdenv.memory.runinfo.start; 30 | this.isLock = false; 31 | this.id = 0; 32 | this.currentTask = new currentTask(); 33 | } 34 | 35 | addTimeout(func, time, type = 'timeout', idx = this.index.length) { 36 | const { sdenv } = this; 37 | let timekey = new sdenv.memory.sdDate().getTime() - sdenv.memory.runinfo.start + time; 38 | const obj = { 39 | func, // 方法 40 | type, // 类型: timeout interval 41 | flag: 0, // 执行状态: -1(取消执行)0(未执行)1(执行中)2(已执行) 42 | time, // 延时时间 43 | expect_time: timekey, // 预期执行时间 44 | real_time: null, // 实际执行时间 45 | index: idx, // 编号 46 | id: this.id++, 47 | } 48 | if (this.index[idx]) { 49 | this.index[idx] = obj; 50 | } else { 51 | this.index.push(obj); 52 | } 53 | if (time === 0 && this.currentTask.get()) { 54 | timekey = this.currentTask.getTime(); 55 | this.timeouts[timekey].push(obj); 56 | this.currentTask.add(obj); 57 | } else { 58 | if (this.timeouts[timekey] === undefined) this.timeouts[timekey] = []; 59 | this.timeouts[timekey].push(obj); 60 | if (isAlive()) this.exec(); 61 | } 62 | // const win = sdenv.memory.sdWindow; 63 | // win.console.log(`程序时间${timekey}处${type}回调添加成功`); 64 | return idx; 65 | } 66 | 67 | addInterval(func, time, idx = undefined) { 68 | const self = this; 69 | const newFunc = function() { 70 | try { 71 | func(); 72 | } catch (err) { 73 | console.error(err); 74 | } finally { 75 | if (self.index[idx].flag !== -1) self.addInterval(func, time, idx); 76 | } 77 | } 78 | idx = this.addTimeout(newFunc, time, 'interval', idx); 79 | return idx; 80 | } 81 | 82 | remove(idx) { 83 | if (typeof idx !== 'number' || !this.index[idx]) return false; 84 | if (this.index[idx].flag === 0) { 85 | this.index[idx].flag = -1; 86 | } else if ([-1, 2].includes(this.index[idx].flag)) { 87 | return undefined; 88 | } else if (this.index[idx].flag === 1) { 89 | return false; 90 | } 91 | return undefined 92 | } 93 | 94 | exec() { 95 | if (this.isLock) return; 96 | this.isLock = true; 97 | this.sdenv.memory.sdWindow.setTimeout(() => { 98 | this.isLock = false; 99 | this.run(); 100 | }, 0); 101 | } 102 | 103 | run() { 104 | const { sdenv } = this; 105 | const win = sdenv.memory.sdWindow; 106 | const times = Object.keys(this.timeouts).filter((key) => { 107 | if (Number(key) + sdenv.memory.runinfo.start <= this.lastOp) return false; 108 | return true; 109 | }); 110 | if (times.length === 0) return false; 111 | this.lastOp = Number(times[0]) + sdenv.memory.runinfo.start; 112 | this.currentTask.set({ 113 | task: this.timeouts[times[0]], 114 | time: times[0], 115 | }); 116 | for(let i = 0; this.currentTask.getTask(i); i++) { 117 | const cfg = this.currentTask.getTask(i); 118 | if (isDied() || cfg.flag === -1) return; 119 | const funcStr = cfg.func.param ? JSON.stringify(cfg.func.param) : sdenv.tools.compressText(cfg.func.toString()); 120 | win.console.log(`【TIMEOUT RUN】执行程序时间${times[0]}处${cfg.type}回调,延时:${cfg.time},编号:${cfg.id},方法:${funcStr}`); 121 | cfg.flag = 1; 122 | cfg.real_time = new sdenv.memory.sdDate().getTime() - sdenv.memory.runinfo.start; 123 | try { 124 | cfg.func(); 125 | win.console.log(`【TIMEOUT RUNED】执行程序时间${times[0]}处${cfg.type}回调,延时:${cfg.time},编号:${cfg.id},方法:${funcStr}`); 126 | } catch (e) { 127 | console.error(e); 128 | } 129 | cfg.flag = 2 130 | } 131 | this.currentTask.clear(); 132 | if (isDied()) return undefined; 133 | if (times.length > 1) { 134 | this.run(); 135 | } else { 136 | this.exec(); 137 | } 138 | return undefined; 139 | } 140 | } 141 | 142 | function check() { 143 | if (!this.memory.timeout) { 144 | this.memory.timeout = new Timeout(this); 145 | } 146 | } 147 | 148 | export const addTimeout = function(func, time) { 149 | check.call(this); 150 | return this.memory.timeout.addTimeout(func, Number(time || 0), 'timeout') 151 | } 152 | 153 | export const addInterval = function(func, time) { 154 | check.call(this); 155 | return this.memory.timeout.addInterval(func, time); 156 | } 157 | 158 | export const removeTimeout = function(idx) { 159 | check.call(this); 160 | return this.memory.timeout.remove(idx); 161 | } 162 | 163 | export const removeInterval = function(idx) { 164 | check.call(this); 165 | return this.memory.timeout.remove(idx); 166 | } 167 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './loopRun'; 2 | -------------------------------------------------------------------------------- /src/utils/loopRun.js: -------------------------------------------------------------------------------- 1 | // 循环体执行监控 2 | import _orderBy from 'lodash-es/orderBy'; 3 | 4 | export function loopRunInit() { 5 | const sdenv = this; 6 | const addUtil = sdenv.getTools('addUtil'); 7 | const preLoop = {}; 8 | const win = sdenv.memory.sdWindow; 9 | const runloop = sdenv.cache.runloop = { current: 1 }; 10 | 11 | let log = false; 12 | 13 | addUtil((key, idx, name, runlist = '', lens = 0) => { 14 | /* 15 | * 初始化一个循环 16 | * key: 循环体的case的变量名 17 | * idx: 初始化下标 18 | * name: 循环方法名称 19 | * runlist: 循环任务名或者循环任务队列 20 | * lens: 任务队列长度 21 | * */ 22 | const arr = [] 23 | const current = runloop.current++; 24 | if (!runloop[key]) { 25 | runloop[key] = []; 26 | } 27 | const loopobj = { 28 | idx, // 启动的下标 29 | name, // 函数名 30 | data: arr, // 实际运行队列 31 | current, // 启动编号 32 | idxs: [], // 实际运行下标队列 33 | list: Array.isArray(runlist) ? runlist.join() : runlist, // 预期运行队列 34 | lens: lens || (Array.isArray(runlist) ? runlist.length : -1), // 预期运行队列长度 35 | pre: { // 上一次循环信息 36 | current: preLoop.cur, 37 | curloop: preLoop.num, 38 | }, 39 | param: {}, // 需要记录的关键数据 40 | } 41 | if (preLoop.loop) { 42 | preLoop.loop.param[preLoop.loop.data.length - 1] = { 43 | next: current // 档次编号记录到父循环中 44 | } 45 | } 46 | runloop[key].push(loopobj); 47 | if (log) win.console.log(`【RUN TASK】current:${current}`); 48 | return { 49 | addLoop: (id, it, _key) => { 50 | loopobj.data.push(it); 51 | loopobj.idxs.push(id); 52 | Object.assign(preLoop, { 53 | key: _key, 54 | cur: current, 55 | num: arr.length, 56 | loop: loopobj, 57 | }); 58 | return (...args) => { 59 | if (log) { 60 | console.log([`(${current},${arr.length})`, ...args.map(it => sdenv.tools.compressText(it, 200))].join(' ==== ')); 61 | } 62 | } 63 | }, 64 | curLoop: () => arr.length, 65 | current, 66 | loopobj, 67 | openLog: () => (log = true), 68 | closeLog: () => (log = false), 69 | isLog: () => log, 70 | } 71 | }, 'initLoop'); 72 | 73 | // 上一个循环的信息 74 | addUtil(() => ({ ...preLoop }), 'getPreLoop'); 75 | 76 | addUtil(() => log, 'getLogLoop'); 77 | 78 | addUtil(() => log = false, 'closeLogLoop'); 79 | 80 | addUtil(() => log = true, 'openLogLoop'); 81 | 82 | addUtil((copy) => { 83 | // 返回循环体运行数据,copy为复制方法,非浏览器环境需要手动传入 84 | const data = JSON.parse(JSON.stringify(runloop)); 85 | const ascii = {}; 86 | Object.entries(data).forEach(([key, item]) => { 87 | if (!Array.isArray(item)) return; 88 | ascii[key] = {} 89 | item.forEach((each) => { 90 | each.data.forEach((it) => { 91 | if (ascii[key][it] === undefined) ascii[key][it] = 0; 92 | ascii[key][it]++; 93 | }) 94 | each.data = each.data.join(); 95 | each.idxs = each.idxs.join(); 96 | }) 97 | ascii[key] = _orderBy(Object.entries(ascii[key]).map(([code, num]) => ({ code, num })), 'num', 'desc') 98 | }); 99 | if (copy) { 100 | copy(JSON.stringify(data)); 101 | console.log('运行时数据复制成功'); 102 | } 103 | return { ascii, ...data }; 104 | }, 'getLoopData'); 105 | } 106 | -------------------------------------------------------------------------------- /test/sdenv.test.js: -------------------------------------------------------------------------------- 1 | const sdenvExtend = require('../'); 2 | 3 | test('sdenv', () => { 4 | const sdenv = new sdenvExtend(); 5 | expect(sdenv === sdenvExtend()).toBe(true); 6 | expect(eval('!new function(){eval("this.a=1")}().a')).toBe(false) 7 | }); 8 | -------------------------------------------------------------------------------- /test/tools.test.js: -------------------------------------------------------------------------------- 1 | const sdenv = require('../sdenv-extend')(); 2 | 3 | test('has tools', () => { 4 | expect(Object.keys(sdenv.tools).length > 0).toBe(true); 5 | }); 6 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | const sdenv = require('../sdenv-extend')({ 2 | config: { 3 | randomReturn: 0.123, 4 | randomFixed: true 5 | }, 6 | datas: { 7 | dateAndRandom: { 8 | firstMap: { _getTime: 1704778312614, _newdate: 1704778312614 }, 9 | _getTime: [0, 100], 10 | _newdate: [0, 100] 11 | } 12 | } 13 | }); 14 | 15 | test('looprun test', () => { 16 | const { runloop } = sdenv.cache; 17 | const { 18 | addLoop, 19 | curLoop, 20 | current, 21 | loopobj 22 | } = sdenv.utils.initLoop('casekey', 0, 'funcname', [1, 2, 3, 4, 5]); 23 | expect(curLoop()).toBe(0); 24 | addLoop(0, 10, 'casekey'); 25 | expect(curLoop()).toBe(1); 26 | addLoop(1, 8, 'casekey'); 27 | expect(curLoop()).toBe(2); 28 | expect(runloop.current).toBe(2); 29 | expect(current).toBe(1); 30 | expect(loopobj.data).toEqual([10, 8]); 31 | expect(loopobj.idxs).toEqual([0, 1]); 32 | }); 33 | 34 | test('dateAndRandom test', () => { 35 | expect(Math.random()).toBe(0.123); 36 | expect(new Date().getTime()).toBe(1704778312614); 37 | expect(new Date().getTime()).toBe(1704778312714); 38 | }); 39 | --------------------------------------------------------------------------------