فهرست منبع

`seed` option and `IE8` fix

sanex3339 8 سال پیش
والد
کامیت
8f3441a4f9

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 Change Log
 ===
 ===
+v0.8.1
+---
+* New option `seed` sets seed for random generator. This is useful for creating repeatable results.
+* IE8 runtime error fix.
+
 v0.8.1
 v0.8.1
 ---
 ---
 * `disableConsoleOutput` option now replaces `console.xxx` functions on empty function instead of infinity loop.
 * `disableConsoleOutput` option now replaces `console.xxx` functions on empty function instead of infinity loop.

+ 6 - 0
README.md

@@ -123,6 +123,7 @@ Following options available for the JS Obfuscator:
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     selfDefending: true,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
@@ -148,6 +149,7 @@ Following options available for the JS Obfuscator:
     --disableConsoleOutput <boolean>
     --disableConsoleOutput <boolean>
     --reservedNames <list> (comma separated)
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
     --rotateStringArray <boolean>
+    --seed <number>
     --selfDefending <boolean>
     --selfDefending <boolean>
     --sourceMap <boolean>
     --sourceMap <boolean>
     --sourceMapBaseUrl <string>
     --sourceMapBaseUrl <string>
@@ -218,6 +220,10 @@ Shift the `stringArray` array by a fixed and random (generated at the code obfus
 
 
 This option is recommended if your original source code isn't small, as the helper function can attract attention.
 This option is recommended if your original source code isn't small, as the helper function can attract attention.
 
 
+### `seed`
+Type: `number` Default: `0`
+
+This option sets seed for random generator. This is useful for creating repeatable results.
 
 
 ### `selfDefending`
 ### `selfDefending`
 Type: `boolean` Default: `true`
 Type: `boolean` Default: `true`

+ 56 - 28
dist/index.js

@@ -88,7 +88,7 @@ module.exports =
 /******/ 	__webpack_require__.p = "";
 /******/ 	__webpack_require__.p = "";
 /******/
 /******/
 /******/ 	// Load entry module and return exports
 /******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(__webpack_require__.s = 89);
+/******/ 	return __webpack_require__(__webpack_require__.s = 88);
 /******/ })
 /******/ })
 /************************************************************************/
 /************************************************************************/
 /******/ ([
 /******/ ([
@@ -167,11 +167,32 @@ var Utils = function () {
             domain = domain.split(':')[0];
             domain = domain.split(':')[0];
             return domain;
             return domain;
         }
         }
+    }, {
+        key: 'getRandomFloat',
+        value: function getRandomFloat(min, max) {
+            return Utils.getRandomGenerator().floating({
+                min: min,
+                max: max,
+                fixed: 7
+            });
+        }
     }, {
     }, {
         key: 'getRandomGenerator',
         key: 'getRandomGenerator',
         value: function getRandomGenerator() {
         value: function getRandomGenerator() {
+            var randomGenerator = Utils.randomGenerator;
+            if (!randomGenerator) {
+                throw new Error('`randomGenerator` static property is undefined');
+            }
             return Utils.randomGenerator;
             return Utils.randomGenerator;
         }
         }
+    }, {
+        key: 'getRandomInteger',
+        value: function getRandomInteger(min, max) {
+            return Utils.getRandomGenerator().integer({
+                min: min,
+                max: max
+            });
+        }
     }, {
     }, {
         key: 'getRandomVariableName',
         key: 'getRandomVariableName',
         value: function getRandomVariableName() {
         value: function getRandomVariableName() {
@@ -180,10 +201,7 @@ var Utils = function () {
             var rangeMinInteger = 10000,
             var rangeMinInteger = 10000,
                 rangeMaxInteger = 99999999,
                 rangeMaxInteger = 99999999,
                 prefix = '_0x';
                 prefix = '_0x';
-            return '' + prefix + Utils.decToHex(Utils.getRandomGenerator().integer({
-                min: rangeMinInteger,
-                max: rangeMaxInteger
-            })).substr(0, length);
+            return '' + prefix + Utils.decToHex(Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)).substr(0, length);
         }
         }
     }, {
     }, {
         key: 'hideString',
         key: 'hideString',
@@ -196,7 +214,7 @@ var Utils = function () {
                     i2 = -1,
                     i2 = -1,
                     result = '';
                     result = '';
                 while (i1 < s1.length || i2 < s2.length) {
                 while (i1 < s1.length || i2 < s2.length) {
-                    if (Math.random() < 0.5 && i2 < s2.length) {
+                    if (Utils.getRandomFloat(0, 1) < 0.5 && i2 < s2.length) {
                         result += s2.charAt(++i2);
                         result += s2.charAt(++i2);
                     } else {
                     } else {
                         result += s1.charAt(++i1);
                         result += s1.charAt(++i1);
@@ -245,6 +263,11 @@ var Utils = function () {
             }
             }
             return result;
             return result;
         }
         }
+    }, {
+        key: 'setRandomGenerator',
+        value: function setRandomGenerator(randomGenerator) {
+            Utils.randomGenerator = randomGenerator;
+        }
     }, {
     }, {
         key: 'strEnumify',
         key: 'strEnumify',
         value: function strEnumify(obj) {
         value: function strEnumify(obj) {
@@ -640,10 +663,7 @@ var NodeAppender = function () {
     }, {
     }, {
         key: 'getRandomStackTraceIndex',
         key: 'getRandomStackTraceIndex',
         value: function getRandomStackTraceIndex(stackTraceRootLength) {
         value: function getRandomStackTraceIndex(stackTraceRootLength) {
-            return Utils_1.Utils.getRandomGenerator().integer({
-                min: 0,
-                max: Math.max(0, Math.round(stackTraceRootLength - 1))
-            });
+            return Utils_1.Utils.getRandomInteger(0, Math.max(0, Math.round(stackTraceRootLength - 1)));
         }
         }
     }, {
     }, {
         key: 'insertNodeAtIndex',
         key: 'insertNodeAtIndex',
@@ -1003,6 +1023,7 @@ exports.NO_CUSTOM_NODES_PRESET = Object.freeze({
     domainLock: [],
     domainLock: [],
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: false,
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     selfDefending: false,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
@@ -1070,7 +1091,9 @@ var NodeCallsControllerFunctionNode = function (_AbstractCustomNode_) {
             if (this.appendState === AppendState_1.AppendState.AfterObfuscation) {
             if (this.appendState === AppendState_1.AppendState.AfterObfuscation) {
                 return NodeUtils_1.NodeUtils.convertCodeToStructure(JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate().formatUnicorn({
                 return NodeUtils_1.NodeUtils.convertCodeToStructure(JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate().formatUnicorn({
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                }), NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET).getObfuscatedCode());
+                }), Object.assign({}, NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })).getObfuscatedCode());
             }
             }
             return NodeUtils_1.NodeUtils.convertCodeToStructure(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate().formatUnicorn({
             return NodeUtils_1.NodeUtils.convertCodeToStructure(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate().formatUnicorn({
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
@@ -1127,7 +1150,7 @@ var StringLiteralReplacer = function (_AbstractReplacer_1$A) {
     _createClass(StringLiteralReplacer, [{
     _createClass(StringLiteralReplacer, [{
         key: 'replace',
         key: 'replace',
         value: function replace(nodeValue) {
         value: function replace(nodeValue) {
-            var replaceWithStringArrayFlag = nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray && Math.random() <= this.options.stringArrayThreshold;
+            var replaceWithStringArrayFlag = nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray && Utils_1.Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold;
             if (this.options.stringArray && replaceWithStringArrayFlag) {
             if (this.options.stringArray && replaceWithStringArrayFlag) {
                 return this.replaceStringLiteralWithStringArrayCall(nodeValue);
                 return this.replaceStringLiteralWithStringArrayCall(nodeValue);
             }
             }
@@ -1233,7 +1256,6 @@ exports.ObfuscationResult = ObfuscationResult;
 "use strict";
 "use strict";
 
 
 exports.JSFuck = {
 exports.JSFuck = {
-    Window: '[]["filter"]["constructor"]("return this")()',
     False: '![]',
     False: '![]',
     True: '!![]',
     True: '!![]',
     a: '(false+"")[1]',
     a: '(false+"")[1]',
@@ -1349,6 +1371,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     domainLock: [],
     domainLock: [],
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     selfDefending: true,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
@@ -1391,10 +1414,12 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
 
 
 var esprima = __webpack_require__(24);
 var esprima = __webpack_require__(24);
 var escodegen = __webpack_require__(13);
 var escodegen = __webpack_require__(13);
+var chance_1 = __webpack_require__(82);
 var ObfuscationResult_1 = __webpack_require__(20);
 var ObfuscationResult_1 = __webpack_require__(20);
 var Obfuscator_1 = __webpack_require__(28);
 var Obfuscator_1 = __webpack_require__(28);
 var Options_1 = __webpack_require__(57);
 var Options_1 = __webpack_require__(57);
 var SourceMapCorrector_1 = __webpack_require__(29);
 var SourceMapCorrector_1 = __webpack_require__(29);
+var Utils_1 = __webpack_require__(0);
 
 
 var JavaScriptObfuscatorInternal = function () {
 var JavaScriptObfuscatorInternal = function () {
     function JavaScriptObfuscatorInternal(sourceCode) {
     function JavaScriptObfuscatorInternal(sourceCode) {
@@ -1417,6 +1442,9 @@ var JavaScriptObfuscatorInternal = function () {
             var astTree = esprima.parse(this.sourceCode, {
             var astTree = esprima.parse(this.sourceCode, {
                 loc: true
                 loc: true
             });
             });
+            if (this.options.seed !== 0) {
+                Utils_1.Utils.setRandomGenerator(new chance_1.Chance(this.options.seed));
+            }
             astTree = new Obfuscator_1.Obfuscator(this.options).obfuscateNode(astTree);
             astTree = new Obfuscator_1.Obfuscator(this.options).obfuscateNode(astTree);
             this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
             this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
         }
         }
@@ -1842,7 +1870,7 @@ var JavaScriptObfuscatorCLI = function () {
                 return val.split(',');
                 return val.split(',');
             }).option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', function (val) {
             }).option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', function (val) {
                 return val.split(',');
                 return val.split(',');
-            }).option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`').option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`').option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--stringArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--stringArrayEncoding <boolean|string> [true, false, base64, rc4]', 'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed', JavaScriptObfuscatorCLI.parseStringArrayEncoding).option('--stringArrayThreshold <number>', 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--unicodeEscapeSequence <boolean>', 'Allows to enable/disable string conversion to unicode escape sequence', JavaScriptObfuscatorCLI.parseBoolean).parse(this.rawArguments);
+            }).option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--seed <number>', 'Sets seed for random generator. This is useful for creating repeatable results.', parseFloat).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`').option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`').option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--stringArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--stringArrayEncoding <boolean|string> [true, false, base64, rc4]', 'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed', JavaScriptObfuscatorCLI.parseStringArrayEncoding).option('--stringArrayThreshold <number>', 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--unicodeEscapeSequence <boolean>', 'Allows to enable/disable string conversion to unicode escape sequence', JavaScriptObfuscatorCLI.parseBoolean).parse(this.rawArguments);
             this.commands.on('--help', function () {
             this.commands.on('--help', function () {
                 console.log('  Examples:\n');
                 console.log('  Examples:\n');
                 console.log('    %> javascript-obfuscator in.js --compact true --selfDefending false');
                 console.log('    %> javascript-obfuscator in.js --compact true --selfDefending false');
@@ -2131,10 +2159,7 @@ var DebugProtectionFunctionNode = function (_AbstractCustomNode_) {
         key: 'appendNode',
         key: 'appendNode',
         value: function appendNode(blockScopeNode) {
         value: function appendNode(blockScopeNode) {
             var programBodyLength = blockScopeNode.body.length,
             var programBodyLength = blockScopeNode.body.length,
-                randomIndex = Utils_1.Utils.getRandomGenerator().integer({
-                min: 0,
-                max: programBodyLength
-            });
+                randomIndex = Utils_1.Utils.getRandomInteger(0, programBodyLength);
             NodeAppender_1.NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
             NodeAppender_1.NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
         }
         }
     }, {
     }, {
@@ -2273,7 +2298,9 @@ var SelfDefendingUnicodeNode = function (_AbstractCustomNode_) {
             return NodeUtils_1.NodeUtils.convertCodeToStructure(JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(SelfDefendingTemplate_1.SelfDefendingTemplate().formatUnicorn({
             return NodeUtils_1.NodeUtils.convertCodeToStructure(JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(SelfDefendingTemplate_1.SelfDefendingTemplate().formatUnicorn({
                 selfDefendingFunctionName: Utils_1.Utils.getRandomVariableName(),
                 selfDefendingFunctionName: Utils_1.Utils.getRandomVariableName(),
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            }), NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET).getObfuscatedCode());
+            }), Object.assign({}, NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })).getObfuscatedCode());
         }
         }
     }]);
     }]);
 
 
@@ -2385,7 +2412,9 @@ var StringArrayCallsWrapper = function (_AbstractCustomNode_) {
                 decodeNodeTemplate: decodeNodeTemplate,
                 decodeNodeTemplate: decodeNodeTemplate,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayName: this.stringArrayName
                 stringArrayName: this.stringArrayName
-            }), NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET).getObfuscatedCode());
+            }), Object.assign({}, NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })).getObfuscatedCode());
         }
         }
     }]);
     }]);
 
 
@@ -2557,7 +2586,9 @@ var StringArrayRotateFunctionNode = function (_AbstractCustomNode_) {
                 stringArrayName: this.stringArrayName,
                 stringArrayName: this.stringArrayName,
                 stringArrayRotateValue: Utils_1.Utils.decToHex(this.stringArrayRotateValue),
                 stringArrayRotateValue: Utils_1.Utils.decToHex(this.stringArrayRotateValue),
                 whileFunctionName: whileFunctionName
                 whileFunctionName: whileFunctionName
-            }), NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET).getObfuscatedCode());
+            }), Object.assign({}, NoCustomNodesPreset_1.NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })).getObfuscatedCode());
         }
         }
     }]);
     }]);
 
 
@@ -2805,10 +2836,7 @@ var StringArrayNodesGroup = function (_AbstractNodesGroup_) {
                 return;
                 return;
             }
             }
             if (this.options.rotateStringArray) {
             if (this.options.rotateStringArray) {
-                this.stringArrayRotateValue = Utils_1.Utils.getRandomGenerator().integer({
-                    min: 100,
-                    max: 500
-                });
+                this.stringArrayRotateValue = Utils_1.Utils.getRandomInteger(100, 500);
             } else {
             } else {
                 this.stringArrayRotateValue = 0;
                 this.stringArrayRotateValue = 0;
             }
             }
@@ -3593,6 +3621,7 @@ __decorate([class_validator_1.IsArray(), class_validator_1.ArrayUnique(), class_
     each: true
     each: true
 }), __metadata('design:type', Array)], Options.prototype, "reservedNames", void 0);
 }), __metadata('design:type', Array)], Options.prototype, "reservedNames", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "rotateStringArray", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "rotateStringArray", void 0);
+__decorate([class_validator_1.IsNumber(), __metadata('design:type', Number)], Options.prototype, "seed", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "selfDefending", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "selfDefending", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "sourceMap", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "sourceMap", void 0);
 __decorate([class_validator_1.IsString(), class_validator_1.ValidateIf(function (options) {
 __decorate([class_validator_1.IsString(), class_validator_1.ValidateIf(function (options) {
@@ -4271,7 +4300,7 @@ exports.DebugProtectionFunctionIntervalTemplate = DebugProtectionFunctionInterva
 
 
 var Utils_1 = __webpack_require__(0);
 var Utils_1 = __webpack_require__(0);
 function DebugProtectionFunctionTemplate() {
 function DebugProtectionFunctionTemplate() {
-    return '\n        var {debugProtectionFunctionName} = function () {\n            function debuggerProtection (counter) {\n                if ((\'\' + counter / counter)[\'length\'] !== 1 || counter % 20 === 0) {\n                    (function () {}.constructor(\'debugger\')());\n                } else {\n                    [].filter.constructor(' + Utils_1.Utils.stringToJSFuck('debugger') + ')();\n                }\n                \n                debuggerProtection(++counter);\n            }\n            \n            try {\n                debuggerProtection(0);\n            } catch (y) {}\n        };\n    ';
+    return '\n        var {debugProtectionFunctionName} = function () {\n            function debuggerProtection (counter) {\n                if ((\'\' + counter / counter)[\'length\'] !== 1 || counter % 20 === 0) {\n                    (function () {}.constructor(' + Utils_1.Utils.stringToJSFuck('debugger') + ')());\n                } else {\n                    (function () {}.constructor(' + Utils_1.Utils.stringToJSFuck('debugger') + ')());\n                }\n                \n                debuggerProtection(++counter);\n            }\n            \n            try {\n                debuggerProtection(0);\n            } catch (y) {}\n        };\n    ';
 }
 }
 exports.DebugProtectionFunctionTemplate = DebugProtectionFunctionTemplate;
 exports.DebugProtectionFunctionTemplate = DebugProtectionFunctionTemplate;
 
 
@@ -4432,8 +4461,7 @@ module.exports = require("mkdirp");
 
 
 /***/ },
 /***/ },
 /* 87 */,
 /* 87 */,
-/* 88 */,
-/* 89 */
+/* 88 */
 /***/ function(module, exports, __webpack_require__) {
 /***/ function(module, exports, __webpack_require__) {
 
 
 "use strict";
 "use strict";

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "0.8.1",
+  "version": "0.8.2",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",

+ 7 - 0
src/JavaScriptObfuscatorInternal.ts

@@ -2,6 +2,8 @@ import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
+import { Chance } from 'chance';
+
 import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
@@ -11,6 +13,7 @@ import { ObfuscationResult } from './ObfuscationResult';
 import { Obfuscator } from './Obfuscator';
 import { Obfuscator } from './Obfuscator';
 import { Options } from './options/Options';
 import { Options } from './options/Options';
 import { SourceMapCorrector } from './SourceMapCorrector';
 import { SourceMapCorrector } from './SourceMapCorrector';
+import { Utils } from './Utils';
 
 
 export class JavaScriptObfuscatorInternal {
 export class JavaScriptObfuscatorInternal {
     /**
     /**
@@ -91,6 +94,10 @@ export class JavaScriptObfuscatorInternal {
             loc: true
             loc: true
         });
         });
 
 
+        if (this.options.seed !== 0) {
+            Utils.setRandomGenerator(new Chance(this.options.seed));
+        }
+
         astTree = new Obfuscator(this.options).obfuscateNode(astTree);
         astTree = new Obfuscator(this.options).obfuscateNode(astTree);
 
 
         this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
         this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);

+ 42 - 7
src/Utils.ts

@@ -4,9 +4,9 @@ import { JSFuck } from './enums/JSFuck';
 
 
 export class Utils {
 export class Utils {
     /**
     /**
-     * @type {Chance.Chance}
+     * @type {Chance.Chance | Chance.SeededChance}
      */
      */
-    private static randomGenerator: Chance.Chance = new Chance();
+    private static randomGenerator: Chance.Chance | Chance.SeededChance = new Chance();
 
 
     /**
     /**
      * @param array
      * @param array
@@ -99,13 +99,44 @@ export class Utils {
         return domain;
         return domain;
     }
     }
 
 
+    /**
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    public static getRandomFloat (min: number, max: number): number {
+        return Utils.getRandomGenerator().floating({
+            min: min,
+            max: max,
+            fixed: 7
+        });
+    }
+
     /**
     /**
      * @returns {Chance.Chance}
      * @returns {Chance.Chance}
      */
      */
     public static getRandomGenerator (): Chance.Chance {
     public static getRandomGenerator (): Chance.Chance {
+        const randomGenerator: Chance.Chance = Utils.randomGenerator;
+
+        if (!randomGenerator) {
+            throw new Error(`\`randomGenerator\` static property is undefined`);
+        }
+
         return Utils.randomGenerator;
         return Utils.randomGenerator;
     }
     }
 
 
+    /**
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    public static getRandomInteger (min: number, max: number): number {
+        return Utils.getRandomGenerator().integer({
+            min: min,
+            max: max
+        });
+    }
+
     /**
     /**
      * @param length
      * @param length
      * @returns {string}
      * @returns {string}
@@ -117,10 +148,7 @@ export class Utils {
 
 
         return `${prefix}${(
         return `${prefix}${(
             Utils.decToHex(
             Utils.decToHex(
-                Utils.getRandomGenerator().integer({
-                    min: rangeMinInteger,
-                    max: rangeMaxInteger
-                })
+                Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
             )
             )
         ).substr(0, length)}`;
         ).substr(0, length)}`;
     }
     }
@@ -140,7 +168,7 @@ export class Utils {
                 result: string = '';
                 result: string = '';
 
 
             while (i1 < s1.length || i2 < s2.length) {
             while (i1 < s1.length || i2 < s2.length) {
-                if (Math.random() < 0.5 && i2 < s2.length) {
+                if (Utils.getRandomFloat(0, 1) < 0.5 && i2 < s2.length) {
                     result += s2.charAt(++i2);
                     result += s2.charAt(++i2);
                 } else {
                 } else {
                     result += s1.charAt(++i1);
                     result += s1.charAt(++i1);
@@ -213,6 +241,13 @@ export class Utils {
         return result;
         return result;
     }
     }
 
 
+    /**
+     * @param randomGenerator
+     */
+    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
+        Utils.randomGenerator = randomGenerator;
+    }
+
     /**
     /**
      * @param obj
      * @param obj
      * @returns {T}
      * @returns {T}

+ 1 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -151,6 +151,7 @@ export class JavaScriptObfuscatorCLI {
             .option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--seed <number>', 'Sets seed for random generator. This is useful for creating repeatable results.', parseFloat)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')
             .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')

+ 1 - 4
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts

@@ -40,10 +40,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
      */
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
         let programBodyLength: number = blockScopeNode.body.length,
         let programBodyLength: number = blockScopeNode.body.length,
-            randomIndex: number = Utils.getRandomGenerator().integer({
-                min: 0,
-                max: programBodyLength
-            });
+            randomIndex: number = Utils.getRandomInteger(0, programBodyLength);
 
 
         NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
         NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
     }
     }

+ 8 - 3
src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts

@@ -79,9 +79,14 @@ export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     protected getNodeStructure (): TStatement[] {
     protected getNodeStructure (): TStatement[] {
         if (this.appendState === AppendState.AfterObfuscation) {
         if (this.appendState === AppendState.AfterObfuscation) {
             return NodeUtils.convertCodeToStructure(
             return NodeUtils.convertCodeToStructure(
-                JavaScriptObfuscator.obfuscate(SingleNodeCallControllerTemplate().formatUnicorn({
-                    singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                }), NO_CUSTOM_NODES_PRESET).getObfuscatedCode()
+                JavaScriptObfuscator.obfuscate(
+                    SingleNodeCallControllerTemplate().formatUnicorn({
+                        singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+                    }),
+                    Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                        seed: this.options.seed
+                    })
+                ).getObfuscatedCode()
             );
             );
         }
         }
 
 

+ 3 - 1
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -78,7 +78,9 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
                     selfDefendingFunctionName: Utils.getRandomVariableName(),
                     selfDefendingFunctionName: Utils.getRandomVariableName(),
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 }),
                 }),
-                NO_CUSTOM_NODES_PRESET
+                Object.assign({},  NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })
             ).getObfuscatedCode()
             ).getObfuscatedCode()
         );
         );
     }
     }

+ 3 - 1
src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts

@@ -139,7 +139,9 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
                     stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                     stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                     stringArrayName: this.stringArrayName
                     stringArrayName: this.stringArrayName
                 }),
                 }),
