Selaa lähdekoodia

`seed` option and `IE8` fix

sanex3339 8 vuotta sitten
vanhempi
commit
8f3441a4f9

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 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
 ---
 * `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,
     reservedNames: [],
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
@@ -148,6 +149,7 @@ Following options available for the JS Obfuscator:
     --disableConsoleOutput <boolean>
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
+    --seed <number>
     --selfDefending <boolean>
     --sourceMap <boolean>
     --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.
 
+### `seed`
+Type: `number` Default: `0`
+
+This option sets seed for random generator. This is useful for creating repeatable results.
 
 ### `selfDefending`
 Type: `boolean` Default: `true`

+ 56 - 28
dist/index.js

@@ -88,7 +88,7 @@ module.exports =
 /******/ 	__webpack_require__.p = "";
 /******/
 /******/ 	// 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];
             return domain;
         }
+    }, {
+        key: 'getRandomFloat',
+        value: function getRandomFloat(min, max) {
+            return Utils.getRandomGenerator().floating({
+                min: min,
+                max: max,
+                fixed: 7
+            });
+        }
     }, {
         key: 'getRandomGenerator',
         value: function getRandomGenerator() {
+            var randomGenerator = Utils.randomGenerator;
+            if (!randomGenerator) {
+                throw new Error('`randomGenerator` static property is undefined');
+            }
             return Utils.randomGenerator;
         }
+    }, {
+        key: 'getRandomInteger',
+        value: function getRandomInteger(min, max) {
+            return Utils.getRandomGenerator().integer({
+                min: min,
+                max: max
+            });
+        }
     }, {
         key: 'getRandomVariableName',
         value: function getRandomVariableName() {
@@ -180,10 +201,7 @@ var Utils = function () {
             var rangeMinInteger = 10000,
                 rangeMaxInteger = 99999999,
                 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',
@@ -196,7 +214,7 @@ var Utils = function () {
                     i2 = -1,
                     result = '';
                 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);
                     } else {
                         result += s1.charAt(++i1);
@@ -245,6 +263,11 @@ var Utils = function () {
             }
             return result;
         }
+    }, {
+        key: 'setRandomGenerator',
+        value: function setRandomGenerator(randomGenerator) {
+            Utils.randomGenerator = randomGenerator;
+        }
     }, {
         key: 'strEnumify',
         value: function strEnumify(obj) {
@@ -640,10 +663,7 @@ var NodeAppender = function () {
     }, {
         key: 'getRandomStackTraceIndex',
         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',
@@ -1003,6 +1023,7 @@ exports.NO_CUSTOM_NODES_PRESET = Object.freeze({
     domainLock: [],
     reservedNames: [],
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
@@ -1070,7 +1091,9 @@ var NodeCallsControllerFunctionNode = function (_AbstractCustomNode_) {
             if (this.appendState === AppendState_1.AppendState.AfterObfuscation) {
                 return NodeUtils_1.NodeUtils.convertCodeToStructure(JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(SingleNodeCallControllerTemplate_1.SingleNodeCallControllerTemplate().formatUnicorn({
                     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({
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
@@ -1127,7 +1150,7 @@ var StringLiteralReplacer = function (_AbstractReplacer_1$A) {
     _createClass(StringLiteralReplacer, [{
         key: 'replace',
         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) {
                 return this.replaceStringLiteralWithStringArrayCall(nodeValue);
             }
@@ -1233,7 +1256,6 @@ exports.ObfuscationResult = ObfuscationResult;
 "use strict";
 
 exports.JSFuck = {
-    Window: '[]["filter"]["constructor"]("return this")()',
     False: '![]',
     True: '!![]',
     a: '(false+"")[1]',
@@ -1349,6 +1371,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     domainLock: [],
     reservedNames: [],
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
@@ -1391,10 +1414,12 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
 
 var esprima = __webpack_require__(24);
 var escodegen = __webpack_require__(13);
+var chance_1 = __webpack_require__(82);
 var ObfuscationResult_1 = __webpack_require__(20);
 var Obfuscator_1 = __webpack_require__(28);
 var Options_1 = __webpack_require__(57);
 var SourceMapCorrector_1 = __webpack_require__(29);
+var Utils_1 = __webpack_require__(0);
 
 var JavaScriptObfuscatorInternal = function () {
     function JavaScriptObfuscatorInternal(sourceCode) {
@@ -1417,6 +1442,9 @@ var JavaScriptObfuscatorInternal = function () {
             var astTree = esprima.parse(this.sourceCode, {
                 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);
             this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
         }
@@ -1842,7 +1870,7 @@ var JavaScriptObfuscatorCLI = function () {
                 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) {
                 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 () {
                 console.log('  Examples:\n');
                 console.log('    %> javascript-obfuscator in.js --compact true --selfDefending false');
@@ -2131,10 +2159,7 @@ var DebugProtectionFunctionNode = function (_AbstractCustomNode_) {
         key: 'appendNode',
         value: function appendNode(blockScopeNode) {
             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);
         }
     }, {
@@ -2273,7 +2298,9 @@ var SelfDefendingUnicodeNode = function (_AbstractCustomNode_) {
             return NodeUtils_1.NodeUtils.convertCodeToStructure(JavaScriptObfuscator_1.JavaScriptObfuscator.obfuscate(SelfDefendingTemplate_1.SelfDefendingTemplate().formatUnicorn({
                 selfDefendingFunctionName: Utils_1.Utils.getRandomVariableName(),
                 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,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 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,
                 stringArrayRotateValue: Utils_1.Utils.decToHex(this.stringArrayRotateValue),
                 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;
             }
             if (this.options.rotateStringArray) {
-                this.stringArrayRotateValue = Utils_1.Utils.getRandomGenerator().integer({
-                    min: 100,
-                    max: 500
-                });
+                this.stringArrayRotateValue = Utils_1.Utils.getRandomInteger(100, 500);
             } else {
                 this.stringArrayRotateValue = 0;
             }
@@ -3593,6 +3621,7 @@ __decorate([class_validator_1.IsArray(), class_validator_1.ArrayUnique(), class_
     each: true
 }), __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.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, "sourceMap", void 0);
 __decorate([class_validator_1.IsString(), class_validator_1.ValidateIf(function (options) {
@@ -4271,7 +4300,7 @@ exports.DebugProtectionFunctionIntervalTemplate = DebugProtectionFunctionInterva
 
 var Utils_1 = __webpack_require__(0);
 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;
 
@@ -4432,8 +4461,7 @@ module.exports = require("mkdirp");
 
 /***/ },
 /* 87 */,
-/* 88 */,
-/* 89 */
+/* 88 */
 /***/ function(module, exports, __webpack_require__) {
 
 "use strict";

+ 1 - 1
package.json

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

+ 7 - 0
src/JavaScriptObfuscatorInternal.ts

@@ -2,6 +2,8 @@ import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
+import { Chance } from 'chance';
+
 import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
@@ -11,6 +13,7 @@ import { ObfuscationResult } from './ObfuscationResult';
 import { Obfuscator } from './Obfuscator';
 import { Options } from './options/Options';
 import { SourceMapCorrector } from './SourceMapCorrector';
+import { Utils } from './Utils';
 
 export class JavaScriptObfuscatorInternal {
     /**
@@ -91,6 +94,10 @@ export class JavaScriptObfuscatorInternal {
             loc: true
         });
 
+        if (this.options.seed !== 0) {
+            Utils.setRandomGenerator(new Chance(this.options.seed));
+        }
+
         astTree = new Obfuscator(this.options).obfuscateNode(astTree);
 
         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 {
     /**
-     * @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
@@ -99,13 +99,44 @@ export class Utils {
         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}
      */
     public static getRandomGenerator (): Chance.Chance {
+        const randomGenerator: Chance.Chance = Utils.randomGenerator;
+
+        if (!randomGenerator) {
+            throw new Error(`\`randomGenerator\` static property is undefined`);
+        }
+
         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
      * @returns {string}