-                NO_CUSTOM_NODES_PRESET
+                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })
             ).getObfuscatedCode()
             ).getObfuscatedCode()
         );
         );
     }
     }

+ 3 - 1
src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts

@@ -103,7 +103,9 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
                     stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
                     stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
                     whileFunctionName
                     whileFunctionName
                 }),
                 }),
-                NO_CUSTOM_NODES_PRESET
+                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })
             ).getObfuscatedCode()
             ).getObfuscatedCode()
         );
         );
     }
     }

+ 0 - 2
src/enums/JSFuck.ts

@@ -1,6 +1,4 @@
 export const JSFuck: any = {
 export const JSFuck: any = {
-    Window: '[]["filter"]["constructor"]("return this")()',
-
     False: '![]',
     False: '![]',
     True: '!![]',
     True: '!![]',
 
 

+ 1 - 0
src/interfaces/IObfuscatorOptions.d.ts

@@ -9,6 +9,7 @@ export interface IObfuscatorOptions {
     domainLock?: string[];
     domainLock?: string[];
     reservedNames?: string[];
     reservedNames?: string[];
     rotateStringArray?: boolean;
     rotateStringArray?: boolean;
+    seed?: number;
     selfDefending?: boolean;
     selfDefending?: boolean;
     sourceMap?: boolean;
     sourceMap?: boolean;
     sourceMapBaseUrl?: string;
     sourceMapBaseUrl?: string;

+ 1 - 0
src/interfaces/IOptions.d.ts

@@ -9,6 +9,7 @@ export interface IOptions {
     readonly domainLock: string[];
     readonly domainLock: string[];
     readonly reservedNames: string[];
     readonly reservedNames: string[];
     readonly rotateStringArray: boolean;
     readonly rotateStringArray: boolean;
+    readonly seed: number;
     readonly selfDefending: boolean;
     readonly selfDefending: boolean;
     readonly sourceMap: boolean;
     readonly sourceMap: boolean;
     readonly sourceMapBaseUrl: string;
     readonly sourceMapBaseUrl: string;

+ 1 - 4
src/node-groups/StringArrayNodesGroup.ts

@@ -40,10 +40,7 @@ export class StringArrayNodesGroup extends AbstractNodesGroup {
         }
         }
 
 
         if (this.options.rotateStringArray) {
         if (this.options.rotateStringArray) {
-            this.stringArrayRotateValue = Utils.getRandomGenerator().integer({
-                min: 100,
-                max: 500
-            });
+            this.stringArrayRotateValue = Utils.getRandomInteger(100, 500);
         } else {
         } else {
             this.stringArrayRotateValue = 0;
             this.stringArrayRotateValue = 0;
         }
         }

+ 1 - 1
src/node-obfuscators/replacers/StringLiteralReplacer.ts

@@ -27,7 +27,7 @@ export class StringLiteralReplacer extends AbstractReplacer {
     public replace (nodeValue: string): string {
     public replace (nodeValue: string): string {
         const replaceWithStringArrayFlag: boolean = (
         const replaceWithStringArrayFlag: boolean = (
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && Math.random() <= this.options.stringArrayThreshold
+            && Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
         );
         );
 
 
         if (this.options.stringArray && replaceWithStringArrayFlag) {
         if (this.options.stringArray && replaceWithStringArrayFlag) {

+ 1 - 4
src/node/NodeAppender.ts

@@ -101,10 +101,7 @@ export class NodeAppender {
      * @param stackTraceRootLength
      * @param stackTraceRootLength
      */
      */
     public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
     public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
-        return Utils.getRandomGenerator().integer({
-            min: 0,
-            max: Math.max(0, Math.round(stackTraceRootLength - 1))
-        });
+        return Utils.getRandomInteger(0, Math.max(0, Math.round(stackTraceRootLength - 1)));
     }
     }
 
 
     /**
     /**

+ 6 - 0
src/options/Options.ts

@@ -85,6 +85,12 @@ export class Options implements IOptions {
     @IsBoolean()
     @IsBoolean()
     public readonly rotateStringArray: boolean;
     public readonly rotateStringArray: boolean;
 
 
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly seed: number;
+
     /**
     /**
      * @type {boolean}
      * @type {boolean}
      */
      */

+ 1 - 0
src/preset-options/DefaultPreset.ts

@@ -10,6 +10,7 @@ export const DEFAULT_PRESET: IObfuscatorOptions = Object.freeze({
     domainLock: [],
     domainLock: [],
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     selfDefending: true,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',

+ 1 - 0
src/preset-options/NoCustomNodesPreset.ts

@@ -10,6 +10,7 @@ export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     domainLock: [],
     domainLock: [],
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: false,
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     selfDefending: false,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',

+ 2 - 2
src/templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate.ts

@@ -8,9 +8,9 @@ export function DebugProtectionFunctionTemplate (): string {
         var {debugProtectionFunctionName} = function () {
         var {debugProtectionFunctionName} = function () {
             function debuggerProtection (counter) {
             function debuggerProtection (counter) {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
-                    (function () {}.constructor('debugger')());
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 } else {
                 } else {
-                    [].filter.constructor(${Utils.stringToJSFuck('debugger')})();
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 }
                 }
                 
                 
                 debuggerProtection(++counter);
                 debuggerProtection(++counter);

+ 36 - 0
test/functional-tests/JavaScriptObfuscator.spec.ts

@@ -3,6 +3,7 @@ import { IObfuscationResult } from '../../src/interfaces/IObfuscationResult';
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 
 
 import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
 import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { readFileAsString } from '../helpers/readFileAsString';
 
 
 const assert: Chai.AssertStatic = require('chai').assert;
 const assert: Chai.AssertStatic = require('chai').assert;
 
 
@@ -125,5 +126,40 @@ describe('JavaScriptObfuscator', () => {
             assert.match(obfuscatedCode2, pattern1);
             assert.match(obfuscatedCode2, pattern1);
             assert.match(obfuscatedCode2, pattern2);
             assert.match(obfuscatedCode2, pattern2);
         });
         });
+
+        it('should returns same code every time with same `seed`', () => {
+            const code: string = readFileAsString('./test/fixtures/sample.js');
+            const seed: number = 12345;
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: seed }
+            );
+            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: seed }
+            );
+
+            assert.equal(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
+        });
+
+        it('should returns different code with different `seed` option value', () => {
+            const code: string = readFileAsString('./test/fixtures/sample.js');
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 12345 }
+            );
+            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 12346 }
+            );
+
+            const obfuscationResult3: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 0 }
+            );
+            const obfuscationResult4: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 0 }
+            );
+
+            assert.notEqual(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
+            assert.notEqual(obfuscationResult3.getObfuscatedCode(), obfuscationResult4.getObfuscatedCode());
+        });
     });
     });
 });
 });