@@ -117,10 +148,7 @@ export class Utils {
 
         return `${prefix}${(
             Utils.decToHex(
-                Utils.getRandomGenerator().integer({
-                    min: rangeMinInteger,
-                    max: rangeMaxInteger
-                })
+                Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
             )
         ).substr(0, length)}`;
     }
@@ -140,7 +168,7 @@ export class Utils {
                 result: string = '';
 
             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);
                 } else {
                     result += s1.charAt(++i1);
@@ -213,6 +241,13 @@ export class Utils {
         return result;
     }
 
+    /**
+     * @param randomGenerator
+     */
+    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
+        Utils.randomGenerator = randomGenerator;
+    }
+
     /**
      * @param obj
      * @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('--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('--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`')

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

@@ -40,10 +40,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
         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);
     }

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

@@ -79,9 +79,14 @@ export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     protected getNodeStructure (): TStatement[] {
         if (this.appendState === AppendState.AfterObfuscation) {
             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(),
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 }),
-                NO_CUSTOM_NODES_PRESET
+                Object.assign({},  NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })
             ).getObfuscatedCode()
         );
     }

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

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

+ 0 - 2
src/enums/JSFuck.ts

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

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

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

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

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

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

@@ -40,10 +40,7 @@ export class StringArrayNodesGroup extends AbstractNodesGroup {
         }
 
         if (this.options.rotateStringArray) {
-            this.stringArrayRotateValue = Utils.getRandomGenerator().integer({
-                min: 100,
-                max: 500
-            });
+            this.stringArrayRotateValue = Utils.getRandomInteger(100, 500);
         } else {
             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 {
         const replaceWithStringArrayFlag: boolean = (
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && Math.random() <= this.options.stringArrayThreshold
+            && Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
         );
 
         if (this.options.stringArray && replaceWithStringArrayFlag) {

+ 1 - 4
src/node/NodeAppender.ts

@@ -101,10 +101,7 @@ export class NodeAppender {
      * @param stackTraceRootLength
      */
     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()
     public readonly rotateStringArray: boolean;
 
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly seed: number;
+
     /**
      * @type {boolean}
      */

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

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

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

@@ -10,6 +10,7 @@ export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     domainLock: [],
     reservedNames: [],
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     sourceMap: false,
     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 () {
             function debuggerProtection (counter) {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
-                    (function () {}.constructor('debugger')());
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 } else {
-                    [].filter.constructor(${Utils.stringToJSFuck('debugger')})();
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 }
                 
                 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 { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { readFileAsString } from '../helpers/readFileAsString';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 
@@ -125,5 +126,40 @@ describe('JavaScriptObfuscator', () => {
             assert.match(obfuscatedCode2, pattern1);
             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());
+        });
     });
 });