瀏覽代碼

Merge branch 'dev'

# Conflicts:
#	dist/index.js
#	package.json
#	test/unit-tests/OptionsNormalizer.spec.ts
sanex3339 8 年之前
父節點
當前提交
a9cff90be5
共有 100 個文件被更改,包括 3311 次插入2637 次删除
  1. 46 2
      README.md
  2. 458 415
      dist/index.js
  3. 0 4
      index.ts
  4. 18 15
      package.json
  5. 18 11
      src/JavaScriptObfuscator.ts
  6. 62 47
      src/JavaScriptObfuscatorInternal.ts
  7. 8 1
      src/ObfuscationResult.ts
  8. 112 115
      src/Obfuscator.ts
  9. 37 42
      src/SourceMapCorrector.ts
  10. 0 53
      src/StringArray.ts
  11. 0 295
      src/Utils.ts
  12. 3 3
      src/cli/CLIUtils.ts
  13. 106 34
      src/cli/JavaScriptObfuscatorCLI.ts
  14. 110 0
      src/container/InversifyContainerFacade.ts
  15. 25 0
      src/container/ServiceIdentifiers.ts
  16. 126 0
      src/container/modules/custom-nodes/CustomNodesModule.ts
  17. 34 0
      src/container/modules/node-transformers/NodeControlFlowTransformersModule.ts
  18. 49 0
      src/container/modules/node-transformers/NodeObfuscatorsModule.ts
  19. 91 0
      src/container/modules/node-transformers/NodeTransformersModule.ts
  20. 52 0
      src/container/modules/stack-trace-analyzer/StackTraceAnalyzerModule.ts
  21. 27 0
      src/container/modules/storages/StoragesModule.ts
  22. 17 25
      src/custom-nodes/AbstractCustomNode.ts
  23. 80 0
      src/custom-nodes/AbstractCustomNodeGroup.ts
  24. 20 66
      src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts
  25. 110 0
      src/custom-nodes/console-output-nodes/group/ConsoleOutputCustomNodeGroup.ts
  26. 48 0
      src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode.ts
  27. 78 0
      src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode.ts
  28. 57 0
      src/custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts
  29. 18 26
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts
  30. 18 26
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts
  31. 17 38
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts
  32. 109 0
      src/custom-nodes/debug-protection-nodes/group/DebugProtectionCustomNodeGroup.ts
  33. 23 55
      src/custom-nodes/domain-lock-nodes/DomainLockNode.ts
  34. 110 0
      src/custom-nodes/domain-lock-nodes/group/DomainLockCustomNodeGroup.ts
  35. 35 61
      src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
  36. 25 55
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  37. 110 0
      src/custom-nodes/self-defending-nodes/group/SelfDefendingCustomNodeGroup.ts
  38. 49 64
      src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts
  39. 36 58
      src/custom-nodes/string-array-nodes/StringArrayNode.ts
  40. 44 60
      src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts
  41. 130 0
      src/custom-nodes/string-array-nodes/group/StringArrayCustomNodeGroup.ts
  42. 58 0
      src/decorators/Initializable.ts
  43. 0 4
      src/enums/AppendState.ts
  44. 1 1
      src/enums/NodeType.ts
  45. 9 0
      src/enums/ObfuscationEvents.ts
  46. 6 0
      src/enums/VisitorDirection.ts
  47. 5 0
      src/enums/container/CalleeDataExtractors.ts
  48. 7 0
      src/enums/container/CustomNodeGroups.ts
  49. 15 0
      src/enums/container/CustomNodes.ts
  50. 3 0
      src/enums/container/NodeControlFlowReplacers.ts
  51. 6 0
      src/enums/container/NodeObfuscatorsReplacers.ts
  52. 12 0
      src/enums/container/NodeTransformers.ts
  53. 10 0
      src/event-emitters/ObfuscationEventEmitter.ts
  54. 6 0
      src/interfaces/IInitializable.d.ts
  55. 5 0
      src/interfaces/IJavaScriptObfsucator.d.ts
  56. 0 9
      src/interfaces/INodeObfuscator.d.ts
  57. 0 8
      src/interfaces/INodesGroup.d.ts
  58. 3 1
      src/interfaces/IObfuscationResult.d.ts
  59. 1 1
      src/interfaces/IObfuscator.d.ts
  60. 0 23
      src/interfaces/IObfuscatorOptions.d.ts
  61. 0 3
      src/interfaces/IReplacer.d.ts
  62. 1 1
      src/interfaces/ISourceMapCorrector.d.ts
  63. 14 0
      src/interfaces/container/IInversifyContainerFacade.d.ts
  64. 5 17
      src/interfaces/custom-nodes/ICustomNode.d.ts
  65. 29 0
      src/interfaces/custom-nodes/ICustomNodeGroup.d.ts
  66. 0 5
      src/interfaces/custom-nodes/ICustomNodeWithData.d.ts
  67. 0 3
      src/interfaces/custom-nodes/ICustomNodeWithIdentifier.d.ts
  68. 8 0
      src/interfaces/event-emitters/IObfuscationEventEmitter.d.ts
  69. 13 0
      src/interfaces/node-transformers/IControlFlowReplacer.d.ts
  70. 5 0
      src/interfaces/node-transformers/INodeTransformer.d.ts
  71. 3 0
      src/interfaces/node-transformers/IReplacer.d.ts
  72. 3 2
      src/interfaces/options/IOptions.d.ts
  73. 3 1
      src/interfaces/stack-trace-analyzer/ICalleeDataExtractor.d.ts
  74. 3 1
      src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts
  75. 11 0
      src/interfaces/storages/IStorage.d.ts
  76. 0 49
      src/node-groups/AbstractNodesGroup.ts
  77. 0 43
      src/node-groups/ConsoleOutputNodesGroup.ts
  78. 0 40
      src/node-groups/DebugProtectionNodesGroup.ts
  79. 0 43
      src/node-groups/DomainLockNodesGroup.ts
  80. 0 50
      src/node-groups/SelfDefendingNodesGroup.ts
  81. 0 84
      src/node-groups/StringArrayNodesGroup.ts
  82. 0 32
      src/node-obfuscators/AbstractNodeObfuscator.ts
  83. 0 67
      src/node-obfuscators/CatchClauseObfuscator.ts
  84. 0 78
      src/node-obfuscators/FunctionDeclarationObfuscator.ts
  85. 0 82
      src/node-obfuscators/FunctionObfuscator.ts
  86. 0 75
      src/node-obfuscators/LabeledStatementObfuscator.ts
  87. 0 50
      src/node-obfuscators/LiteralObfuscator.ts
  88. 0 82
      src/node-obfuscators/MemberExpressionObfuscator.ts
  89. 0 52
      src/node-obfuscators/MethodDefinitionObfuscator.ts
  90. 0 31
      src/node-obfuscators/replacers/AbstractReplacer.ts
  91. 0 13
      src/node-obfuscators/replacers/BooleanLiteralReplacer.ts
  92. 0 18
      src/node-obfuscators/replacers/NumberLiteralReplacer.ts
  93. 0 97
      src/node-obfuscators/replacers/StringLiteralReplacer.ts
  94. 30 0
      src/node-transformers/AbstractNodeTransformer.ts
  95. 126 0
      src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts
  96. 52 0
      src/node-transformers/node-control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts
  97. 76 0
      src/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts
  98. 81 0
      src/node-transformers/node-obfuscators/CatchClauseObfuscator.ts
  99. 92 0
      src/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.ts
  100. 93 0
      src/node-transformers/node-obfuscators/FunctionObfuscator.ts

+ 46 - 2
README.md

@@ -6,7 +6,7 @@
 
 
 # JavaScript obfuscator for Node.js
 # JavaScript obfuscator for Node.js
 
 
-JavaScript obfuscator for Node.js is a free alternative to [js-obfuscator](https://github.com/caiguanhao/js-obfuscator) (which uses [javascriptobfuscator.com](https://javascriptobfuscator.com/Javascript-Obfuscator.aspx))
+JavaScript obfuscator for Node.js is a free obfuscator with wide number of features which provides protection for your source code.
 
 
 * without any limits and sending data to a server;
 * without any limits and sending data to a server;
 * compatible with ES6;
 * compatible with ES6;
@@ -118,6 +118,7 @@ Following options available for the JS Obfuscator:
 ```javascript
 ```javascript
 {
 {
     compact: true,
     compact: true,
+    controlFlowFlattening: false,
     debugProtection: false,
     debugProtection: false,
     debugProtectionInterval: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
     disableConsoleOutput: true,
@@ -144,6 +145,7 @@ Following options available for the JS Obfuscator:
     -o, --output
     -o, --output
 
 
     --compact <boolean>
     --compact <boolean>
+    --controlFlowFlattening <boolean>
     --debugProtection <boolean>
     --debugProtection <boolean>
     --debugProtectionInterval <boolean>
     --debugProtectionInterval <boolean>
     --disableConsoleOutput <boolean>
     --disableConsoleOutput <boolean>
@@ -166,6 +168,48 @@ Type: `boolean` Default: `true`
 
 
 Compact code output on one line.
 Compact code output on one line.
 
 
+### `controlFlowFlattening`
+Type: `boolean` Default: `false`
+
+Enables code control flow flattening. Control flow flattening is a structure transformation of the source code that hindering the comprehension of the program.
+
+Example:
+```ts
+// input
+(function(){
+    function foo () {
+        return function () {
+            var sum = 1 + 2;
+        }
+    }
+})();
+
+// output
+(function() {
+    var _0x451dc8 = {
+        '\x44\x64\x4f': function _0x3(_0x4ea314, _0x4fa62e) {
+            return _0x4ea314 + _0x4fa62e;
+        }
+    };
+
+    function _0x5382d8() {
+        var _0x349b22 = {
+            '\x48\x65\x61': function _0x2(_0x14a596, _0x250c4b) {
+                return _0x451dc8['\x44\x64\x4f'](_0x14a596, _0x250c4b);
+            }
+        };
+        return function() {
+            var _0x412353 = {
+                '\x73\x47\x6f': function _0x4(_0x43c6b0, _0x133730) {
+                    return _0x349b22['\x48\x65\x61'](_0x43c6b0, _0x133730);
+                }
+            };
+            var _0x1d8637 = _0x412353['\x73\x47\x6f'](0x1, 0x2);
+        };
+    }
+}());
+```
+
 ### `debugProtection`
 ### `debugProtection`
 Type: `boolean` Default: `false`
 Type: `boolean` Default: `false`
 
 
@@ -202,7 +246,7 @@ Type: `string[]` Default: `[]`
 Disables the obfuscation of variables names, function names and function parameters that match the Regular Expression used.
 Disables the obfuscation of variables names, function names and function parameters that match the Regular Expression used.
 
 
 Example:
 Example:
-```javascript
+```ts
 	{
 	{
 		reservedNames: [
 		reservedNames: [
 			'^someVariable',
 			'^someVariable',

File diff suppressed because it is too large
+ 458 - 415
dist/index.js


+ 0 - 4
index.ts

@@ -2,8 +2,4 @@
 
 
 import { JavaScriptObfuscator } from './src/JavaScriptObfuscator';
 import { JavaScriptObfuscator } from './src/JavaScriptObfuscator';
 
 
-if (!(<any>global)._babelPolyfill) {
-    require('babel-polyfill');
-}
-
 module.exports = JavaScriptObfuscator;
 module.exports = JavaScriptObfuscator;

+ 18 - 15
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "0.8.3",
+  "version": "0.9.0-dev.1",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",
@@ -20,44 +20,47 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
   },
   },
   "dependencies": {
   "dependencies": {
-    "babel-polyfill": "6.16.0",
+    "babel-polyfill": "6.20.0",
     "chance": "1.0.4",
     "chance": "1.0.4",
     "class-validator": "0.6.6",
     "class-validator": "0.6.6",
     "commander": "2.9.0",
     "commander": "2.9.0",
     "escodegen": "1.8.1",
     "escodegen": "1.8.1",
-    "esprima": "3.1.1",
+    "esprima": "3.1.2",
     "estraverse": "4.2.0",
     "estraverse": "4.2.0",
-    "format-unicorn": "1.1.0",
+    "inversify": "^3.0.0-rc.1",
+    "is-equal": "^1.5.3",
     "mkdirp": "0.5.1",
     "mkdirp": "0.5.1",
-    "source-map-support": "0.4.6"
+    "reflect-metadata": "^0.1.8",
+    "source-map-support": "0.4.6",
+    "string-template": "^1.0.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@types/chai": "3.4.34",
     "@types/chai": "3.4.34",
     "@types/chance": "0.7.31",
     "@types/chance": "0.7.31",
-    "@types/commander": "2.3.30",
+    "@types/commander": "2.3.31",
     "@types/escodegen": "0.0.6",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "2.1.33",
     "@types/esprima": "2.1.33",
     "@types/estraverse": "0.0.6",
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.34",
     "@types/estree": "0.0.34",
-    "@types/format-unicorn": "0.0.29",
     "@types/joi": "9.0.33",
     "@types/joi": "9.0.33",
     "@types/mkdirp": "0.3.29",
     "@types/mkdirp": "0.3.29",
     "@types/mocha": "2.2.33",
     "@types/mocha": "2.2.33",
-    "@types/node": "6.0.47",
-    "@types/sinon": "1.16.31",
-    "awesome-typescript-loader": "2.2.4",
+    "@types/node": "6.0.51",
+    "@types/sinon": "1.16.32",
+    "@types/string-template": "^1.0.2",
+    "awesome-typescript-loader": "^3.0.0-beta.9",
     "babel-cli": "6.18.0",
     "babel-cli": "6.18.0",
-    "babel-loader": "6.2.7",
+    "babel-loader": "6.2.9",
     "babel-preset-es2015": "6.18.0",
     "babel-preset-es2015": "6.18.0",
     "chai": "3.5.0",
     "chai": "3.5.0",
     "coveralls": "2.11.15",
     "coveralls": "2.11.15",
     "istanbul": "1.1.0-alpha.1",
     "istanbul": "1.1.0-alpha.1",
-    "mocha": "3.1.2",
+    "mocha": "3.2.0",
     "sinon": "2.0.0-pre.3",
     "sinon": "2.0.0-pre.3",
     "ts-node": "1.7.0",
     "ts-node": "1.7.0",
-    "tslint": "3.15.1",
-    "typescript": "2.0.9",
-    "webpack": "2.1.0-beta.25",
+    "tslint": "4.0.2",
+    "typescript": "2.1.4",
+    "webpack": "2.1.0-beta.27",
     "webpack-node-externals": "1.5.4"
     "webpack-node-externals": "1.5.4"
   },
   },
   "repository": {
   "repository": {

+ 18 - 11
src/JavaScriptObfuscator.ts

@@ -1,24 +1,31 @@
+import 'reflect-metadata';
+
+if (!(<any>global)._babelPolyfill) {
+    require('babel-polyfill');
+}
+
+import { TInputOptions } from './types/options/TInputOptions';
+
+import { IInversifyContainerFacade } from './interfaces/container/IInversifyContainerFacade';
+import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
-import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 
 
+import { InversifyContainerFacade } from './container/InversifyContainerFacade';
 import { JavaScriptObfuscatorCLI } from './cli/JavaScriptObfuscatorCLI';
 import { JavaScriptObfuscatorCLI } from './cli/JavaScriptObfuscatorCLI';
-import { JavaScriptObfuscatorInternal } from './JavaScriptObfuscatorInternal';
+import { ServiceIdentifiers } from './container/ServiceIdentifiers';
 
 
 export class JavaScriptObfuscator {
 export class JavaScriptObfuscator {
     /**
     /**
      * @param sourceCode
      * @param sourceCode
-     * @param obfuscatorOptions
+     * @param inputOptions
      * @returns {string}
      * @returns {string}
      */
      */
-    public static obfuscate (sourceCode: string, obfuscatorOptions: IObfuscatorOptions = {}): IObfuscationResult {
-        let javaScriptObfuscator: JavaScriptObfuscatorInternal = new JavaScriptObfuscatorInternal(
-            sourceCode,
-            obfuscatorOptions
-        );
-
-        javaScriptObfuscator.obfuscate();
+    public static obfuscate (sourceCode: string, inputOptions: TInputOptions = {}): IObfuscationResult {
+        const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade(inputOptions);
+        const javaScriptObfuscator: IJavaScriptObfuscator = inversifyContainerFacade
+            .get<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator);
 
 
-        return javaScriptObfuscator.getObfuscationResult();
+        return javaScriptObfuscator.obfuscate(sourceCode);
     }
     }
 
 
     /**
     /**

+ 62 - 47
src/JavaScriptObfuscatorInternal.ts

@@ -1,71 +1,82 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from './container/ServiceIdentifiers';
+
 import * as esprima from 'esprima';
 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 { IGeneratorOutput } from './interfaces/IGeneratorOutput';
+import { IJavaScriptObfuscator } from './interfaces/IJavaScriptObfsucator';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
-import { IOptions } from './interfaces/IOptions';
+import { IObfuscator } from './interfaces/IObfuscator';
+import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
+import { IOptions } from './interfaces/options/IOptions';
+import { ISourceMapCorrector } from './interfaces/ISourceMapCorrector';
 
 
-import { ObfuscationResult } from './ObfuscationResult';
-import { Obfuscator } from './Obfuscator';
-import { Options } from './options/Options';
-import { SourceMapCorrector } from './SourceMapCorrector';
-import { Utils } from './Utils';
+import { RandomGeneratorUtils } from './utils/RandomGeneratorUtils';
 
 
-export class JavaScriptObfuscatorInternal {
+@injectable()
+export class JavaScriptObfuscatorInternal implements IJavaScriptObfuscator {
     /**
     /**
      * @type {GenerateOptions}
      * @type {GenerateOptions}
      */
      */
-    private static escodegenParams: escodegen.GenerateOptions = {
+    private static readonly escodegenParams: escodegen.GenerateOptions = {
         verbatim: 'x-verbatim-property',
         verbatim: 'x-verbatim-property',
         sourceMapWithCode: true
         sourceMapWithCode: true
     };
     };
 
 
     /**
     /**
-     * @type {IGeneratorOutput}
+     * @type {esprima.Options}
      */
      */
-    private generatorOutput: IGeneratorOutput;
+    private static readonly esprimaParams: esprima.Options = {
+        loc: true
+    };
+
+    /**
+     * @type {IObfuscator}
+     */
+    private readonly obfuscator: IObfuscator;
 
 
     /**
     /**
      * @type {IOptions}
      * @type {IOptions}
      */
      */
-    private options: IOptions;
+    private readonly options: IOptions;
 
 
     /**
     /**
-     * @type {string}
+     * @type {ISourceMapCorrector}
      */
      */
-    private sourceCode: string;
+    private readonly sourceMapCorrector: ISourceMapCorrector;
 
 
     /**
     /**
-     * @param sourceCode
-     * @param obfuscatorOptions
+     * @param obfuscator
+     * @param sourceMapCorrector
+     * @param options
      */
      */
-    constructor (sourceCode: string, obfuscatorOptions: IObfuscatorOptions = {}) {
-        this.sourceCode = sourceCode;
-        this.options = new Options(obfuscatorOptions);
+    constructor (
+        @inject(ServiceIdentifiers.IObfuscator) obfuscator: IObfuscator,
+        @inject(ServiceIdentifiers.ISourceMapCorrector) sourceMapCorrector: ISourceMapCorrector,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.obfuscator = obfuscator;
+        this.sourceMapCorrector = sourceMapCorrector;
+        this.options = options;
     }
     }
 
 
     /**
     /**
      * @param sourceCode
      * @param sourceCode
      * @param astTree
      * @param astTree
-     * @param options
      */
      */
-    private static generateCode (sourceCode: string, astTree: ESTree.Node, options: IOptions): IGeneratorOutput {
-        const escodegenParams: escodegen.GenerateOptions = Object.assign(
-            {},
-            JavaScriptObfuscatorInternal.escodegenParams
-        );
+    private generateCode (sourceCode: string, astTree: ESTree.Program): IGeneratorOutput {
+        const escodegenParams: escodegen.GenerateOptions = {
+            ...JavaScriptObfuscatorInternal.escodegenParams
+        };
 
 
-        if (options.sourceMap) {
+        if (this.options.sourceMap) {
             escodegenParams.sourceMap = 'sourceMap';
             escodegenParams.sourceMap = 'sourceMap';
             escodegenParams.sourceContent = sourceCode;
             escodegenParams.sourceContent = sourceCode;
         }
         }
 
 
         escodegenParams.format = {
         escodegenParams.format = {
-            compact: options.compact
+            compact: this.options.compact
         };
         };
 
 
         const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
         const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
@@ -76,30 +87,34 @@ export class JavaScriptObfuscatorInternal {
     }
     }
 
 
     /**
     /**
+     * @param generatorOutput
      * @returns {IObfuscationResult}
      * @returns {IObfuscationResult}
      */
      */
-    public getObfuscationResult (): IObfuscationResult {
-        return new SourceMapCorrector(
-            new ObfuscationResult(
-                this.generatorOutput.code,
-                this.generatorOutput.map
-            ),
-            this.options.sourceMapBaseUrl + this.options.sourceMapFileName,
-            this.options.sourceMapMode
-        ).correct();
+    private getObfuscationResult (generatorOutput: IGeneratorOutput): IObfuscationResult {
+        return this.sourceMapCorrector.correct(
+            generatorOutput.code,
+            generatorOutput.map
+        );
     }
     }
 
 
-    public obfuscate (): void {
-        let astTree: ESTree.Node = esprima.parse(this.sourceCode, {
-            loc: true
-        });
-
+    /**
+     * @param sourceCode
+     * @returns {IObfuscationResult}
+     */
+    public obfuscate (sourceCode: string): IObfuscationResult {
         if (this.options.seed !== 0) {
         if (this.options.seed !== 0) {
-            Utils.setRandomGenerator(new Chance(this.options.seed));
+            RandomGeneratorUtils.setRandomGeneratorSeed(this.options.seed);
         }
         }
 
 
-        astTree = new Obfuscator(this.options).obfuscateNode(astTree);
+        // parse AST tree
+        const astTree: ESTree.Program = esprima.parse(sourceCode, JavaScriptObfuscatorInternal.esprimaParams);
+
+        // obfuscate AST tree
+        const obfuscatedAstTree: ESTree.Program = this.obfuscator.obfuscateAstTree(astTree);
+
+        // generate code
+        const generatorOutput: IGeneratorOutput = this.generateCode(sourceCode, obfuscatedAstTree);
 
 
-        this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
+        return this.getObfuscationResult(generatorOutput);
     }
     }
 }
 }

+ 8 - 1
src/ObfuscationResult.ts

@@ -1,21 +1,28 @@
+import { injectable } from 'inversify';
+
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 
 
+import { initializable } from './decorators/Initializable';
+
+@injectable()
 export class ObfuscationResult implements IObfuscationResult {
 export class ObfuscationResult implements IObfuscationResult {
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private obfuscatedCode: string;
     private obfuscatedCode: string;
 
 
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private sourceMap: string;
     private sourceMap: string;
 
 
     /**
     /**
      * @param obfuscatedCode
      * @param obfuscatedCode
      * @param sourceMap
      * @param sourceMap
      */
      */
-    constructor (obfuscatedCode: string, sourceMap: string) {
+    public initialize (obfuscatedCode: string, sourceMap: string): void {
         this.obfuscatedCode = obfuscatedCode;
         this.obfuscatedCode = obfuscatedCode;
         this.sourceMap = sourceMap;
         this.sourceMap = sourceMap;
     }
     }

+ 112 - 115
src/Obfuscator.ts

@@ -1,173 +1,170 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from './container/ServiceIdentifiers';
+
 import * as estraverse from 'estraverse';
 import * as estraverse from 'estraverse';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
-import { TNodeGroup } from './types/TNodeGroup';
-import { TNodeObfuscator } from './types/TNodeObfuscator';
+import { TNodeTransformersFactory } from './types/container/TNodeTransformersFactory';
+import { TVisitorDirection } from './types/TVisitorDirection';
 
 
-import { ICustomNode } from './interfaces/custom-nodes/ICustomNode';
+import { ICustomNodeGroup } from './interfaces/custom-nodes/ICustomNodeGroup';
+import { IObfuscationEventEmitter } from './interfaces/event-emitters/IObfuscationEventEmitter';
 import { IObfuscator } from './interfaces/IObfuscator';
 import { IObfuscator } from './interfaces/IObfuscator';
-import { IOptions } from './interfaces/IOptions';
+import { IOptions } from './interfaces/options/IOptions';
+import { INodeTransformer } from './interfaces/node-transformers/INodeTransformer';
+import { IStackTraceAnalyzer } from './interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
+import { IStorage } from './interfaces/storages/IStorage';
 
 
-import { AppendState } from './enums/AppendState';
+import { NodeTransformers } from './enums/container/NodeTransformers';
 import { NodeType } from './enums/NodeType';
 import { NodeType } from './enums/NodeType';
+import { ObfuscationEvents } from './enums/ObfuscationEvents';
+import { VisitorDirection } from './enums/VisitorDirection';
 
 
-import { CatchClauseObfuscator } from './node-obfuscators/CatchClauseObfuscator';
-import { ConsoleOutputNodesGroup } from './node-groups/ConsoleOutputNodesGroup';
-import { DebugProtectionNodesGroup } from './node-groups/DebugProtectionNodesGroup';
-import { DomainLockNodesGroup } from './node-groups/DomainLockNodesGroup';
-import { FunctionDeclarationObfuscator } from './node-obfuscators/FunctionDeclarationObfuscator';
-import { FunctionObfuscator } from './node-obfuscators/FunctionObfuscator';
-import { LabeledStatementObfuscator } from './node-obfuscators/LabeledStatementObfuscator';
-import { LiteralObfuscator } from './node-obfuscators/LiteralObfuscator';
-import { MemberExpressionObfuscator } from './node-obfuscators/MemberExpressionObfuscator';
-import { MethodDefinitionObfuscator } from './node-obfuscators/MethodDefinitionObfuscator';
 import { Node } from './node/Node';
 import { Node } from './node/Node';
 import { NodeUtils } from './node/NodeUtils';
 import { NodeUtils } from './node/NodeUtils';
-import { ObjectExpressionObfuscator } from './node-obfuscators/ObjectExpressionObfuscator';
-import { SelfDefendingNodesGroup } from './node-groups/SelfDefendingNodesGroup';
-import { StackTraceAnalyzer } from './stack-trace-analyzer/StackTraceAnalyzer';
-import { StringArrayNodesGroup } from './node-groups/StringArrayNodesGroup';
-import { VariableDeclarationObfuscator } from './node-obfuscators/VariableDeclarationObfuscator';
 
 
+@injectable()
 export class Obfuscator implements IObfuscator {
 export class Obfuscator implements IObfuscator {
     /**
     /**
-     * @type {TNodeGroup[]}
+     * @type {Map<string, NodeTransformers[]>}
      */
      */
-    private static nodeGroups: TNodeGroup[] = [
-        DomainLockNodesGroup,
-        SelfDefendingNodesGroup,
-        ConsoleOutputNodesGroup,
-        DebugProtectionNodesGroup,
-        StringArrayNodesGroup
-    ];
+    private static readonly nodeControlFlowTransformersMap: Map <string, NodeTransformers[]> = new Map <string, NodeTransformers[]> ([
+        [NodeType.FunctionDeclaration, [NodeTransformers.FunctionControlFlowTransformer]],
+        [NodeType.FunctionExpression, [NodeTransformers.FunctionControlFlowTransformer]]
+    ]);
 
 
     /**
     /**
-     * @type {Map<string, TNodeObfuscator[]>}
+     * @type {Map<string, NodeTransformers[]>}
      */
      */
-    private static nodeObfuscators: Map <string, TNodeObfuscator[]> = new Map <string, TNodeObfuscator[]> ([
-        [NodeType.ArrowFunctionExpression, [FunctionObfuscator]],
-        [NodeType.ClassDeclaration, [FunctionDeclarationObfuscator]],
-        [NodeType.CatchClause, [CatchClauseObfuscator]],
+    private static readonly nodeObfuscatorsMap: Map <string, NodeTransformers[]> = new Map <string, NodeTransformers[]> ([
+        [NodeType.ArrowFunctionExpression, [NodeTransformers.FunctionObfuscator]],
+        [NodeType.ClassDeclaration, [NodeTransformers.FunctionDeclarationObfuscator]],
+        [NodeType.CatchClause, [NodeTransformers.CatchClauseObfuscator]],
         [NodeType.FunctionDeclaration, [
         [NodeType.FunctionDeclaration, [
-            FunctionDeclarationObfuscator,
-            FunctionObfuscator
+            NodeTransformers.FunctionDeclarationObfuscator,
+            NodeTransformers.FunctionObfuscator
         ]],
         ]],
-        [NodeType.FunctionExpression, [FunctionObfuscator]],
-        [NodeType.MemberExpression, [MemberExpressionObfuscator]],
-        [NodeType.MethodDefinition, [MethodDefinitionObfuscator]],
-        [NodeType.ObjectExpression, [ObjectExpressionObfuscator]],
-        [NodeType.VariableDeclaration, [VariableDeclarationObfuscator]],
-        [NodeType.LabeledStatement, [LabeledStatementObfuscator]],
-        [NodeType.Literal, [LiteralObfuscator]]
+        [NodeType.FunctionExpression, [NodeTransformers.FunctionObfuscator]],
+        [NodeType.MemberExpression, [NodeTransformers.MemberExpressionObfuscator]],
+        [NodeType.MethodDefinition, [NodeTransformers.MethodDefinitionObfuscator]],
+        [NodeType.ObjectExpression, [NodeTransformers.ObjectExpressionObfuscator]],
+        [NodeType.VariableDeclaration, [NodeTransformers.VariableDeclarationObfuscator]],
+        [NodeType.LabeledStatement, [NodeTransformers.LabeledStatementObfuscator]],
+        [NodeType.Literal, [NodeTransformers.LiteralObfuscator]]
     ]);
     ]);
 
 
     /**
     /**
-     * @type {Map<string, AbstractCustomNode>}
+     * @type {IStorage<ICustomNodeGroup>}
      */
      */
-    private customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ();
+    private readonly customNodeGroupStorage: IStorage<ICustomNodeGroup>;
 
 
     /**
     /**
-     * @type {IOptions}
+     * @type {TNodeTransformersFactory}
      */
      */
-    private options: IOptions;
+    private readonly nodeTransformersFactory: TNodeTransformersFactory;
 
 
     /**
     /**
-     * @param options
+     * @type {IObfuscationEventEmitter}
      */
      */
-    constructor (options: IOptions) {
-        this.options = options;
-    }
+    private readonly obfuscationEventEmitter: IObfuscationEventEmitter;
 
 
     /**
     /**
-     * @param node
-     * @returns {ESTree.Node}
+     * @type {IOptions}
      */
      */
-    public obfuscateNode (node: ESTree.Program): ESTree.Node {
-        if (Node.isProgramNode(node) && !node.body.length) {
-            return node;
-        }
-
-        NodeUtils.parentize(node);
+    private readonly options: IOptions;
 
 
-        const stackTraceData: IStackTraceData[] = new StackTraceAnalyzer(node.body).analyze();
-
-        this.initializeCustomNodes(stackTraceData);
-
-        this.beforeObfuscation(node);
-        this.obfuscate(node);
-        this.afterObfuscation(node);
-
-        return node;
-    }
+    /**
+     * @type {IStackTraceAnalyzer}
+     */
+    private readonly stackTraceAnalyzer: IStackTraceAnalyzer;
 
 
     /**
     /**
-     * @param astTree
+     * @param stackTraceAnalyzer
+     * @param obfuscationEventEmitter
+     * @param customNodeGroupStorage
+     * @param nodeTransformersFactory
+     * @param options
      */
      */
-    private afterObfuscation (astTree: ESTree.Node): void {
-        this.customNodes.forEach((node: ICustomNode) => {
-            if (node.getAppendState() === AppendState.AfterObfuscation) {
-                node.appendNode(astTree);
-            }
-        });
+    constructor (
+        @inject(ServiceIdentifiers.IStackTraceAnalyzer) stackTraceAnalyzer: IStackTraceAnalyzer,
+        @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers['IStorage<ICustomNodeGroup>']) customNodeGroupStorage: IStorage<ICustomNodeGroup>,
+        @inject(ServiceIdentifiers['Factory<INodeTransformer[]>']) nodeTransformersFactory: TNodeTransformersFactory,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.stackTraceAnalyzer = stackTraceAnalyzer;
+        this.obfuscationEventEmitter = obfuscationEventEmitter;
+        this.customNodeGroupStorage = customNodeGroupStorage;
+        this.nodeTransformersFactory = nodeTransformersFactory;
+        this.options = options;
     }
     }
 
 
     /**
     /**
      * @param astTree
      * @param astTree
+     * @returns {ESTree.Program}
      */
      */
-    private beforeObfuscation (astTree: ESTree.Node): void {
-        this.customNodes.forEach((node: ICustomNode) => {
-            if (node.getAppendState() === AppendState.BeforeObfuscation) {
-                node.appendNode(astTree);
-            }
-        });
-    };
+    public obfuscateAstTree (astTree: ESTree.Program): ESTree.Program {
+        if (Node.isProgramNode(astTree) && !astTree.body.length) {
+            return astTree;
+        }
 
 
-    /**
-     * @param stackTraceData
-     */
-    private initializeCustomNodes (stackTraceData: IStackTraceData[]): void {
-        let customNodes: [string, ICustomNode][] = [];
+        NodeUtils.parentize(astTree);
 
 
-        Obfuscator.nodeGroups.forEach((nodeGroupConstructor: TNodeGroup) => {
-            const nodeGroupNodes: Map <string, ICustomNode> | undefined = new nodeGroupConstructor(
-                stackTraceData, this.options
-            ).getNodes();
+        const stackTraceData: IStackTraceData[] = this.stackTraceAnalyzer.analyze(astTree.body);
 
 
-            if (!nodeGroupNodes) {
-                return;
-            }
+        // initialize custom node groups and configure custom nodes
+        this.customNodeGroupStorage
+            .getStorage()
+            .forEach((customNodeGroup: ICustomNodeGroup) => {
+                customNodeGroup.initialize();
 
 
-            customNodes.push(...nodeGroupNodes);
-        });
+                this.obfuscationEventEmitter.once(
+                    customNodeGroup.getAppendEvent(),
+                    customNodeGroup.appendCustomNodes.bind(customNodeGroup)
+                );
+            });
 
 
-        this.customNodes = new Map <string, ICustomNode> (customNodes);
-    }
+        this.obfuscationEventEmitter.emit(ObfuscationEvents.BeforeObfuscation, astTree, stackTraceData);
 
 
+        // first pass: control flow flattening
+        if (this.options.controlFlowFlattening) {
+            this.transformAstTree(
+                astTree,
+                VisitorDirection.leave,
+                this.nodeTransformersFactory(Obfuscator.nodeControlFlowTransformersMap)
+            );
+        }
 
 
-    /**
-     * @param node
-     * @param parentNode
-     */
-    private initializeNodeObfuscators (node: ESTree.Node, parentNode: ESTree.Node): void {
-        let nodeObfuscators: TNodeObfuscator[] | undefined = Obfuscator.nodeObfuscators.get(node.type);
+        // second pass: nodes obfuscation
+        this.transformAstTree(
+            astTree,
+            VisitorDirection.enter,
+            this.nodeTransformersFactory(Obfuscator.nodeObfuscatorsMap)
+        );
 
 
-        if (!nodeObfuscators) {
-            return;
-        }
+        this.obfuscationEventEmitter.emit(ObfuscationEvents.AfterObfuscation, astTree, stackTraceData);
 
 
-        nodeObfuscators.forEach((obfuscator: TNodeObfuscator) => {
-            new obfuscator(this.customNodes, this.options).obfuscateNode(node, parentNode);
-        });
+        return astTree;
     }
     }
 
 
     /**
     /**
-     * @param node
+     * @param astTree
+     * @param direction
+     * @param nodeTransformersConcreteFactory
      */
      */
-    private obfuscate (node: ESTree.Node): void {
-        estraverse.traverse(node, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): void => {
-                this.initializeNodeObfuscators(node, parentNode);
+    private transformAstTree (
+        astTree: ESTree.Program,
+        direction: TVisitorDirection,
+        nodeTransformersConcreteFactory: (nodeType: string) => INodeTransformer[]
+    ): void {
+        estraverse.traverse(astTree, {
+            [direction]: (node: ESTree.Node, parentNode: ESTree.Node): void => {
+                const nodeTransformers: INodeTransformer[] = nodeTransformersConcreteFactory(node.type);
+
+                nodeTransformers.forEach((nodeTransformer: INodeTransformer) => {
+                    nodeTransformer.transformNode(node, parentNode);
+                });
             }
             }
         });
         });
     }
     }

+ 37 - 42
src/SourceMapCorrector.ts

@@ -1,88 +1,83 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from './container/ServiceIdentifiers';
+
+import { TObfuscationResultFactory } from './types/container/TObfuscationResultFactory';
+
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
+import { IOptions } from './interfaces/options/IOptions';
 import { ISourceMapCorrector } from './interfaces/ISourceMapCorrector';
 import { ISourceMapCorrector } from './interfaces/ISourceMapCorrector';
 
 
-import { TSourceMapMode } from './types/TSourceMapMode';
-
 import { SourceMapMode } from './enums/SourceMapMode';
 import { SourceMapMode } from './enums/SourceMapMode';
 
 
-import { ObfuscationResult } from './ObfuscationResult';
-import { Utils } from './Utils';
+import { CryptUtils } from './utils/CryptUtils';
 
 
+@injectable()
 export class SourceMapCorrector implements ISourceMapCorrector {
 export class SourceMapCorrector implements ISourceMapCorrector {
     /**
     /**
-     * @type {string}
-     */
-    private obfuscatedCode: string;
-
-    /**
-     * @type {string}
+     * @type {TObfuscationResultFactory}
      */
      */
-    private sourceMap: string;
+    private readonly obfuscationResultFactory: TObfuscationResultFactory;
 
 
     /**
     /**
-     * @type {TSourceMapMode}
+     * @type {IOptions}
      */
      */
-    private sourceMapMode: TSourceMapMode;
+    private readonly options: IOptions;
 
 
     /**
     /**
-     * @type {string}
-     */
-    private sourceMapUrl: string;
-
-    /**
-     * @param obfuscationResult
-     * @param sourceMapUrl
-     * @param sourceMapMode
+     * @param obfuscationResultFactory
+     * @param options
      */
      */
     constructor (
     constructor (
-        obfuscationResult: IObfuscationResult,
-        sourceMapUrl: string,
-        sourceMapMode: TSourceMapMode
+        @inject(ServiceIdentifiers['Factory<IObfuscationResult>']) obfuscationResultFactory: TObfuscationResultFactory,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
-        this.obfuscatedCode = obfuscationResult.getObfuscatedCode();
-        this.sourceMap = obfuscationResult.getSourceMap();
-
-        this.sourceMapUrl = sourceMapUrl;
-        this.sourceMapMode = sourceMapMode;
+        this.obfuscationResultFactory = obfuscationResultFactory;
+        this.options = options;
     }
     }
 
 
     /**
     /**
      * @returns {ObfuscationResult}
      * @returns {ObfuscationResult}
+     * @param obfuscatedCode
+     * @param sourceMap
      */
      */
-    public correct (): IObfuscationResult {
-        return new ObfuscationResult(
-            this.correctObfuscatedCode(),
-            this.sourceMap
+    public correct (obfuscatedCode: string, sourceMap: string): IObfuscationResult {
+        return this.obfuscationResultFactory(
+            this.correctObfuscatedCode(obfuscatedCode, sourceMap),
+            sourceMap
         );
         );
     }
     }
 
 
     /**
     /**
+     * @param obfuscatedCode
+     * @param sourceMap
      * @returns {string}
      * @returns {string}
      */
      */
-    private correctObfuscatedCode (): string {
-        if (!this.sourceMap) {
-            return this.obfuscatedCode;
+    private correctObfuscatedCode (obfuscatedCode: string, sourceMap: string): string {
+        if (!sourceMap) {
+            return obfuscatedCode;
         }
         }
 
 
+        const sourceMapUrl: string = this.options.sourceMapBaseUrl + this.options.sourceMapFileName;
+
         let sourceMappingUrl: string = '//# sourceMappingURL=';
         let sourceMappingUrl: string = '//# sourceMappingURL=';
 
 
-        switch (this.sourceMapMode) {
+        switch (this.options.sourceMapMode) {
             case SourceMapMode.Inline:
             case SourceMapMode.Inline:
-                sourceMappingUrl += `data:application/json;base64,${Utils.btoa(this.sourceMap)}`;
+                sourceMappingUrl += `data:application/json;base64,${CryptUtils.btoa(sourceMap)}`;
 
 
                 break;
                 break;
 
 
             case SourceMapMode.Separate:
             case SourceMapMode.Separate:
             default:
             default:
-                if (!this.sourceMapUrl) {
-                    return this.obfuscatedCode;
+                if (!sourceMapUrl) {
+                    return obfuscatedCode;
                 }
                 }
 
 
-                sourceMappingUrl += this.sourceMapUrl;
+                sourceMappingUrl += sourceMapUrl;
 
 
                 break;
                 break;
         }
         }
 
 
-        return `${this.obfuscatedCode}\n${sourceMappingUrl}`;
+        return `${obfuscatedCode}\n${sourceMappingUrl}`;
     };
     };
 }
 }

+ 0 - 53
src/StringArray.ts

@@ -1,53 +0,0 @@
-import { Utils } from './Utils';
-
-export class StringArray {
-    /**
-     * @type {string[]}
-     */
-    private array: string[] = [];
-
-    /**
-     * @param value
-     */
-    public addToArray (value: string): void {
-        this.array.push(value);
-    }
-
-    /**
-     * @returns {string[]}
-     */
-    public getArray (): string[] {
-        return this.array;
-    }
-
-    /**
-     * @param value
-     * @returns {number}
-     */
-    public getIndexOf(value: string): number {
-        return this.array.indexOf(value);
-    }
-
-    /**
-     * @returns {number}
-     */
-    public getLength (): number {
-        return this.array.length;
-    }
-
-    /**
-     * @param rotationValue
-     */
-    public rotateArray (rotationValue: number): void {
-        this.array = Utils.arrayRotate(this.array, rotationValue);
-    }
-
-    /**
-     * @returns {string}
-     */
-    public toString (): string {
-        return this.array.map((value: string) => {
-            return `'${value}'`;
-        }).toString()
-    }
-}

+ 0 - 295
src/Utils.ts

@@ -1,295 +0,0 @@
-import { Chance } from 'chance';
-
-import { JSFuck } from './enums/JSFuck';
-
-export class Utils {
-    /**
-     * @type {Chance.Chance | Chance.SeededChance}
-     */
-    private static randomGenerator: Chance.Chance | Chance.SeededChance = new Chance();
-
-    /**
-     * @param array
-     * @param searchElement
-     * @returns {boolean}
-     */
-    public static arrayContains (array: any[], searchElement: any): boolean {
-        return array.indexOf(searchElement) >= 0;
-    }
-
-    /**
-     * @param array
-     * @param times
-     * @returns {T[]}
-     */
-    public static arrayRotate <T> (array: T[], times: number): T[] {
-        if (!array.length) {
-            throw new ReferenceError(`Cannot rotate empty array.`);
-        }
-
-        if (times <= 0) {
-            return array;
-        }
-
-        let newArray: T[] = array,
-            temp: T | undefined;
-
-        while (times--) {
-            temp = newArray.pop()!;
-            newArray.unshift(temp);
-        }
-
-        return newArray;
-    }
-
-    /**
-     * @param string
-     */
-    public static btoa (string: string): string {
-        const chars: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
-
-        let output: string = '';
-
-        string = encodeURIComponent(string).replace(/%([0-9A-F]{2})/g, (match, p1) => {
-            return String.fromCharCode(parseInt('0x' + p1));
-        });
-
-        for (
-            let block: number|undefined, charCode: number, idx: number = 0, map: string = chars;
-            string.charAt(idx | 0) || (map = '=', idx % 1);
-            output += map.charAt(63 & block >> 8 - idx % 1 * 8)
-        ) {
-            charCode = string.charCodeAt(idx += 3/4);
-
-            if (charCode > 0xFF) {
-                throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
-            }
-
-            block = block << 8 | charCode;
-        }
-
-        return output;
-    }
-
-    /**
-     * @param dec
-     * @returns {string}
-     */
-    public static decToHex (dec: number): string {
-        const radix: number = 16;
-
-        return Number(dec).toString(radix);
-    }
-
-    /**
-     * @param url
-     * @returns {string}
-     */
-    public static extractDomainFromUrl (url: string): string {
-        let domain: string;
-
-        if (url.indexOf('://') > -1 || url.indexOf('//') === 0) {
-            domain = url.split('/')[2];
-        } else {
-            domain = url.split('/')[0];
-        }
-
-        domain = domain.split(':')[0];
-
-        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}
-     */
-    public static getRandomVariableName (length: number = 6): string {
-        const rangeMinInteger: number = 10000,
-            rangeMaxInteger: number = 99999999,
-            prefix: string = '_0x';
-
-        return `${prefix}${(
-            Utils.decToHex(
-                Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
-            )
-        ).substr(0, length)}`;
-    }
-
-    /**
-     * @param str
-     * @param length
-     * @returns {string[]}
-     */
-    public static hideString(str: string, length: number): [string, string] {
-        const escapeRegExp: (s: string) => string = (s: string) =>
-            s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
-
-        const randomMerge: (s1: string, s2: string) => string = function (s1: string, s2: string): string {
-            let i1: number = -1,
-                i2: number = -1,
-                result: string = '';
-
-            while (i1 < s1.length || i2 < s2.length) {
-                if (Utils.getRandomFloat(0, 1) < 0.5 && i2 < s2.length) {
-                    result += s2.charAt(++i2);
-                } else {
-                    result += s1.charAt(++i1);
-                }
-            }
-
-            return result;
-        };
-
-        const customPool: string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-
-        let randomString: string = Utils.randomGenerator.string({length: length, pool: customPool}),
-            randomStringDiff: string = randomString.replace(
-                new RegExp('[' + escapeRegExp(str) + ']', 'g'),
-            ''),
-            randomStringDiffArray: string[] = randomStringDiff.split('');
-
-        Utils.randomGenerator.shuffle(randomStringDiffArray);
-        randomStringDiff = randomStringDiffArray.join('');
-
-        return [randomMerge(str, randomStringDiff), randomStringDiff];
-
-    }
-
-    /**
-     * @param number
-     * @returns {boolean}
-     */
-    public static isInteger (number: number): boolean {
-        return number % 1 === 0;
-    }
-
-    /**
-     * RC4 symmetric cipher encryption/decryption
-     * https://gist.github.com/farhadi/2185197
-     *
-     * @param key
-     * @param string
-     * @returns {string}
-     */
-    public static rc4 (string: string, key: string) {
-        let s: number[] = [],
-            j: number = 0,
-            x: number,
-            result: string = '';
-
-        for (var i = 0; i < 256; i++) {
-            s[i] = i;
-        }
-
-        for (i = 0; i < 256; i++) {
-            j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
-            x = s[i];
-            s[i] = s[j];
-            s[j] = x;
-        }
-
-        i = 0;
-        j = 0;
-
-        for (let y = 0; y < string.length; y++) {
-            i = (i + 1) % 256;
-            j = (j + s[i]) % 256;
-            x = s[i];
-            s[i] = s[j];
-            s[j] = x;
-            result += String.fromCharCode(string.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
-        }
-
-        return result;
-    }
-
-    /**
-     * @param randomGenerator
-     */
-    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
-        Utils.randomGenerator = randomGenerator;
-    }
-
-    /**
-     * @param obj
-     * @returns {T}
-     */
-    public static strEnumify <T extends {[prop: string]: ''|string}> (obj: T): T {
-        return obj;
-    }
-
-    /**
-     * @param string
-     * @returns {string}
-     */
-    public static stringToJSFuck (string: string): string {
-        return Array
-            .from(string)
-            .map((character: string): string => {
-                return JSFuck[character] || character;
-            })
-            .join(' + ');
-    }
-
-    /**
-     * @param string
-     * @returns {string}
-     */
-    public static stringToUnicodeEscapeSequence (string: string): string {
-        const radix: number = 16;
-
-        let prefix: string,
-            regexp: RegExp = new RegExp('[\x00-\x7F]'),
-            template: string;
-
-        return `${string.replace(/[\s\S]/g, (escape: string): string => {
-            if (regexp.test(escape)) {
-                prefix = '\\x';
-                template = '0'.repeat(2);
-            } else {
-                prefix = '\\u';
-                template = '0'.repeat(4);
-            }
-
-            return `${prefix}${(template + escape.charCodeAt(0).toString(radix)).slice(-template.length)}`;
-        })}`;
-    }
-}

+ 3 - 3
src/cli/CLIUtils.ts

@@ -4,20 +4,20 @@ import * as path from 'path';
 
 
 import { IPackageConfig } from '../interfaces/IPackageConfig';
 import { IPackageConfig } from '../interfaces/IPackageConfig';
 
 
-import { Utils } from '../Utils';
+import { Utils } from '../utils/Utils';
 
 
 export class CLIUtils {
 export class CLIUtils {
     /**
     /**
      * @type {string[]}
      * @type {string[]}
      */
      */
-    private static availableInputExtensions: string[] = [
+    private static readonly availableInputExtensions: string[] = [
         '.js'
         '.js'
     ];
     ];
 
 
     /**
     /**
      * @type {BufferEncoding}
      * @type {BufferEncoding}
      */
      */
-    private static encoding: BufferEncoding = 'utf8';
+    private static readonly encoding: BufferEncoding = 'utf8';
 
 
     /**
     /**
      * @param outputPath
      * @param outputPath

+ 106 - 34
src/cli/JavaScriptObfuscatorCLI.ts

@@ -1,10 +1,10 @@
 import * as commander from 'commander';
 import * as commander from 'commander';
 import * as path from 'path';
 import * as path from 'path';
 
 
-import { TStringArrayEncoding } from '../types/TStringArrayEncoding';
+import { TInputOptions } from '../types/options/TInputOptions';
+import { TStringArrayEncoding } from '../types/options/TStringArrayEncoding';
 
 
 import { IObfuscationResult } from '../interfaces/IObfuscationResult';
 import { IObfuscationResult } from '../interfaces/IObfuscationResult';
-import { IObfuscatorOptions } from '../interfaces/IObfuscatorOptions';
 
 
 import { SourceMapMode } from '../enums/SourceMapMode';
 import { SourceMapMode } from '../enums/SourceMapMode';
 import { StringArrayEncoding } from '../enums/StringArrayEncoding';
 import { StringArrayEncoding } from '../enums/StringArrayEncoding';
@@ -13,13 +13,13 @@ import { DEFAULT_PRESET } from '../preset-options/DefaultPreset';
 
 
 import { CLIUtils } from './CLIUtils';
 import { CLIUtils } from './CLIUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
-import { Utils } from '../Utils';
+import { Utils } from '../utils/Utils';
 
 
 export class JavaScriptObfuscatorCLI {
 export class JavaScriptObfuscatorCLI {
     /**
     /**
      * @type {string[]}
      * @type {string[]}
      */
      */
-    private arguments: string[];
+    private readonly arguments: string[];
 
 
     /**
     /**
      * @type {commander.ICommand}
      * @type {commander.ICommand}
@@ -69,7 +69,7 @@ export class JavaScriptObfuscatorCLI {
      * @returns {string}
      * @returns {string}
      */
      */
     private static parseSourceMapMode (value: string): string {
     private static parseSourceMapMode (value: string): string {
-        let availableMode: boolean = Object
+        const availableMode: boolean = Object
             .keys(SourceMapMode)
             .keys(SourceMapMode)
             .some((key: string): boolean => {
             .some((key: string): boolean => {
                 return SourceMapMode[key] === value;
                 return SourceMapMode[key] === value;
@@ -118,11 +118,11 @@ export class JavaScriptObfuscatorCLI {
     }
     }
 
 
     /**
     /**
-     * @returns {IObfuscatorOptions}
+     * @returns {TInputOptions}
      */
      */
-    private buildOptions (): IObfuscatorOptions {
-        let obfuscatorOptions: IObfuscatorOptions = {},
-            availableOptions: string[] = Object.keys(DEFAULT_PRESET);
+    private buildOptions (): TInputOptions {
+        const inputOptions: TInputOptions = {};
+        const availableOptions: string[] = Object.keys(DEFAULT_PRESET);
 
 
         for (const option in this.commands) {
         for (const option in this.commands) {
             if (!this.commands.hasOwnProperty(option)) {
             if (!this.commands.hasOwnProperty(option)) {
@@ -133,38 +133,110 @@ export class JavaScriptObfuscatorCLI {
                 continue;
                 continue;
             }
             }
 
 
-            obfuscatorOptions[option] = (<any>this.commands)[option];
+            (<any>inputOptions)[option] = (<any>this.commands)[option];
         }
         }
 
 
-        return Object.assign({}, DEFAULT_PRESET, obfuscatorOptions);
+        return {
+            ...DEFAULT_PRESET,
+            ...inputOptions
+        };
     }
     }
 
 
     private configureCommands (): void {
     private configureCommands (): void {
         this.commands = new commander.Command()
         this.commands = new commander.Command()
             .version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version')
             .version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version')
             .usage('<inputPath> [options]')
             .usage('<inputPath> [options]')
-            .option('-o, --output <path>', 'Output path for obfuscated code')
-            .option('--compact <boolean>', 'Disable one line output code compacting', JavaScriptObfuscatorCLI.parseBoolean)
-            .option('--debugProtection <boolean>', 'Disable browser Debug panel (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean)
-            .option('--debugProtectionInterval <boolean>', 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean)
-            .option('--disableConsoleOutput <boolean>', 'Allow console.log, console.info, console.error and console.warn messages output into browser console', JavaScriptObfuscatorCLI.parseBoolean)
-            .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`')
-            .option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`')
+            .option(
+                '-o, --output <path>',
+                'Output path for obfuscated code'
+            )
+            .option(
+                '--compact <boolean>',
+                'Disable one line output code compacting',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
+            .option(
+                '--controlFlowFlattening <boolean>',
+                'Enables control flow flattening',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
+            .option(
+                '--debugProtection <boolean>',
+                'Disable browser Debug panel (can cause DevTools enabled browser freeze)',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
+            .option(
+                '--debugProtectionInterval <boolean>',
+                'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
+            .option(
+                '--disableConsoleOutput <boolean>',
+                'Allow console.log, console.info, console.error and console.warn messages output into browser console',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
+            .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`'
+            )
+            .option(
+                '--sourceMapFileName <string>',
+                'Sets file name for output source map when `--sourceMapMode=separate`'
+            )
             .option(
             .option(
                 '--sourceMapMode <string> [inline, separate]',
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',
                 'Specify source map output mode',
                 JavaScriptObfuscatorCLI.parseSourceMapMode
                 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)
+            .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);
             .parse(this.rawArguments);
 
 
         this.commands.on('--help', () => {
         this.commands.on('--help', () => {
@@ -180,8 +252,8 @@ export class JavaScriptObfuscatorCLI {
     }
     }
 
 
     private processData (): void {
     private processData (): void {
-        let options: IObfuscatorOptions = this.buildOptions(),
-            outputCodePath: string = CLIUtils.getOutputCodePath((<any>this.commands).output, this.inputPath);
+        const options: TInputOptions = this.buildOptions();
+        const outputCodePath: string = CLIUtils.getOutputCodePath((<any>this.commands).output, this.inputPath);
 
 
         if (options.sourceMap) {
         if (options.sourceMap) {
             this.processDataWithSourceMap(outputCodePath, options);
             this.processDataWithSourceMap(outputCodePath, options);
@@ -194,8 +266,8 @@ export class JavaScriptObfuscatorCLI {
      * @param outputCodePath
      * @param outputCodePath
      * @param options
      * @param options
      */
      */
-    private processDataWithoutSourceMap (outputCodePath: string, options: IObfuscatorOptions): void {
-        let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(this.data, options).getObfuscatedCode();
+    private processDataWithoutSourceMap (outputCodePath: string, options: TInputOptions): void {
+        const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(this.data, options).getObfuscatedCode();
 
 
         CLIUtils.writeFile(outputCodePath, obfuscatedCode);
         CLIUtils.writeFile(outputCodePath, obfuscatedCode);
     }
     }
@@ -204,8 +276,8 @@ export class JavaScriptObfuscatorCLI {
      * @param outputCodePath
      * @param outputCodePath
      * @param options
      * @param options
      */
      */
-    private processDataWithSourceMap (outputCodePath: string, options: IObfuscatorOptions): void {
-        let outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(
+    private processDataWithSourceMap (outputCodePath: string, options: TInputOptions): void {
+        const outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(
             outputCodePath,
             outputCodePath,
             options.sourceMapFileName || ''
             options.sourceMapFileName || ''
         );
         );

+ 110 - 0
src/container/InversifyContainerFacade.ts

@@ -0,0 +1,110 @@
+import { Container, interfaces } from 'inversify';
+import { ServiceIdentifiers } from './ServiceIdentifiers';
+
+import { customNodesModule } from './modules/custom-nodes/CustomNodesModule';
+import { nodeControlFlowTransformersModule } from './modules/node-transformers/NodeControlFlowTransformersModule';
+import { nodeObfuscatorsModule } from './modules/node-transformers/NodeObfuscatorsModule';
+import { nodeTransformersModule } from './modules/node-transformers/NodeTransformersModule';
+import { stackTraceAnalyzerModule } from './modules/stack-trace-analyzer/StackTraceAnalyzerModule';
+import { storagesModule } from './modules/storages/StoragesModule';
+
+import { TInputOptions } from '../types/options/TInputOptions';
+
+import { IInversifyContainerFacade } from '../interfaces/container/IInversifyContainerFacade';
+import { IJavaScriptObfuscator } from '../interfaces/IJavaScriptObfsucator';
+import { IObfuscationEventEmitter } from '../interfaces/event-emitters/IObfuscationEventEmitter';
+import { IObfuscationResult } from '../interfaces/IObfuscationResult';
+import { IObfuscator } from '../interfaces/IObfuscator';
+import { IOptions } from '../interfaces/options/IOptions';
+import { ISourceMapCorrector } from '../interfaces/ISourceMapCorrector';
+
+import { JavaScriptObfuscatorInternal } from '../JavaScriptObfuscatorInternal';
+import { ObfuscationEventEmitter } from '../event-emitters/ObfuscationEventEmitter';
+import { ObfuscationResult } from '../ObfuscationResult';
+import { Obfuscator } from '../Obfuscator';
+import { Options } from "../options/Options";
+import { SourceMapCorrector } from '../SourceMapCorrector';
+
+export class InversifyContainerFacade implements IInversifyContainerFacade {
+    /**
+     * @type {interfaces.Container}
+     */
+    private readonly container: interfaces.Container;
+
+    /**
+     * @param options
+     */
+    constructor (options: TInputOptions) {
+        this.container = new Container();
+
+        this.container
+            .bind<IOptions>(ServiceIdentifiers.IOptions)
+            .toDynamicValue(() => {
+                return new Options(options);
+            })
+            .inSingletonScope();
+
+        this.container
+            .bind<IJavaScriptObfuscator>(ServiceIdentifiers.IJavaScriptObfuscator)
+            .to(JavaScriptObfuscatorInternal)
+            .inSingletonScope();
+
+        this.container
+            .bind<IObfuscator>(ServiceIdentifiers.IObfuscator)
+            .to(Obfuscator)
+            .inSingletonScope();
+
+        this.container
+            .bind<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult)
+            .to(ObfuscationResult)
+            .inSingletonScope();
+
+        this.container
+            .bind<IObfuscationResult>(ServiceIdentifiers['Factory<IObfuscationResult>'])
+            .toFactory<IObfuscationResult>((context: interfaces.Context) => {
+                return (obfuscatedCode: string, sourceMap: string) => {
+                    const obfuscationResult: IObfuscationResult = context.container
+                        .get<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult);
+
+                    obfuscationResult.initialize(obfuscatedCode, sourceMap);
+
+                    return obfuscationResult;
+                };
+            });
+
+        this.container
+            .bind<ISourceMapCorrector>(ServiceIdentifiers.ISourceMapCorrector)
+            .to(SourceMapCorrector)
+            .inSingletonScope();
+
+        this.container
+            .bind<IObfuscationEventEmitter>(ServiceIdentifiers.IObfuscationEventEmitter)
+            .to(ObfuscationEventEmitter)
+            .inSingletonScope();
+
+        // modules
+        this.container.load(storagesModule);
+        this.container.load(stackTraceAnalyzerModule);
+        this.container.load(customNodesModule);
+        this.container.load(nodeTransformersModule);
+        this.container.load(nodeControlFlowTransformersModule);
+        this.container.load(nodeObfuscatorsModule);
+    }
+
+    /**
+     * @param serviceIdentifier
+     * @returns {T}
+     */
+    public get <T> (serviceIdentifier: interfaces.ServiceIdentifier<T>): T {
+        return this.container.get<T>(serviceIdentifier);
+    }
+
+    /**
+     * @param serviceIdentifier
+     * @param named
+     * @returns {T}
+     */
+    public getNamed <T> (serviceIdentifier: interfaces.ServiceIdentifier<T>, named: string | number | symbol): T {
+        return this.container.getNamed<T>(serviceIdentifier, named);
+    }
+}

+ 25 - 0
src/container/ServiceIdentifiers.ts

@@ -0,0 +1,25 @@
+export const ServiceIdentifiers: any = {
+    'Factory<ICalleeDataExtractor>': Symbol('Factory<ICalleeDataExtractor>'),
+    'Factory<IControlFlowReplacer>': Symbol('Factory<IControlFlowReplacer>'),
+    'Factory<ICustomNode>': Symbol('Factory<ICustomNode>'),
+    'Factory<ICustomNodeGroup>': Symbol('Factory<ICustomNodeGroup>'),
+    'Factory<INodeTransformer[]>': Symbol('Factory<INodeTransformer[]>'),
+    'Factory<IObfuscationResult>': Symbol('Factory<IObfuscationResult>'),
+    'Factory<IReplacer>': Symbol('Factory<IReplacer>'),
+    'Factory<IStorage<ICustomNode>>': Symbol('Factory<IStorage<ICustomNode>>'),
+    ICalleeDataExtractor: Symbol('ICalleeDataExtractor'),
+    ICustomNode: Symbol('ICustomNode'),
+    ICustomNodeGroup: Symbol('ICustomNodeGroup'),
+    IControlFlowReplacer: Symbol('IControlFlowReplacer'),
+    IJavaScriptObfuscator: Symbol('IJavaScriptObfuscator'),
+    INodeTransformer: Symbol('INodeTransformer'),
+    IObfuscationEventEmitter: Symbol('IObfuscationEventEmitter'),
+    IObfuscationResult: Symbol('IObfuscationResult'),
+    IObfuscator: Symbol('IObfuscator'),
+    IOptions: Symbol('IOptions'),
+    IReplacer: Symbol('IReplacer'),
+    ISourceMapCorrector: Symbol('ISourceMapCorrector'),
+    IStackTraceAnalyzer: Symbol('IStackTraceAnalyzer'),
+    'IStorage<ICustomNode>': Symbol('IStorage<ICustomNode>'),
+    'IStorage<ICustomNodeGroup>': Symbol('IStorage<ICustomNodeGroup>')
+};

+ 126 - 0
src/container/modules/custom-nodes/CustomNodesModule.ts

@@ -0,0 +1,126 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { ICustomNodeGroup } from '../../../interfaces/custom-nodes/ICustomNodeGroup';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+import { CustomNodeGroups } from '../../../enums/container/CustomNodeGroups';
+
+import { ConsoleOutputCustomNodeGroup } from '../../../custom-nodes/console-output-nodes/group/ConsoleOutputCustomNodeGroup';
+import { DebugProtectionCustomNodeGroup } from '../../../custom-nodes/debug-protection-nodes/group/DebugProtectionCustomNodeGroup';
+import { DomainLockCustomNodeGroup } from '../../../custom-nodes/domain-lock-nodes/group/DomainLockCustomNodeGroup';
+import { SelfDefendingCustomNodeGroup } from '../../../custom-nodes/self-defending-nodes/group/SelfDefendingCustomNodeGroup';
+import { StringArrayCustomNodeGroup } from '../../../custom-nodes/string-array-nodes/group/StringArrayCustomNodeGroup';
+
+import { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode';
+import { ControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode';
+import { ControlFlowStorageNode } from '../../../custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode';
+import { ConsoleOutputDisableExpressionNode } from '../../../custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode';
+import { DebugProtectionFunctionCallNode } from '../../../custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode';
+import { DebugProtectionFunctionIntervalNode } from '../../../custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode';
+import { DebugProtectionFunctionNode } from '../../../custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode';
+import { DomainLockNode } from '../../../custom-nodes/domain-lock-nodes/DomainLockNode';
+import { NodeCallsControllerFunctionNode } from '../../../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
+import { SelfDefendingUnicodeNode } from '../../../custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode';
+import { StringArrayCallsWrapper } from '../../../custom-nodes/string-array-nodes/StringArrayCallsWrapper';
+import { StringArrayNode } from '../../../custom-nodes/string-array-nodes/StringArrayNode';
+import { StringArrayRotateFunctionNode } from '../../../custom-nodes/string-array-nodes/StringArrayRotateFunctionNode';
+
+export const customNodesModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    // custom nodes
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(BinaryExpressionFunctionNode)
+        .whenTargetNamed(CustomNodes.BinaryExpressionFunctionNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(ControlFlowStorageCallNode)
+        .whenTargetNamed(CustomNodes.ControlFlowStorageCallNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(ControlFlowStorageNode)
+        .whenTargetNamed(CustomNodes.ControlFlowStorageNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(ConsoleOutputDisableExpressionNode)
+        .whenTargetNamed(CustomNodes.ConsoleOutputDisableExpressionNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(DebugProtectionFunctionCallNode)
+        .whenTargetNamed(CustomNodes.DebugProtectionFunctionCallNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(DebugProtectionFunctionIntervalNode)
+        .whenTargetNamed(CustomNodes.DebugProtectionFunctionIntervalNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(DebugProtectionFunctionNode)
+        .whenTargetNamed(CustomNodes.DebugProtectionFunctionNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(DomainLockNode)
+        .whenTargetNamed(CustomNodes.DomainLockNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(NodeCallsControllerFunctionNode)
+        .whenTargetNamed(CustomNodes.NodeCallsControllerFunctionNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(SelfDefendingUnicodeNode)
+        .whenTargetNamed(CustomNodes.SelfDefendingUnicodeNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(StringArrayCallsWrapper)
+        .whenTargetNamed(CustomNodes.StringArrayCallsWrapper);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(StringArrayNode)
+        .whenTargetNamed(CustomNodes.StringArrayNode);
+
+    bind<ICustomNode>(ServiceIdentifiers.ICustomNode)
+        .to(StringArrayRotateFunctionNode)
+        .whenTargetNamed(CustomNodes.StringArrayRotateFunctionNode);
+
+    // node groups
+    bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
+        .to(ConsoleOutputCustomNodeGroup)
+        .whenTargetNamed(CustomNodeGroups.ConsoleOutputCustomNodeGroup);
+
+    bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
+        .to(DebugProtectionCustomNodeGroup)
+        .whenTargetNamed(CustomNodeGroups.DebugProtectionCustomNodeGroup);
+
+    bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
+        .to(DomainLockCustomNodeGroup)
+        .whenTargetNamed(CustomNodeGroups.DomainLockCustomNodeGroup);
+
+    bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
+        .to(SelfDefendingCustomNodeGroup)
+        .whenTargetNamed(CustomNodeGroups.SelfDefendingCustomNodeGroup);
+
+    bind<ICustomNodeGroup>(ServiceIdentifiers.ICustomNodeGroup)
+        .to(StringArrayCustomNodeGroup)
+        .whenTargetNamed(CustomNodeGroups.StringArrayCustomNodeGroup);
+
+    // customNode factory
+    bind<ICustomNode>(ServiceIdentifiers['Factory<ICustomNode>'])
+        .toFactory<ICustomNode>((context: interfaces.Context) => {
+            return (customNodeName: CustomNodes) => {
+                return context.container.getNamed<ICustomNode>(
+                    ServiceIdentifiers.ICustomNode,
+                    customNodeName
+                );
+            };
+        });
+
+    // CustomNodeGroup factory
+    bind<ICustomNodeGroup>(ServiceIdentifiers['Factory<ICustomNodeGroup>'])
+        .toFactory<ICustomNodeGroup>((context: interfaces.Context) => {
+            return (customNodeGroupName: CustomNodeGroups) => {
+                return context.container.getNamed<ICustomNodeGroup>(
+                    ServiceIdentifiers.ICustomNodeGroup,
+                    customNodeGroupName
+                );
+            };
+        });
+});

+ 34 - 0
src/container/modules/node-transformers/NodeControlFlowTransformersModule.ts

@@ -0,0 +1,34 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { IControlFlowReplacer } from '../../../interfaces/node-transformers/IControlFlowReplacer';
+
+import { NodeControlFlowReplacers } from '../../../enums/container/NodeControlFlowReplacers';
+
+import { BinaryExpressionControlFlowReplacer } from '../../../node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer';
+
+export const nodeControlFlowTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    bind<IControlFlowReplacer>(ServiceIdentifiers.IControlFlowReplacer)
+        .to(BinaryExpressionControlFlowReplacer)
+        .whenTargetNamed(NodeControlFlowReplacers.BinaryExpressionControlFlowReplacer);
+
+    bind<IControlFlowReplacer>(ServiceIdentifiers['Factory<IControlFlowReplacer>'])
+        .toFactory<IControlFlowReplacer>((context: interfaces.Context) => {
+            const cache: Map <NodeControlFlowReplacers, IControlFlowReplacer> = new Map <NodeControlFlowReplacers, IControlFlowReplacer> ();
+
+            return (replacerName: NodeControlFlowReplacers) => {
+                if (cache.has(replacerName)) {
+                    return <IControlFlowReplacer>cache.get(replacerName);
+                }
+
+                const controlFlowReplacer: IControlFlowReplacer = context.container.getNamed<IControlFlowReplacer>(
+                    ServiceIdentifiers.IControlFlowReplacer,
+                    replacerName
+                );
+
+                cache.set(replacerName, controlFlowReplacer);
+
+                return controlFlowReplacer;
+            };
+        });
+});

+ 49 - 0
src/container/modules/node-transformers/NodeObfuscatorsModule.ts

@@ -0,0 +1,49 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { IReplacer } from '../../../interfaces/node-transformers/IReplacer';
+
+import { NodeObfuscatorsReplacers } from '../../../enums/container/NodeObfuscatorsReplacers';
+
+import { BooleanLiteralReplacer } from '../../../node-transformers/node-obfuscators/replacers/BooleanLiteralReplacer';
+import { IdentifierReplacer } from '../../../node-transformers/node-obfuscators/replacers/IdentifierReplacer';
+import { NumberLiteralReplacer } from '../../../node-transformers/node-obfuscators/replacers/NumberLiteralReplacer';
+import { StringLiteralReplacer } from '../../../node-transformers/node-obfuscators/replacers/StringLiteralReplacer';
+
+export const nodeObfuscatorsModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+        .to(BooleanLiteralReplacer)
+        .whenTargetNamed(NodeObfuscatorsReplacers.BooleanReplacer);
+
+    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+        .to(IdentifierReplacer)
+        .whenTargetNamed(NodeObfuscatorsReplacers.IdentifierReplacer);
+
+    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+        .to(NumberLiteralReplacer)
+        .whenTargetNamed(NodeObfuscatorsReplacers.NumberLiteralReplacer);
+
+    bind<IReplacer>(ServiceIdentifiers.IReplacer)
+        .to(StringLiteralReplacer)
+        .whenTargetNamed(NodeObfuscatorsReplacers.StringLiteralReplacer);
+
+    bind<IReplacer>(ServiceIdentifiers['Factory<IReplacer>'])
+        .toFactory<IReplacer>((context: interfaces.Context) => {
+            const cache: Map <NodeObfuscatorsReplacers, IReplacer> = new Map <NodeObfuscatorsReplacers, IReplacer> ();
+
+            return (replacerName: NodeObfuscatorsReplacers) => {
+                if (cache.has(replacerName)) {
+                    return <IReplacer>cache.get(replacerName);
+                }
+
+                const obfuscationReplacer: IReplacer = context.container.getNamed<IReplacer>(
+                    ServiceIdentifiers.IReplacer,
+                    replacerName
+                );
+
+                cache.set(replacerName, obfuscationReplacer);
+
+                return obfuscationReplacer;
+            };
+        });
+});

+ 91 - 0
src/container/modules/node-transformers/NodeTransformersModule.ts

@@ -0,0 +1,91 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTransformer';
+
+import { NodeTransformers } from '../../../enums/container/NodeTransformers';
+
+import { FunctionControlFlowTransformer } from '../../../node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer';
+
+import { CatchClauseObfuscator } from '../../../node-transformers/node-obfuscators/CatchClauseObfuscator';
+import { FunctionDeclarationObfuscator } from '../../../node-transformers/node-obfuscators/FunctionDeclarationObfuscator';
+import { FunctionObfuscator } from '../../../node-transformers/node-obfuscators/FunctionObfuscator';
+import { LabeledStatementObfuscator } from '../../../node-transformers/node-obfuscators/LabeledStatementObfuscator';
+import { LiteralObfuscator } from '../../../node-transformers/node-obfuscators/LiteralObfuscator';
+import { MemberExpressionObfuscator } from '../../../node-transformers/node-obfuscators/MemberExpressionObfuscator';
+import { MethodDefinitionObfuscator } from '../../../node-transformers/node-obfuscators/MethodDefinitionObfuscator';
+import { ObjectExpressionObfuscator } from '../../../node-transformers/node-obfuscators/ObjectExpressionObfuscator';
+import { VariableDeclarationObfuscator } from '../../../node-transformers/node-obfuscators/VariableDeclarationObfuscator';
+
+export const nodeTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    // node control flow transformers
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(FunctionControlFlowTransformer)
+        .whenTargetNamed(NodeTransformers.FunctionControlFlowTransformer);
+
+    // node obfuscators
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(CatchClauseObfuscator)
+        .whenTargetNamed(NodeTransformers.CatchClauseObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(FunctionDeclarationObfuscator)
+        .whenTargetNamed(NodeTransformers.FunctionDeclarationObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(FunctionObfuscator)
+        .whenTargetNamed(NodeTransformers.FunctionObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(LabeledStatementObfuscator)
+        .whenTargetNamed(NodeTransformers.LabeledStatementObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(LiteralObfuscator)
+        .whenTargetNamed(NodeTransformers.LiteralObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(MemberExpressionObfuscator)
+        .whenTargetNamed(NodeTransformers.MemberExpressionObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(MethodDefinitionObfuscator)
+        .whenTargetNamed(NodeTransformers.MethodDefinitionObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(ObjectExpressionObfuscator)
+        .whenTargetNamed(NodeTransformers.ObjectExpressionObfuscator);
+
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(VariableDeclarationObfuscator)
+        .whenTargetNamed(NodeTransformers.VariableDeclarationObfuscator);
+
+    // node transformers factory
+    bind<INodeTransformer[]>(ServiceIdentifiers['Factory<INodeTransformer[]>'])
+        .toFactory<INodeTransformer[]>((context: interfaces.Context) => {
+            const cache: Map <NodeTransformers, INodeTransformer> = new Map <NodeTransformers, INodeTransformer> ();
+
+            return (nodeTransformersMap: Map<string, NodeTransformers[]>) => (nodeType: string) => {
+                const nodeTransformers: NodeTransformers[] = nodeTransformersMap.get(nodeType) || [];
+                const instancesArray: INodeTransformer[] = [];
+
+                nodeTransformers.forEach((transformer: NodeTransformers) => {
+                    let nodeTransformer: INodeTransformer;
+
+                    if (cache.has(transformer)) {
+                        nodeTransformer = <INodeTransformer>cache.get(transformer);
+                    } else {
+                        nodeTransformer = context.container.getNamed<INodeTransformer>(
+                            ServiceIdentifiers.INodeTransformer,
+                            transformer
+                        );
+                        cache.set(transformer, nodeTransformer);
+                    }
+
+                    instancesArray.push(nodeTransformer);
+                });
+
+                return instancesArray;
+            };
+        });
+});

+ 52 - 0
src/container/modules/stack-trace-analyzer/StackTraceAnalyzerModule.ts

@@ -0,0 +1,52 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { ICalleeDataExtractor } from '../../../interfaces/stack-trace-analyzer/ICalleeDataExtractor';
+import { IStackTraceAnalyzer } from '../../../interfaces/stack-trace-analyzer/IStackTraceAnalyzer';
+
+import { CalleeDataExtractors } from '../../../enums/container/CalleeDataExtractors';
+import { FunctionDeclarationCalleeDataExtractor } from '../../../stack-trace-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor';
+import { FunctionExpressionCalleeDataExtractor } from '../../../stack-trace-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor';
+import { ObjectExpressionCalleeDataExtractor } from '../../../stack-trace-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor';
+import { StackTraceAnalyzer } from '../../../stack-trace-analyzer/StackTraceAnalyzer';
+
+export const stackTraceAnalyzerModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    // stack trace analyzer
+    bind<IStackTraceAnalyzer>(ServiceIdentifiers.IStackTraceAnalyzer)
+        .to(StackTraceAnalyzer)
+        .inSingletonScope();
+
+    // callee data extractors
+    bind<ICalleeDataExtractor>(ServiceIdentifiers.ICalleeDataExtractor)
+        .to(FunctionDeclarationCalleeDataExtractor)
+        .whenTargetNamed(CalleeDataExtractors.FunctionDeclarationCalleeDataExtractor);
+
+    bind<ICalleeDataExtractor>(ServiceIdentifiers.ICalleeDataExtractor)
+        .to(FunctionExpressionCalleeDataExtractor)
+        .whenTargetNamed(CalleeDataExtractors.FunctionExpressionCalleeDataExtractor);
+
+    bind<ICalleeDataExtractor>(ServiceIdentifiers.ICalleeDataExtractor)
+        .to(ObjectExpressionCalleeDataExtractor)
+        .whenTargetNamed(CalleeDataExtractors.ObjectExpressionCalleeDataExtractor);
+
+    // node transformers factory
+    bind<ICalleeDataExtractor>(ServiceIdentifiers['Factory<ICalleeDataExtractor>'])
+        .toFactory<ICalleeDataExtractor>((context: interfaces.Context) => {
+            const cache: Map <CalleeDataExtractors, ICalleeDataExtractor> = new Map <CalleeDataExtractors, ICalleeDataExtractor> ();
+
+            return (calleeDataExtractorName: CalleeDataExtractors) => {
+                if (cache.has(calleeDataExtractorName)) {
+                    return <ICalleeDataExtractor>cache.get(calleeDataExtractorName);
+                }
+
+                const calleeDataExtractor: ICalleeDataExtractor = context.container.getNamed<ICalleeDataExtractor>(
+                    ServiceIdentifiers.ICalleeDataExtractor,
+                    calleeDataExtractorName
+                );
+
+                cache.set(calleeDataExtractorName, calleeDataExtractor);
+
+                return calleeDataExtractor;
+            };
+        });
+});

+ 27 - 0
src/container/modules/storages/StoragesModule.ts

@@ -0,0 +1,27 @@
+import { ContainerModule, interfaces } from 'inversify';
+import { ServiceIdentifiers } from '../../ServiceIdentifiers';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { ICustomNodeGroup } from '../../../interfaces/custom-nodes/ICustomNodeGroup';
+import { IStorage } from '../../../interfaces/storages/IStorage';
+
+import { ControlFlowStorage } from '../../../storages/control-flow/ControlFlowStorage';
+import { CustomNodeGroupStorage } from '../../../storages/custom-node-group/CustomNodeGroupStorage';
+
+export const storagesModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
+    // storages
+    bind<IStorage<ICustomNodeGroup>>(ServiceIdentifiers['IStorage<ICustomNodeGroup>'])
+        .to(CustomNodeGroupStorage)
+        .inSingletonScope();
+
+    bind<IStorage<ICustomNode>>(ServiceIdentifiers['IStorage<ICustomNode>'])
+        .to(ControlFlowStorage);
+
+    // controlFlowStorage factory
+    bind<IStorage<ICustomNode>>(ServiceIdentifiers['Factory<IStorage<ICustomNode>>'])
+        .toFactory<IStorage<ICustomNode>>((context: interfaces.Context) => {
+            return () => {
+                return context.container.get<IStorage<ICustomNode>>(ServiceIdentifiers['IStorage<ICustomNode>']);
+            };
+        });
+});

+ 17 - 25
src/custom-nodes/AbstractCustomNode.ts

@@ -1,40 +1,37 @@
-import * as ESTree from 'estree';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
 
 
 import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
-import { TStatement } from '../types/TStatement';
+import { IOptions } from '../interfaces/options/IOptions';
+import { TStatement } from '../types/node/TStatement';
 
 
-import { AppendState } from '../enums/AppendState';
+import { NodeUtils } from '../node/NodeUtils';
 
 
+@injectable()
 export abstract class AbstractCustomNode implements ICustomNode {
 export abstract class AbstractCustomNode implements ICustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected abstract appendState: AppendState;
-
     /**
     /**
      * @type {IOptions}
      * @type {IOptions}
      */
      */
-    protected options: IOptions;
+    protected readonly options: IOptions;
 
 
     /**
     /**
      * @param options
      * @param options
      */
      */
-    constructor (options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         this.options = options;
         this.options = options;
     }
     }
 
 
     /**
     /**
-     * @param astTree
+     * @param args
      */
      */
-    public abstract appendNode (astTree: ESTree.Node): void;
+    public abstract initialize (...args: any[]): void;
 
 
     /**
     /**
-     * @returns {AppendState}
+     * @returns {string}
      */
      */
-    public getAppendState (): AppendState {
-        return this.appendState;
-    }
+    public abstract getCode (): string;
 
 
     /**
     /**
      * @returns {TStatement[]}
      * @returns {TStatement[]}
@@ -43,15 +40,10 @@ export abstract class AbstractCustomNode implements ICustomNode {
         return this.getNodeStructure();
         return this.getNodeStructure();
     }
     }
 
 
-    /**
-     * @param appendState
-     */
-    public setAppendState (appendState: AppendState): void {
-        this.appendState = appendState;
-    }
-
     /**
     /**
      * @returns {TStatement[]}
      * @returns {TStatement[]}
      */
      */
-    protected abstract getNodeStructure (): TStatement[];
+    protected getNodeStructure (): TStatement[] {
+        return NodeUtils.convertCodeToStructure(this.getCode());
+    }
 }
 }

+ 80 - 0
src/custom-nodes/AbstractCustomNodeGroup.ts

@@ -0,0 +1,80 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
+
+import { TNodeWithBlockStatement } from '../types/node/TNodeWithBlockStatement';
+import { TObfuscationEvent } from '../types/event-emitters/TObfuscationEvent';
+
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { ICustomNodeGroup } from '../interfaces/custom-nodes/ICustomNodeGroup';
+import { IOptions } from '../interfaces/options/IOptions';
+import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { CustomNodes } from '../enums/container/CustomNodes';
+
+@injectable()
+export abstract class AbstractCustomNodeGroup implements ICustomNodeGroup {
+    /**
+     * @type {TObfuscationEvent}
+     */
+    protected abstract readonly appendEvent: TObfuscationEvent;
+
+    /**
+     * @type {Map<CustomNodes, ICustomNode>}
+     */
+    protected abstract customNodes: Map <CustomNodes, ICustomNode>;
+
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected readonly stackTraceData: IStackTraceData[];
+
+    /**
+     * @type {IOptions}
+     */
+    protected readonly options: IOptions;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.options = options;
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    public abstract appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void;
+
+    /**
+     * @returns {TObfuscationEvent}
+     */
+    public getAppendEvent (): TObfuscationEvent {
+        return this.appendEvent;
+    }
+
+    /**
+     * @returns {Map<CustomNodes, ICustomNode>}
+     */
+    public getCustomNodes (): Map <CustomNodes, ICustomNode> {
+        return this.customNodes;
+    }
+
+    public abstract initialize (): void;
+
+    /**
+     * @param customNodeName
+     * @param callback
+     */
+    protected appendCustomNodeIfExist (customNodeName: CustomNodes, callback: (customNode: ICustomNode) => void): void {
+        const customNode: ICustomNode | undefined = this.customNodes.get(customNodeName);
+
+        if (!customNode) {
+            return;
+        }
+
+        callback(customNode);
+    }
+}

+ 20 - 66
src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts

@@ -1,94 +1,48 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
-import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
-
-import { AppendState } from '../../enums/AppendState';
+import { IOptions } from '../../interfaces/options/IOptions';
 
 
 import { ConsoleOutputDisableExpressionTemplate } from '../../templates/custom-nodes/console-output-nodes/console-output-disable-expression-node/ConsoleOutputDisableExpressionTemplate';
 import { ConsoleOutputDisableExpressionTemplate } from '../../templates/custom-nodes/console-output-nodes/console-output-disable-expression-node/ConsoleOutputDisableExpressionTemplate';
 
 
+import { initializable } from '../../decorators/Initializable';
+
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { Utils } from '../../Utils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 
 
+@injectable()
 export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
 export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
-    protected callsControllerFunctionName: string;
-
-    /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
+    @initializable()
+    private callsControllerFunctionName: string;
 
 
     /**
     /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
-
-        this.stackTraceData = stackTraceData;
-        this.callsControllerFunctionName = callsControllerFunctionName;
-        this.randomStackTraceIndex = randomStackTraceIndex;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param callsControllerFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeAppender.appendNodeToOptimalBlockScope(
-            this.stackTraceData,
-            blockScopeNode,
-            this.getNode(),
-            this.randomStackTraceIndex
-        );
+    public initialize (callsControllerFunctionName: string): void {
+        this.callsControllerFunctionName = callsControllerFunctionName;
     }
     }
 
 
     /**
     /**
-     *  JSCrush version of following code
-     *
-     *  (function () {
-     *      var _console = []["filter"]["constructor"]("return this")().console;
-     *      var _function = function () {};
-     *
-     *      _console.log = _function;
-     *      _console.info = _function;
-     *      _console.warn = _function;
-     *      _console.error = _function;
-     *  _console
-     *  })();
-     *
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            ConsoleOutputDisableExpressionTemplate().formatUnicorn({
-                consoleLogDisableFunctionName: Utils.getRandomVariableName(),
-                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(ConsoleOutputDisableExpressionTemplate(), {
+            consoleLogDisableFunctionName: RandomGeneratorUtils.getRandomVariableName(),
+            singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+        });
     }
     }
 }
 }

+ 110 - 0
src/custom-nodes/console-output-nodes/group/ConsoleOutputCustomNodeGroup.ts

@@ -0,0 +1,110 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TObfuscationEvent } from '../../../types/event-emitters/TObfuscationEvent';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IObfuscationEventEmitter } from '../../../interfaces/event-emitters/IObfuscationEventEmitter';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStackTraceData } from '../../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+import { ObfuscationEvents } from '../../../enums/ObfuscationEvents';
+
+import { AbstractCustomNodeGroup } from '../../AbstractCustomNodeGroup';
+import { NodeAppender } from '../../../node/NodeAppender';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class ConsoleOutputCustomNodeGroup extends AbstractCustomNodeGroup {
+    /**
+     * @type {TObfuscationEvent}
+     */
+    protected readonly appendEvent: TObfuscationEvent = ObfuscationEvents.BeforeObfuscation;
+
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @type {Map<CustomNodes, ICustomNode>}
+     */
+    @initializable()
+    protected customNodes: Map <CustomNodes, ICustomNode>;
+
+    /**
+     * @type {IObfuscationEventEmitter}
+     */
+    private readonly obfuscationEventEmitter: IObfuscationEventEmitter;
+
+    /**
+     * @param customNodeFactory
+     * @param obfuscationEventEmitter
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.customNodeFactory = customNodeFactory;
+        this.obfuscationEventEmitter = obfuscationEventEmitter;
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(stackTraceData.length);
+
+        // consoleOutputDisableExpressionNode append
+        this.appendCustomNodeIfExist(CustomNodes.ConsoleOutputDisableExpressionNode, (customNode: ICustomNode) => {
+            NodeAppender.appendNodeToOptimalBlockScope(
+                stackTraceData,
+                blockScopeNode,
+                customNode.getNode(),
+                randomStackTraceIndex
+            );
+        });
+
+        // nodeCallsControllerFunctionNode append
+        this.appendCustomNodeIfExist(CustomNodes.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
+            let targetBlockScope: TNodeWithBlockStatement;
+
+            if (stackTraceData.length) {
+                targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);
+            } else {
+                targetBlockScope = blockScopeNode;
+            }
+
+            NodeAppender.prependNode(targetBlockScope, customNode.getNode());
+        });
+    }
+
+    public initialize (): void {
+        this.customNodes = new Map <CustomNodes, ICustomNode> ();
+
+        if (!this.options.disableConsoleOutput) {
+            return;
+        }
+
+        const callsControllerFunctionName: string = RandomGeneratorUtils.getRandomVariableName();
+
+        const consoleOutputDisableExpressionNode: ICustomNode = this.customNodeFactory(CustomNodes.ConsoleOutputDisableExpressionNode);
+        const nodeCallsControllerFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.NodeCallsControllerFunctionNode);
+
+        consoleOutputDisableExpressionNode.initialize(callsControllerFunctionName);
+        nodeCallsControllerFunctionNode.initialize(this.appendEvent, callsControllerFunctionName);
+
+        this.customNodes.set(CustomNodes.ConsoleOutputDisableExpressionNode, consoleOutputDisableExpressionNode);
+        this.customNodes.set(CustomNodes.NodeCallsControllerFunctionNode, nodeCallsControllerFunctionNode);
+    }
+}

+ 48 - 0
src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionNode.ts

@@ -0,0 +1,48 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as format from 'string-template';
+
+import { IOptions } from '../../../interfaces/options/IOptions';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { BinaryExpressionFunctionTemplate } from '../../../templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/BinaryExpressionFunctionTemplate';
+
+import { AbstractCustomNode } from '../../AbstractCustomNode';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class BinaryExpressionFunctionNode extends AbstractCustomNode {
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private operator: string;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+    }
+
+    /**
+     * @param operator
+     */
+    initialize (operator: string): void {
+        this.operator = operator;
+    }
+
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        return format(BinaryExpressionFunctionTemplate(), {
+            functionName: RandomGeneratorUtils.getRandomVariableName(1),
+            operator: this.operator
+        });
+    }
+}

+ 78 - 0
src/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallNode.ts

@@ -0,0 +1,78 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as format from 'string-template';
+
+import { IOptions } from '../../../interfaces/options/IOptions';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { ControlFlowStorageCallTemplate } from '../../../templates/custom-nodes/control-flow-replacers-nodes/binary-expression-control-flow-replacer-nodes/ControlFlowStorageCallTemplate';
+
+import { AbstractCustomNode } from '../../AbstractCustomNode';
+
+@injectable()
+export class ControlFlowStorageCallNode extends AbstractCustomNode {
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private controlFlowStorageKey: string;
+
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private controlFlowStorageName: string;
+
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private leftValue: string;
+
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private rightValue: string;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+    }
+
+    /**
+     * @param controlFlowStorageName
+     * @param controlFlowStorageKey
+     * @param leftValue
+     * @param rightValue
+     */
+    public initialize (
+        controlFlowStorageName: string,
+        controlFlowStorageKey: string,
+        leftValue: string,
+        rightValue: string,
+    ): void {
+        this.controlFlowStorageName = controlFlowStorageName;
+        this.controlFlowStorageKey = controlFlowStorageKey;
+        this.leftValue = leftValue;
+        this.rightValue = rightValue;
+    }
+
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        return format(ControlFlowStorageCallTemplate(), {
+            controlFlowStorageKey: this.controlFlowStorageKey,
+            controlFlowStorageName: this.controlFlowStorageName,
+            leftValue: this.leftValue,
+            rightValue: this.rightValue
+        });
+    }
+}

+ 57 - 0
src/custom-nodes/control-flow-storage-nodes/ControlFlowStorageNode.ts

@@ -0,0 +1,57 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as format from 'string-template';
+
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IStorage } from '../../interfaces/storages/IStorage';
+
+import { initializable } from '../../decorators/Initializable';
+
+import { ControlFlowStorageTemplate } from '../../templates/custom-nodes/control-flow-storage-nodes/ControlFlowStorageTemplate';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+
+@injectable()
+export class ControlFlowStorageNode extends AbstractCustomNode {
+    /**
+     * @type {IStorage <ICustomNode>}
+     */
+    @initializable()
+    private controlFlowStorage: IStorage <ICustomNode>;
+
+    /**
+     * @type {string}
+     */
+    @initializable()
+    private controlFlowStorageName: string;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+    }
+
+    /**
+     * @param controlFlowStorage
+     * @param controlFlowStorageName
+     */
+    public initialize (controlFlowStorage: IStorage <ICustomNode>, controlFlowStorageName: string): void {
+        this.controlFlowStorage = controlFlowStorage;
+        this.controlFlowStorageName = controlFlowStorageName;
+    }
+
+    /**
+     * @returns {string}
+     */
+    public getCode (): string {
+        return format(ControlFlowStorageTemplate(), {
+            controlFlowStorage: this.controlFlowStorage.toString(),
+            controlFlowStorageName: this.controlFlowStorageName
+        });
+    }
+}

+ 18 - 26
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts

@@ -1,54 +1,46 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
+import { IOptions } from '../../interfaces/options/IOptions';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { initializable } from '../../decorators/Initializable';
 
 
 import { DebugProtectionFunctionCallTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-call-node/DebufProtectionFunctionCallTemplate';
 import { DebugProtectionFunctionCallTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-call-node/DebufProtectionFunctionCallTemplate';
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
 
+@injectable()
 export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
 export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private debugProtectionFunctionName: string;
     private debugProtectionFunctionName: string;
 
 
     /**
     /**
-     * @param debugProtectionFunctionName
      * @param options
      * @param options
      */
      */
-    constructor (debugProtectionFunctionName: string, options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         super(options);
         super(options);
-
-        this.debugProtectionFunctionName = debugProtectionFunctionName;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param debugProtectionFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeAppender.appendNode(blockScopeNode, this.getNode());
+    public initialize (debugProtectionFunctionName: string): void {
+        this.debugProtectionFunctionName = debugProtectionFunctionName;
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            DebugProtectionFunctionCallTemplate().formatUnicorn({
-                debugProtectionFunctionName: this.debugProtectionFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(DebugProtectionFunctionCallTemplate(), {
+            debugProtectionFunctionName: this.debugProtectionFunctionName
+        });
     }
     }
 }
 }

+ 18 - 26
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts

@@ -1,54 +1,46 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
+import { IOptions } from '../../interfaces/options/IOptions';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { initializable } from '../../decorators/Initializable';
 
 
 import { DebugProtectionFunctionIntervalTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-interval-node/DebugProtectionFunctionIntervalTemplate';
 import { DebugProtectionFunctionIntervalTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-interval-node/DebugProtectionFunctionIntervalTemplate';
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
 
+@injectable()
 export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
 export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private debugProtectionFunctionName: string;
     private debugProtectionFunctionName: string;
 
 
     /**
     /**
-     * @param debugProtectionFunctionName
      * @param options
      * @param options
      */
      */
-    constructor (debugProtectionFunctionName: string, options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         super(options);
         super(options);
-
-        this.debugProtectionFunctionName = debugProtectionFunctionName;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param debugProtectionFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeAppender.appendNode(blockScopeNode, this.getNode());
+    public initialize (debugProtectionFunctionName: string): void {
+        this.debugProtectionFunctionName = debugProtectionFunctionName;
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            DebugProtectionFunctionIntervalTemplate().formatUnicorn({
-                debugProtectionFunctionName: this.debugProtectionFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(DebugProtectionFunctionIntervalTemplate(), {
+            debugProtectionFunctionName: this.debugProtectionFunctionName
+        });
     }
     }
 }
 }

+ 17 - 38
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts

@@ -1,67 +1,46 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
+import { IOptions } from '../../interfaces/options/IOptions';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { initializable } from '../../decorators/Initializable';
 
 
 import { DebugProtectionFunctionTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate';
 import { DebugProtectionFunctionTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate';
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { Utils } from '../../Utils';
 
 
+@injectable()
 export class DebugProtectionFunctionNode extends AbstractCustomNode {
 export class DebugProtectionFunctionNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private debugProtectionFunctionName: string;
     private debugProtectionFunctionName: string;
 
 
     /**
     /**
-     * @param debugProtectionFunctionName
      * @param options
      * @param options
      */
      */
-    constructor (debugProtectionFunctionName: string, options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         super(options);
         super(options);
-
-        this.debugProtectionFunctionName = debugProtectionFunctionName;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param debugProtectionFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        let programBodyLength: number = blockScopeNode.body.length,
-            randomIndex: number = Utils.getRandomInteger(0, programBodyLength);
-
-        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
+    public initialize (debugProtectionFunctionName: string): void {
+        this.debugProtectionFunctionName = debugProtectionFunctionName;
     }
     }
 
 
     /**
     /**
      * @returns {string}
      * @returns {string}
      */
      */
-    public getNodeIdentifier (): string {
-        return this.debugProtectionFunctionName;
-    }
-
-    /**
-     * Found this trick in JScrambler
-     *
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            DebugProtectionFunctionTemplate().formatUnicorn({
-                debugProtectionFunctionName: this.debugProtectionFunctionName
-            })
-        );
+    public getCode (): string {
+        return format(DebugProtectionFunctionTemplate(), {
+            debugProtectionFunctionName: this.debugProtectionFunctionName
+        });
     }
     }
 }
 }

+ 109 - 0
src/custom-nodes/debug-protection-nodes/group/DebugProtectionCustomNodeGroup.ts

@@ -0,0 +1,109 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TObfuscationEvent } from '../../../types/event-emitters/TObfuscationEvent';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IObfuscationEventEmitter } from '../../../interfaces/event-emitters/IObfuscationEventEmitter';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStackTraceData } from '../../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+import { ObfuscationEvents } from '../../../enums/ObfuscationEvents';
+
+import { AbstractCustomNodeGroup } from '../../AbstractCustomNodeGroup';
+import { NodeAppender } from '../../../node/NodeAppender';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class DebugProtectionCustomNodeGroup extends AbstractCustomNodeGroup {
+    /**
+     * @type {TObfuscationEvent}
+     */
+    protected readonly appendEvent: TObfuscationEvent = ObfuscationEvents.BeforeObfuscation;
+
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @type {Map<CustomNodes, ICustomNode>}
+     */
+    @initializable()
+    protected customNodes: Map <CustomNodes, ICustomNode>;
+
+    /**
+     * @type {IObfuscationEventEmitter}
+     */
+    private readonly obfuscationEventEmitter: IObfuscationEventEmitter;
+
+    /**
+     * @param customNodeFactory
+     * @param obfuscationEventEmitter
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.customNodeFactory = customNodeFactory;
+        this.obfuscationEventEmitter = obfuscationEventEmitter;
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+        // debugProtectionFunctionNode append
+        this.appendCustomNodeIfExist(CustomNodes.DebugProtectionFunctionNode, (customNode: ICustomNode) => {
+            NodeAppender.appendNode(blockScopeNode, customNode.getNode());
+        });
+
+        // debugProtectionFunctionCallNode append
+        this.appendCustomNodeIfExist(CustomNodes.DebugProtectionFunctionCallNode, (customNode: ICustomNode) => {
+            NodeAppender.appendNode(blockScopeNode, customNode.getNode());
+        });
+
+        // debugProtectionFunctionIntervalNode append
+        this.appendCustomNodeIfExist(CustomNodes.DebugProtectionFunctionIntervalNode, (customNode: ICustomNode) => {
+            const programBodyLength: number = blockScopeNode.body.length;
+            const randomIndex: number = RandomGeneratorUtils.getRandomInteger(0, programBodyLength);
+
+            NodeAppender.insertNodeAtIndex(blockScopeNode, customNode.getNode(), randomIndex);
+        });
+    }
+
+    public initialize (): void {
+        this.customNodes = new Map <CustomNodes, ICustomNode> ();
+
+        if (!this.options.debugProtection) {
+            return;
+        }
+
+        const debugProtectionFunctionName: string = RandomGeneratorUtils.getRandomVariableName();
+
+        const debugProtectionFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.DebugProtectionFunctionNode);
+        const debugProtectionFunctionCallNode: ICustomNode = this.customNodeFactory(CustomNodes.DebugProtectionFunctionCallNode);
+        const debugProtectionFunctionIntervalNode: ICustomNode = this.customNodeFactory(CustomNodes.DebugProtectionFunctionIntervalNode);
+
+        debugProtectionFunctionNode.initialize(debugProtectionFunctionName);
+        debugProtectionFunctionCallNode.initialize(debugProtectionFunctionName);
+        debugProtectionFunctionIntervalNode.initialize(debugProtectionFunctionName);
+
+        this.customNodes.set(CustomNodes.DebugProtectionFunctionNode, debugProtectionFunctionNode);
+        this.customNodes.set(CustomNodes.DebugProtectionFunctionCallNode, debugProtectionFunctionCallNode);
+
+        if (this.options.debugProtectionInterval) {
+            this.customNodes.set(CustomNodes.DebugProtectionFunctionIntervalNode, debugProtectionFunctionIntervalNode);
+        }
+    }
+}

+ 23 - 55
src/custom-nodes/domain-lock-nodes/DomainLockNode.ts

@@ -1,86 +1,54 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
-import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
+import { IOptions } from '../../interfaces/options/IOptions';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { initializable } from '../../decorators/Initializable';
 
 
 import { DomainLockNodeTemplate } from '../../templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate';
 import { DomainLockNodeTemplate } from '../../templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate';
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { Utils } from '../../Utils';
+import { CryptUtils } from '../../utils/CryptUtils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 
 
+@injectable()
 export class DomainLockNode extends AbstractCustomNode {
 export class DomainLockNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     protected callsControllerFunctionName: string;
     protected callsControllerFunctionName: string;
 
 
     /**
     /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
-
-        this.stackTraceData = stackTraceData;
-        this.callsControllerFunctionName = callsControllerFunctionName;
-        this.randomStackTraceIndex = randomStackTraceIndex;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param callsControllerFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeAppender.appendNodeToOptimalBlockScope(
-            this.stackTraceData,
-            blockScopeNode,
-            this.getNode(),
-            this.randomStackTraceIndex
-        );
+    public initialize (callsControllerFunctionName: string): void {
+        this.callsControllerFunctionName = callsControllerFunctionName;
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
+    public getCode (): string {
         let domainsString: string = this.options.domainLock.join(';'),
         let domainsString: string = this.options.domainLock.join(';'),
-            [hiddenDomainsString, diff]: string[] = Utils.hideString(domainsString, domainsString.length * 3);
-
-        return NodeUtils.convertCodeToStructure(
-            DomainLockNodeTemplate().formatUnicorn({
-                domainLockFunctionName: Utils.getRandomVariableName(),
-                diff: diff,
-                domains: hiddenDomainsString,
-                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            })
-        );
+            [hiddenDomainsString, diff]: string[] = CryptUtils.hideString(domainsString, domainsString.length * 3);
+
+        return format(DomainLockNodeTemplate(), {
+            domainLockFunctionName: RandomGeneratorUtils.getRandomVariableName(),
+            diff: diff,
+            domains: hiddenDomainsString,
+            singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+        });
     }
     }
 }
 }

+ 110 - 0
src/custom-nodes/domain-lock-nodes/group/DomainLockCustomNodeGroup.ts

@@ -0,0 +1,110 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TObfuscationEvent } from '../../../types/event-emitters/TObfuscationEvent';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IObfuscationEventEmitter } from '../../../interfaces/event-emitters/IObfuscationEventEmitter';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStackTraceData } from '../../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+import { ObfuscationEvents } from '../../../enums/ObfuscationEvents';
+
+import { AbstractCustomNodeGroup } from '../../AbstractCustomNodeGroup';
+import { NodeAppender } from '../../../node/NodeAppender';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class DomainLockCustomNodeGroup extends AbstractCustomNodeGroup {
+    /**
+     * @type {TObfuscationEvent}
+     */
+    protected readonly appendEvent: TObfuscationEvent = ObfuscationEvents.BeforeObfuscation;
+
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @type {Map<CustomNodes, ICustomNode>}
+     */
+    @initializable()
+    protected customNodes: Map <CustomNodes, ICustomNode>;
+
+    /**
+     * @type {IObfuscationEventEmitter}
+     */
+    private readonly obfuscationEventEmitter: IObfuscationEventEmitter;
+
+    /**
+     * @param customNodeFactory
+     * @param obfuscationEventEmitter
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.customNodeFactory = customNodeFactory;
+        this.obfuscationEventEmitter = obfuscationEventEmitter;
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(stackTraceData.length);
+
+        // domainLockNode append
+        this.appendCustomNodeIfExist(CustomNodes.DomainLockNode, (customNode: ICustomNode) => {
+            NodeAppender.appendNodeToOptimalBlockScope(
+                stackTraceData,
+                blockScopeNode,
+                customNode.getNode(),
+                randomStackTraceIndex
+            );
+        });
+
+        // nodeCallsControllerFunctionNode append
+        this.appendCustomNodeIfExist(CustomNodes.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
+            let targetBlockScope: TNodeWithBlockStatement;
+
+            if (stackTraceData.length) {
+                targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);
+            } else {
+                targetBlockScope = blockScopeNode;
+            }
+
+            NodeAppender.prependNode(targetBlockScope, customNode.getNode());
+        });
+    }
+
+    public initialize (): void {
+        this.customNodes = new Map <CustomNodes, ICustomNode> ();
+
+        if (!this.options.domainLock.length) {
+            return;
+        }
+
+        const callsControllerFunctionName: string = RandomGeneratorUtils.getRandomVariableName();
+
+        const domainLockNode: ICustomNode = this.customNodeFactory(CustomNodes.DomainLockNode);
+        const nodeCallsControllerFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.NodeCallsControllerFunctionNode);
+
+        domainLockNode.initialize(callsControllerFunctionName);
+        nodeCallsControllerFunctionNode.initialize(this.appendEvent, callsControllerFunctionName);
+
+        this.customNodes.set(CustomNodes.DomainLockNode, domainLockNode);
+        this.customNodes.set(CustomNodes.NodeCallsControllerFunctionNode, nodeCallsControllerFunctionNode);
+    }
+}

+ 35 - 61
src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts

@@ -1,12 +1,15 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
-import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
+import { TObfuscationEvent } from '../../types/event-emitters/TObfuscationEvent';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { ObfuscationEvents } from '../../enums/ObfuscationEvents';
+
+import { initializable } from '../../decorators/Initializable';
 
 
 import { SingleNodeCallControllerTemplate } from '../../templates/custom-nodes/SingleNodeCallControllerTemplate';
 import { SingleNodeCallControllerTemplate } from '../../templates/custom-nodes/SingleNodeCallControllerTemplate';
 
 
@@ -14,86 +17,57 @@ import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
 
+@injectable()
 export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
 export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     /**
     /**
-     * @type {AppendState}
+     * @type {TObfuscationEvent}
      */
      */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
+    @initializable()
+    private appendEvent: TObfuscationEvent;
 
 
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     protected callsControllerFunctionName: string;
     protected callsControllerFunctionName: string;
 
 
     /**
     /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
-
-        this.stackTraceData = stackTraceData;
-        this.callsControllerFunctionName = callsControllerFunctionName;
-        this.randomStackTraceIndex = randomStackTraceIndex;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param appendEvent
+     * @param callsControllerFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        let targetBlockScope: TNodeWithBlockStatement;
-
-        if (this.stackTraceData.length) {
-            targetBlockScope = NodeAppender
-                .getOptimalBlockScope(this.stackTraceData, this.randomStackTraceIndex, 1);
-        } else {
-            targetBlockScope = blockScopeNode;
-        }
-
-        NodeAppender.prependNode(targetBlockScope, this.getNode());
+    public initialize (appendEvent: TObfuscationEvent, callsControllerFunctionName: string): void {
+        this.appendEvent = appendEvent;
+        this.callsControllerFunctionName = callsControllerFunctionName;
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
-        if (this.appendState === AppendState.AfterObfuscation) {
-            return NodeUtils.convertCodeToStructure(
-                JavaScriptObfuscator.obfuscate(
-                    SingleNodeCallControllerTemplate().formatUnicorn({
-                        singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                    }),
-                    Object.assign({}, NO_CUSTOM_NODES_PRESET, {
-                        seed: this.options.seed
-                    })
-                ).getObfuscatedCode()
-            );
+    public getCode (): string {
+        if (this.appendEvent === ObfuscationEvents.AfterObfuscation) {
+            return JavaScriptObfuscator.obfuscate(
+                format(SingleNodeCallControllerTemplate(), {
+                    singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+                }),
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    seed: this.options.seed
+                }
+            ).getObfuscatedCode();
         }
         }
 
 
-        return NodeUtils.convertCodeToStructure(
-            SingleNodeCallControllerTemplate().formatUnicorn({
-                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-            })
-        );
+        return format(SingleNodeCallControllerTemplate(), {
+            singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+        });
     }
     }
 }
 }

+ 25 - 55
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -1,87 +1,57 @@
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { IOptions } from '../../interfaces/IOptions';
-import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
+import * as format from 'string-template';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { initializable } from '../../decorators/Initializable';
 
 
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
 
 import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeUtils } from '../../node/NodeUtils';
-import { Utils } from '../../Utils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 
 
+@injectable()
 export class SelfDefendingUnicodeNode extends AbstractCustomNode {
 export class SelfDefendingUnicodeNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     protected callsControllerFunctionName: string;
     protected callsControllerFunctionName: string;
 
 
     /**
     /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
-
-        this.stackTraceData = stackTraceData;
-        this.callsControllerFunctionName = callsControllerFunctionName;
-        this.randomStackTraceIndex = randomStackTraceIndex;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param callsControllerFunctionName
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeAppender.appendNodeToOptimalBlockScope(
-            this.stackTraceData,
-            blockScopeNode,
-            this.getNode(),
-            this.randomStackTraceIndex
-        );
+    public initialize (callsControllerFunctionName: string): void {
+        this.callsControllerFunctionName = callsControllerFunctionName;
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                SelfDefendingTemplate().formatUnicorn({
-                    selfDefendingFunctionName: Utils.getRandomVariableName(),
-                    singleNodeCallControllerFunctionName: this.callsControllerFunctionName
-                }),
-                Object.assign({},  NO_CUSTOM_NODES_PRESET, {
-                    seed: this.options.seed
-                })
-            ).getObfuscatedCode()
-        );
+    public getCode (): string {
+        return JavaScriptObfuscator.obfuscate(
+            format(SelfDefendingTemplate(), {
+                selfDefendingFunctionName: RandomGeneratorUtils.getRandomVariableName(),
+                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+            }),
+            {
+                ...NO_CUSTOM_NODES_PRESET,
+                seed: this.options.seed
+            }
+        ).getObfuscatedCode();
     }
     }
 }
 }

+ 110 - 0
src/custom-nodes/self-defending-nodes/group/SelfDefendingCustomNodeGroup.ts

@@ -0,0 +1,110 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TObfuscationEvent } from '../../../types/event-emitters/TObfuscationEvent';
+import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IObfuscationEventEmitter } from '../../../interfaces/event-emitters/IObfuscationEventEmitter';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStackTraceData } from '../../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+import { ObfuscationEvents } from '../../../enums/ObfuscationEvents';
+
+import { AbstractCustomNodeGroup } from '../../AbstractCustomNodeGroup';
+import { NodeAppender } from '../../../node/NodeAppender';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class SelfDefendingCustomNodeGroup extends AbstractCustomNodeGroup {
+    /**
+     * @type {TObfuscationEvent}
+     */
+    protected appendEvent: TObfuscationEvent = ObfuscationEvents.AfterObfuscation;
+
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @type {Map<CustomNodes, ICustomNode>}
+     */
+    @initializable()
+    protected customNodes: Map <CustomNodes, ICustomNode>;
+
+    /**
+     * @type {IObfuscationEventEmitter}
+     */
+    private readonly obfuscationEventEmitter: IObfuscationEventEmitter;
+
+    /**
+     * @param customNodeFactory
+     * @param obfuscationEventEmitter
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.customNodeFactory = customNodeFactory;
+        this.obfuscationEventEmitter = obfuscationEventEmitter;
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(stackTraceData.length);
+
+        // selfDefendingUnicodeNode append
+        this.appendCustomNodeIfExist(CustomNodes.SelfDefendingUnicodeNode, (customNode: ICustomNode) => {
+            NodeAppender.appendNodeToOptimalBlockScope(
+                stackTraceData,
+                blockScopeNode,
+                customNode.getNode(),
+                randomStackTraceIndex
+            );
+        });
+
+        // nodeCallsControllerFunctionNode append
+        this.appendCustomNodeIfExist(CustomNodes.NodeCallsControllerFunctionNode, (customNode: ICustomNode) => {
+            let targetBlockScope: TNodeWithBlockStatement;
+
+            if (stackTraceData.length) {
+                targetBlockScope = NodeAppender.getOptimalBlockScope(stackTraceData, randomStackTraceIndex, 1);
+            } else {
+                targetBlockScope = blockScopeNode;
+            }
+
+            NodeAppender.prependNode(targetBlockScope, customNode.getNode());
+        });
+    }
+
+    public initialize (): void {
+        this.customNodes = new Map <CustomNodes, ICustomNode> ();
+
+        if (!this.options.selfDefending) {
+            return;
+        }
+
+        const callsControllerFunctionName: string = RandomGeneratorUtils.getRandomVariableName();
+
+        const selfDefendingUnicodeNode: ICustomNode = this.customNodeFactory(CustomNodes.SelfDefendingUnicodeNode);
+        const nodeCallsControllerFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.NodeCallsControllerFunctionNode);
+
+        selfDefendingUnicodeNode.initialize(callsControllerFunctionName);
+        nodeCallsControllerFunctionNode.initialize(this.appendEvent, callsControllerFunctionName);
+
+        this.customNodes.set(CustomNodes.SelfDefendingUnicodeNode, selfDefendingUnicodeNode);
+        this.customNodes.set(CustomNodes.NodeCallsControllerFunctionNode, nodeCallsControllerFunctionNode);
+    }
+}

+ 49 - 64
src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts

@@ -1,13 +1,16 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
+import { ICustomNodeWithIdentifier } from '../../interfaces/custom-nodes/ICustomNodeWithIdentifier';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IStorage } from '../../interfaces/storages/IStorage';
 
 
-import { AppendState } from '../../enums/AppendState';
 import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
 
 
+import { initializable } from '../../decorators/Initializable';
+
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
 
 import { AtobTemplate } from '../../templates/custom-nodes/AtobTemplate';
 import { AtobTemplate } from '../../templates/custom-nodes/AtobTemplate';
@@ -19,59 +22,68 @@ import { StringArrayRc4DecodeNodeTemplate } from '../../templates/custom-nodes/s
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { StringArray } from '../../StringArray';
 
 
-export class StringArrayCallsWrapper extends AbstractCustomNode {
+@injectable()
+export class StringArrayCallsWrapper extends AbstractCustomNode implements ICustomNodeWithIdentifier {
     /**
     /**
-     * @type {AppendState}
+     * @type {IStorage <string>}
      */
      */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @type {StringArray}
-     */
-    private stringArray: StringArray;
+    @initializable()
+    private stringArray: IStorage <string>;
 
 
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
-    private stringArrayName: string;
+    @initializable()
+    private stringArrayCallsWrapperName: string;
 
 
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
-    private stringArrayCallsWrapperName: string;
+    @initializable()
+    private stringArrayName: string;
 
 
     /**
     /**
-     * @param stringArrayCallsWrapperName
-     * @param stringArrayName
-     * @param stringArray
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stringArrayCallsWrapperName: string,
-        stringArrayName: string,
-        stringArray: StringArray,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
+    }
 
 
-        this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
-        this.stringArrayName = stringArrayName;
+    /**
+     * @param stringArray
+     * @param stringArrayName
+     * @param stringArrayCallsWrapperName
+     */
+    public initialize (
+        stringArray: IStorage <string>,
+        stringArrayName: string,
+        stringArrayCallsWrapperName: string
+    ): void {
         this.stringArray = stringArray;
         this.stringArray = stringArray;
+        this.stringArrayName = stringArrayName;
+        this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @returns {string}
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.stringArray.getLength()) {
-            return;
-        }
+    public getCode (): string {
+        const decodeNodeTemplate: string = this.getDecodeStringArrayTemplate();
 
 
-        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
+        return JavaScriptObfuscator.obfuscate(
+            format(StringArrayCallsWrapperTemplate(), {
+                decodeNodeTemplate,
+                stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
+                stringArrayName: this.stringArrayName
+            }),
+            {
+                ...NO_CUSTOM_NODES_PRESET,
+                seed: this.options.seed
+            }
+        ).getObfuscatedCode();
     }
     }
 
 
     /**
     /**
@@ -81,22 +93,15 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
         return this.stringArrayCallsWrapperName;
         return this.stringArrayCallsWrapperName;
     };
     };
 
 
-    /**
-     * @returns {TStatement[]}
-     */
-    public getNode (): TStatement[] {
-        return super.getNode();
-    }
-
     /**
     /**
      * @returns {string}
      * @returns {string}
      */
      */
-    protected getDecodeStringArrayTemplate (): string {
+    private getDecodeStringArrayTemplate (): string {
         let decodeStringArrayTemplate: string = '',
         let decodeStringArrayTemplate: string = '',
             selfDefendingCode: string = '';
             selfDefendingCode: string = '';
 
 
         if (this.options.selfDefending) {
         if (this.options.selfDefending) {
-            selfDefendingCode = SelfDefendingTemplate().formatUnicorn({
+            selfDefendingCode = format(SelfDefendingTemplate(), {
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayName: this.stringArrayName
                 stringArrayName: this.stringArrayName
             });
             });
@@ -104,7 +109,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
 
 
         switch (this.options.stringArrayEncoding) {
         switch (this.options.stringArrayEncoding) {
             case StringArrayEncoding.base64:
             case StringArrayEncoding.base64:
-                decodeStringArrayTemplate = StringArrayBase64DecodeNodeTemplate().formatUnicorn({
+                decodeStringArrayTemplate = format(StringArrayBase64DecodeNodeTemplate(), {
                     atobPolyfill: AtobTemplate(),
                     atobPolyfill: AtobTemplate(),
                     selfDefendingCode,
                     selfDefendingCode,
                     stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
                     stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
@@ -113,7 +118,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
                 break;
                 break;
 
 
             case StringArrayEncoding.rc4:
             case StringArrayEncoding.rc4:
-                decodeStringArrayTemplate = StringArrayRc4DecodeNodeTemplate().formatUnicorn({
+                decodeStringArrayTemplate = format(StringArrayRc4DecodeNodeTemplate(), {
                     atobPolyfill: AtobTemplate(),
                     atobPolyfill: AtobTemplate(),
                     rc4Polyfill: Rc4Template(),
                     rc4Polyfill: Rc4Template(),
                     selfDefendingCode,
                     selfDefendingCode,
@@ -125,24 +130,4 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
 
 
         return decodeStringArrayTemplate;
         return decodeStringArrayTemplate;
     }
     }
-
-    /**
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
-        const decodeNodeTemplate: string = this.getDecodeStringArrayTemplate();
-
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                StringArrayCallsWrapperTemplate().formatUnicorn({
-                    decodeNodeTemplate,
-                    stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                    stringArrayName: this.stringArrayName
-                }),
-                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
-                    seed: this.options.seed
-                })
-            ).getObfuscatedCode()
-        );
-    }
 }
 }

+ 36 - 58
src/custom-nodes/string-array-nodes/StringArrayNode.ts

@@ -1,87 +1,84 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
+import { TStatement } from '../../types/node/TStatement';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { ICustomNodeWithData } from '../../interfaces/custom-nodes/ICustomNodeWithData';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IStorage } from '../../interfaces/storages/IStorage';
 
 
-import { StringArray } from '../../StringArray';
+import { initializable } from '../../decorators/Initializable';
 
 
 import { StringArrayTemplate } from '../../templates/custom-nodes/string-array-nodes/string-array-node/StringArrayTemplate';
 import { StringArrayTemplate } from '../../templates/custom-nodes/string-array-nodes/string-array-node/StringArrayTemplate';
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
+import { StringArrayStorage } from '../../storages/string-array/StringArrayStorage';
 
 
-export class StringArrayNode extends AbstractCustomNode {
+@injectable()
+export class StringArrayNode extends AbstractCustomNode implements ICustomNodeWithData {
     /**
     /**
      * @type {number}
      * @type {number}
      */
      */
     public static ARRAY_RANDOM_LENGTH: number = 4;
     public static ARRAY_RANDOM_LENGTH: number = 4;
 
 
     /**
     /**
-     * @type {AppendState}
+     * @type {IStorage <string>}
      */
      */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @type {StringArray}
-     */
-    private stringArray: StringArray;
+    @initializable()
+    private stringArray: IStorage <string>;
 
 
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private stringArrayName: string;
     private stringArrayName: string;
 
 
     /**
     /**
      * @type {number}
      * @type {number}
      */
      */
+    @initializable()
     private stringArrayRotateValue: number;
     private stringArrayRotateValue: number;
 
 
     /**
     /**
-     * @param stringArray
-     * @param stringArrayName
-     * @param stringArrayRotateValue
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stringArray: StringArray,
-        stringArrayName: string,
-        stringArrayRotateValue: number = 0,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
-
-        this.stringArray = stringArray;
-        this.stringArrayName = stringArrayName;
-        this.stringArrayRotateValue = stringArrayRotateValue;
     }
     }
 
 
     /**
     /**
-     * @param blockScopeNode
+     * @param stringArray
+     * @param stringArrayName
+     * @param stringArrayRotateValue
      */
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.stringArray.getLength()) {
-            return;
-        }
-
-        NodeAppender.prependNode(blockScopeNode, this.getNode());
+    public initialize (
+        stringArray: IStorage <string>,
+        stringArrayName: string,
+        stringArrayRotateValue: number
+    ): void {
+        this.stringArray = stringArray;
+        this.stringArrayName = stringArrayName;
+        this.stringArrayRotateValue = stringArrayRotateValue;
     }
     }
 
 
     /**
     /**
      * @returns {string}
      * @returns {string}
      */
      */
-    public getNodeIdentifier (): string {
-        return this.stringArrayName;
+    public getCode (): string {
+        return format(StringArrayTemplate(), {
+            stringArrayName: this.stringArrayName,
+            stringArray: this.stringArray.toString()
+        });
     }
     }
 
 
     /**
     /**
-     * @returns {StringArray}
+     * @returns {IStorage <string>}
      */
      */
-    public getNodeData (): StringArray {
+    public getNodeData (): IStorage <string> {
         return this.stringArray;
         return this.stringArray;
     }
     }
 
 
@@ -89,27 +86,8 @@ export class StringArrayNode extends AbstractCustomNode {
      * @returns {TStatement[]}
      * @returns {TStatement[]}
      */
      */
     public getNode (): TStatement[] {
     public getNode (): TStatement[] {
-        this.stringArray.rotateArray(this.stringArrayRotateValue);
+        (<StringArrayStorage>this.stringArray).rotateArray(this.stringArrayRotateValue);
 
 
         return super.getNode();
         return super.getNode();
     }
     }
-
-    /**
-     * @param data
-     */
-    public updateNodeData (data: string): void {
-        this.stringArray.addToArray(data);
-    }
-
-    /**
-     * @returns {TStatement[]}
-     */
-    protected getNodeStructure (): TStatement[] {
-        return NodeUtils.convertCodeToStructure(
-            StringArrayTemplate().formatUnicorn({
-                stringArrayName: this.stringArrayName,
-                stringArray: this.stringArray.toString()
-            })
-        );
-    }
 }
 }

+ 44 - 60
src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts

@@ -1,11 +1,12 @@
-import 'format-unicorn';
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
 
 
-import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
-import { TStatement } from '../../types/TStatement';
+import * as format from 'string-template';
 
 
-import { IOptions } from '../../interfaces/IOptions';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IStorage } from '../../interfaces/storages/IStorage';
 
 
-import { AppendState } from '../../enums/AppendState';
+import { initializable } from '../../decorators/Initializable';
 
 
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
 
@@ -14,79 +15,63 @@ import { StringArrayRotateFunctionTemplate } from '../../templates/custom-nodes/
 
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { StringArray } from '../../StringArray';
-import { Utils } from '../../Utils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+import { Utils } from '../../utils/Utils';
 
 
+@injectable()
 export class StringArrayRotateFunctionNode extends AbstractCustomNode {
 export class StringArrayRotateFunctionNode extends AbstractCustomNode {
     /**
     /**
-     * @type {AppendState}
+     * @type {IStorage <string>}
      */
      */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @type {StringArray}
-     */
-    private stringArray: StringArray;
+    @initializable()
+    private stringArray: IStorage <string>;
 
 
     /**
     /**
      * @type {string}
      * @type {string}
      */
      */
+    @initializable()
     private stringArrayName: string;
     private stringArrayName: string;
 
 
     /**
     /**
      * @param {number}
      * @param {number}
      */
      */
+    @initializable()
     private stringArrayRotateValue: number;
     private stringArrayRotateValue: number;
 
 
     /**
     /**
-     * @param stringArrayName
-     * @param stringArray
-     * @param stringArrayRotateValue
      * @param options
      * @param options
      */
      */
     constructor (
     constructor (
-        stringArrayName: string,
-        stringArray: StringArray,
-        stringArrayRotateValue: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
     ) {
         super(options);
         super(options);
-
-        this.stringArrayName = stringArrayName;
-        this.stringArray = stringArray;
-        this.stringArrayRotateValue = stringArrayRotateValue;
-    }
-
-    /**
-     * @param blockScopeNode
-     */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.stringArray.getLength()) {
-            return;
-        }
-
-        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @param stringArray
+     * @param stringArrayName
+     * @param stringArrayRotateValue
      */
      */
-    public getNode (): TStatement[] {
-        return super.getNode();
+    public initialize (
+        stringArray: IStorage <string>,
+        stringArrayName: string,
+        stringArrayRotateValue: number
+    ): void {
+        this.stringArray = stringArray;
+        this.stringArrayName = stringArrayName;
+        this.stringArrayRotateValue = stringArrayRotateValue;
     }
     }
 
 
     /**
     /**
-     * @returns {TStatement[]}
+     * @returns {string}
      */
      */
-    protected getNodeStructure (): TStatement[] {
+    public getCode (): string {
         let code: string = '',
         let code: string = '',
-            timesName: string = Utils.getRandomVariableName(),
-            whileFunctionName: string = Utils.getRandomVariableName();
+            timesName: string = RandomGeneratorUtils.getRandomVariableName(),
+            whileFunctionName: string = RandomGeneratorUtils.getRandomVariableName();
 
 
         if (this.options.selfDefending) {
         if (this.options.selfDefending) {
-            code = SelfDefendingTemplate().formatUnicorn({
+            code = format(SelfDefendingTemplate(), {
                 timesName,
                 timesName,
                 whileFunctionName
                 whileFunctionName
             });
             });
@@ -94,19 +79,18 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
             code = `${whileFunctionName}(++${timesName})`;
             code = `${whileFunctionName}(++${timesName})`;
         }
         }
 
 
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                StringArrayRotateFunctionTemplate().formatUnicorn({
-                    code,
-                    timesName,
-                    stringArrayName: this.stringArrayName,
-                    stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
-                    whileFunctionName
-                }),
-                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
-                    seed: this.options.seed
-                })
-            ).getObfuscatedCode()
-        );
+        return JavaScriptObfuscator.obfuscate(
+            format(StringArrayRotateFunctionTemplate(), {
+                code,
+                timesName,
+                stringArrayName: this.stringArrayName,
+                stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
+                whileFunctionName
+            }),
+            {
+                ...NO_CUSTOM_NODES_PRESET,
+                seed: this.options.seed
+            }
+        ).getObfuscatedCode();
     }
     }
 }
 }

+ 130 - 0
src/custom-nodes/string-array-nodes/group/StringArrayCustomNodeGroup.ts

@@ -0,0 +1,130 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+import { TNodeWithBlockStatement } from '../../../types/node/TNodeWithBlockStatement';
+import { TObfuscationEvent } from '../../../types/event-emitters/TObfuscationEvent';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IObfuscationEventEmitter } from '../../../interfaces/event-emitters/IObfuscationEventEmitter';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStackTraceData } from '../../../interfaces/stack-trace-analyzer/IStackTraceData';
+import { IStorage } from '../../../interfaces/storages/IStorage';
+
+import { initializable } from '../../../decorators/Initializable';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+import { ObfuscationEvents } from '../../../enums/ObfuscationEvents';
+
+import { StringArrayNode } from '../StringArrayNode';
+
+import { AbstractCustomNodeGroup } from '../../AbstractCustomNodeGroup';
+import { NodeAppender } from '../../../node/NodeAppender';
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+import { StringArrayStorage } from '../../../storages/string-array/StringArrayStorage';
+
+@injectable()
+export class StringArrayCustomNodeGroup extends AbstractCustomNodeGroup {
+    /**
+     * @type {TObfuscationEvent}
+     */
+    protected appendEvent: TObfuscationEvent = ObfuscationEvents.AfterObfuscation;
+
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @type {Map<CustomNodes, ICustomNode>}
+     */
+    @initializable()
+    protected customNodes: Map <CustomNodes, ICustomNode>;
+
+    /**
+     * @type {IObfuscationEventEmitter}
+     */
+    private readonly obfuscationEventEmitter: IObfuscationEventEmitter;
+
+    /**
+     * @type {IStorage <string>}
+     */
+    @initializable()
+    private stringArray: IStorage <string>;
+
+    /**
+     * @param customNodeFactory
+     * @param obfuscationEventEmitter
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IObfuscationEventEmitter) obfuscationEventEmitter: IObfuscationEventEmitter,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.customNodeFactory = customNodeFactory;
+        this.obfuscationEventEmitter = obfuscationEventEmitter;
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    public appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void {
+        if (!this.stringArray.getLength()) {
+            return;
+        }
+
+        // stringArrayNode append
+        this.appendCustomNodeIfExist(CustomNodes.StringArrayNode, (customNode: ICustomNode) => {
+            NodeAppender.prependNode(blockScopeNode, customNode.getNode());
+        });
+
+        // stringArrayCallsWrapper append
+        this.appendCustomNodeIfExist(CustomNodes.StringArrayCallsWrapper, (customNode: ICustomNode) => {
+            NodeAppender.insertNodeAtIndex(blockScopeNode, customNode.getNode(), 1);
+        });
+
+        // stringArrayRotateFunctionNode append
+        this.appendCustomNodeIfExist(CustomNodes.StringArrayRotateFunctionNode, (customNode: ICustomNode) => {
+            NodeAppender.insertNodeAtIndex(blockScopeNode, customNode.getNode(), 1);
+        });
+    }
+
+    public initialize (): void {
+        this.customNodes = new Map <CustomNodes, ICustomNode> ();
+        this.stringArray = new StringArrayStorage();
+
+        if (!this.options.stringArray) {
+            return;
+        }
+
+        const stringArrayNode: ICustomNode = this.customNodeFactory(CustomNodes.StringArrayNode);
+        const stringArrayCallsWrapper: ICustomNode = this.customNodeFactory(CustomNodes.StringArrayCallsWrapper);
+        const stringArrayRotateFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.StringArrayRotateFunctionNode);
+
+        const stringArrayName: string = RandomGeneratorUtils.getRandomVariableName(StringArrayNode.ARRAY_RANDOM_LENGTH);
+        const stringArrayCallsWrapperName: string = RandomGeneratorUtils.getRandomVariableName(StringArrayNode.ARRAY_RANDOM_LENGTH);
+
+        let stringArrayRotateValue: number;
+
+        if (this.options.rotateStringArray) {
+            stringArrayRotateValue = RandomGeneratorUtils.getRandomInteger(100, 500);
+        } else {
+            stringArrayRotateValue = 0;
+        }
+
+        stringArrayNode.initialize(this.stringArray, stringArrayName, stringArrayRotateValue);
+        stringArrayCallsWrapper.initialize(this.stringArray, stringArrayName, stringArrayCallsWrapperName);
+        stringArrayRotateFunctionNode.initialize(this.stringArray, stringArrayName, stringArrayRotateValue);
+
+        this.customNodes.set(CustomNodes.StringArrayNode, stringArrayNode);
+        this.customNodes.set(CustomNodes.StringArrayCallsWrapper, stringArrayCallsWrapper);
+
+        if (this.options.rotateStringArray) {
+            this.customNodes.set(CustomNodes.StringArrayRotateFunctionNode, stringArrayRotateFunctionNode);
+        }
+    }
+}

+ 58 - 0
src/decorators/Initializable.ts

@@ -0,0 +1,58 @@
+/* tslint:disable:no-invalid-this */
+
+import { IInitializable } from '../interfaces/IInitializable';
+
+/**
+ * @param initializeMethodKey
+ * @returns {(target:IInitializable, propertyKey:(string|symbol))=>PropertyDescriptor}
+ */
+export function initializable (
+    initializeMethodKey: string = 'initialize'
+): (target: IInitializable, propertyKey: string | symbol) => any {
+    const decoratorName: string = Object.keys(this)[0];
+
+    return (target: IInitializable, propertyKey: string | symbol): any => {
+        const initializeMethod: any = (<any>target)[initializeMethodKey];
+
+        if (!initializeMethod || typeof initializeMethod !== 'function') {
+           throw new Error(`\`${initializeMethodKey}\` method with initialization logic not found. \`@${decoratorName}\` decorator requires \`${initializeMethodKey}\` method`);
+        }
+
+        const methodDescriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor(target, initializeMethodKey) || {
+            configurable: true,
+            enumerable: true
+        };
+        const originalMethod: Function = methodDescriptor.value;
+
+        methodDescriptor.value = function (): void {
+            originalMethod.apply(this, arguments);
+
+            // call property getter to activate initialization check inside it
+            if (this[propertyKey]) {}
+        };
+
+        Object.defineProperty(target, initializeMethodKey, methodDescriptor);
+
+        const metadataPropertyKey: string = `_${propertyKey}`;
+        const propertyDescriptor: PropertyDescriptor = Object.getOwnPropertyDescriptor(target, metadataPropertyKey) || {
+            configurable: true,
+            enumerable: true
+        };
+
+        propertyDescriptor.get = function(): any {
+            if (this[metadataPropertyKey] === undefined) {
+                throw new Error(`Property \`${propertyKey}\` is not initialized! Initialize it first!`);
+            }
+
+            return this[metadataPropertyKey];
+        };
+
+        propertyDescriptor.set = function (newVal: any): void {
+            this[metadataPropertyKey] = newVal;
+        };
+
+        Object.defineProperty(target, propertyKey, propertyDescriptor);
+
+        return propertyDescriptor;
+    };
+}

+ 0 - 4
src/enums/AppendState.ts

@@ -1,4 +0,0 @@
-export enum AppendState {
-    AfterObfuscation,
-    BeforeObfuscation
-}

+ 1 - 1
src/enums/NodeType.ts

@@ -1,4 +1,4 @@
-import { Utils } from '../Utils';
+import { Utils } from '../utils/Utils';
 
 
 export const NodeType: any = Utils.strEnumify({
 export const NodeType: any = Utils.strEnumify({
     ArrayExpression: 'ArrayExpression',
     ArrayExpression: 'ArrayExpression',

+ 9 - 0
src/enums/ObfuscationEvents.ts

@@ -0,0 +1,9 @@
+import { TObfuscationEvent } from '../types/event-emitters/TObfuscationEvent';
+
+export const ObfuscationEvents: {
+    AfterObfuscation: TObfuscationEvent,
+    BeforeObfuscation: TObfuscationEvent
+} = {
+    AfterObfuscation: 'afterObfuscation',
+    BeforeObfuscation: 'beforeObfuscation'
+};

+ 6 - 0
src/enums/VisitorDirection.ts

@@ -0,0 +1,6 @@
+import { Utils } from '../utils/Utils';
+
+export const VisitorDirection: any = Utils.strEnumify({
+    enter: 'enter',
+    leave: 'leave'
+});

+ 5 - 0
src/enums/container/CalleeDataExtractors.ts

@@ -0,0 +1,5 @@
+export enum CalleeDataExtractors {
+    FunctionDeclarationCalleeDataExtractor,
+    FunctionExpressionCalleeDataExtractor,
+    ObjectExpressionCalleeDataExtractor,
+}

+ 7 - 0
src/enums/container/CustomNodeGroups.ts

@@ -0,0 +1,7 @@
+export enum CustomNodeGroups {
+    ConsoleOutputCustomNodeGroup,
+    DebugProtectionCustomNodeGroup,
+    DomainLockCustomNodeGroup,
+    SelfDefendingCustomNodeGroup,
+    StringArrayCustomNodeGroup
+}

+ 15 - 0
src/enums/container/CustomNodes.ts

@@ -0,0 +1,15 @@
+export enum CustomNodes {
+    BinaryExpressionFunctionNode,
+    ControlFlowStorageCallNode,
+    ControlFlowStorageNode,
+    ConsoleOutputDisableExpressionNode,
+    DebugProtectionFunctionCallNode,
+    DebugProtectionFunctionIntervalNode,
+    DebugProtectionFunctionNode,
+    DomainLockNode,
+    NodeCallsControllerFunctionNode,
+    SelfDefendingUnicodeNode,
+    StringArrayCallsWrapper,
+    StringArrayNode,
+    StringArrayRotateFunctionNode
+}

+ 3 - 0
src/enums/container/NodeControlFlowReplacers.ts

@@ -0,0 +1,3 @@
+export enum NodeControlFlowReplacers {
+    BinaryExpressionControlFlowReplacer
+}

+ 6 - 0
src/enums/container/NodeObfuscatorsReplacers.ts

@@ -0,0 +1,6 @@
+export enum NodeObfuscatorsReplacers {
+    BooleanReplacer,
+    IdentifierReplacer,
+    NumberLiteralReplacer,
+    StringLiteralReplacer
+}

+ 12 - 0
src/enums/container/NodeTransformers.ts

@@ -0,0 +1,12 @@
+export enum NodeTransformers {
+    FunctionControlFlowTransformer,
+    CatchClauseObfuscator,
+    FunctionDeclarationObfuscator,
+    FunctionObfuscator,
+    LabeledStatementObfuscator,
+    LiteralObfuscator,
+    MemberExpressionObfuscator,
+    MethodDefinitionObfuscator,
+    ObjectExpressionObfuscator,
+    VariableDeclarationObfuscator
+}

+ 10 - 0
src/event-emitters/ObfuscationEventEmitter.ts

@@ -0,0 +1,10 @@
+import { decorate, injectable } from 'inversify';
+
+import { IObfuscationEventEmitter } from '../interfaces/event-emitters/IObfuscationEventEmitter';
+
+import { EventEmitter } from 'events';
+
+decorate(injectable(), EventEmitter);
+
+@injectable()
+export class ObfuscationEventEmitter extends EventEmitter implements IObfuscationEventEmitter {}

+ 6 - 0
src/interfaces/IInitializable.d.ts

@@ -0,0 +1,6 @@
+export interface IInitializable {
+    /**
+     * @param args
+     */
+    initialize (...args: any[]): void;
+}

+ 5 - 0
src/interfaces/IJavaScriptObfsucator.d.ts

@@ -0,0 +1,5 @@
+import { IObfuscationResult } from './IObfuscationResult';
+
+export interface IJavaScriptObfuscator {
+    obfuscate (sourceCode: string): IObfuscationResult;
+}

+ 0 - 9
src/interfaces/INodeObfuscator.d.ts

@@ -1,9 +0,0 @@
-import * as ESTree from 'estree';
-
-export interface INodeObfuscator {
-    /**
-     * @param node
-     * @param parentNode
-     */
-    obfuscateNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
-}

+ 0 - 8
src/interfaces/INodesGroup.d.ts

@@ -1,8 +0,0 @@
-import { ICustomNode } from './custom-nodes/ICustomNode';
-
-export interface INodesGroup {
-    /**
-     * @returns {Map <string, ICustomNode> | undefined}
-     */
-    getNodes (): Map <string, ICustomNode> | undefined;
-}

+ 3 - 1
src/interfaces/IObfuscationResult.d.ts

@@ -1,4 +1,6 @@
-export interface IObfuscationResult {
+import { IInitializable } from './IInitializable';
+
+export interface IObfuscationResult extends IInitializable {
     /**
     /**
      * @return {string}
      * @return {string}
      */
      */

+ 1 - 1
src/interfaces/IObfuscator.d.ts

@@ -1,5 +1,5 @@
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
 export interface IObfuscator {
 export interface IObfuscator {
-    obfuscateNode (node: ESTree.Node): ESTree.Node;
+    obfuscateAstTree (astTree: ESTree.Program): ESTree.Program;
 }
 }

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

@@ -1,23 +0,0 @@
-import { TSourceMapMode } from '../types/TSourceMapMode';
-import { TStringArrayEncoding } from '../types/TStringArrayEncoding';
-
-export interface IObfuscatorOptions {
-    compact?: boolean;
-    debugProtection?: boolean;
-    debugProtectionInterval?: boolean;
-    disableConsoleOutput?: boolean;
-    domainLock?: string[];
-    reservedNames?: string[];
-    rotateStringArray?: boolean;
-    seed?: number;
-    selfDefending?: boolean;
-    sourceMap?: boolean;
-    sourceMapBaseUrl?: string;
-    sourceMapFileName?: string;
-    sourceMapMode?: TSourceMapMode;
-    stringArray?: boolean;
-    stringArrayEncoding?: TStringArrayEncoding;
-    stringArrayThreshold?: number;
-    unicodeEscapeSequence?: boolean;
-    [key: string]: any;
-}

+ 0 - 3
src/interfaces/IReplacer.d.ts

@@ -1,3 +0,0 @@
-export interface IReplacer {
-    replace (nodeValue: any, namesMap?: Map <string, string>): string;
-}

+ 1 - 1
src/interfaces/ISourceMapCorrector.d.ts

@@ -1,5 +1,5 @@
 import { IObfuscationResult } from './IObfuscationResult';
 import { IObfuscationResult } from './IObfuscationResult';
 
 
 export interface ISourceMapCorrector {
 export interface ISourceMapCorrector {
-    correct (): IObfuscationResult;
+    correct (obfuscatedCode: string, sourceMap: string): IObfuscationResult;
 }
 }

+ 14 - 0
src/interfaces/container/IInversifyContainerFacade.d.ts

@@ -0,0 +1,14 @@
+import { interfaces } from 'inversify';
+
+export interface IInversifyContainerFacade {
+    /**
+     * @param serviceIdentifier
+     */
+    get <T> (serviceIdentifier: interfaces.ServiceIdentifier<T>): T;
+
+    /**
+     * @param serviceIdentifier
+     * @param named
+     */
+    getNamed <T> (serviceIdentifier: interfaces.ServiceIdentifier<T>, named: string | number | symbol): T;
+}

+ 5 - 17
src/interfaces/custom-nodes/ICustomNode.d.ts

@@ -1,27 +1,15 @@
-import * as ESTree from 'estree';
+import { TStatement } from '../../types/node/TStatement';
 
 
-import { TStatement } from '../../types/TStatement';
-
-import { AppendState } from '../../enums/AppendState';
-
-export interface ICustomNode {
-    /**
-     * @param astTree
-     */
-    appendNode (astTree: ESTree.Node): void;
+import { IInitializable } from '../IInitializable';
 
 
+export interface ICustomNode extends IInitializable {
     /**
     /**
-     * @returns {AppendState}
+     * @returns {string}
      */
      */
-    getAppendState (): AppendState;
+    getCode (): string;
 
 
     /**
     /**
      * @returns ESTree.Node[]
      * @returns ESTree.Node[]
      */
      */
     getNode (): TStatement[];
     getNode (): TStatement[];
-
-    /**
-     * @param appendState
-     */
-    setAppendState (appendState: AppendState): void;
 }
 }

+ 29 - 0
src/interfaces/custom-nodes/ICustomNodeGroup.d.ts

@@ -0,0 +1,29 @@
+import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+import { TObfuscationEvent } from '../../types/event-emitters/TObfuscationEvent';
+
+import { ICustomNode } from './ICustomNode';
+import { IInitializable } from '../IInitializable';
+import { IStackTraceData } from '../stack-trace-analyzer/IStackTraceData';
+
+import { CustomNodes } from '../../enums/container/CustomNodes';
+
+export interface ICustomNodeGroup extends IInitializable {
+    /**
+     * @param blockScopeNode
+     * @param stackTraceData
+     */
+    appendCustomNodes (blockScopeNode: TNodeWithBlockStatement, stackTraceData: IStackTraceData[]): void;
+
+    /**
+     * @returns {TObfuscationEvent}
+     */
+    getAppendEvent (): TObfuscationEvent;
+
+    /**
+     * @type {Map <CustomNodes, ICustomNode>}
+     */
+    getCustomNodes (): Map <CustomNodes, ICustomNode>;
+
+
+    initialize (): void;
+}

+ 0 - 5
src/interfaces/custom-nodes/ICustomNodeWithData.d.ts

@@ -5,9 +5,4 @@ export interface ICustomNodeWithData extends ICustomNode {
      * @returns any
      * @returns any
      */
      */
     getNodeData (): any;
     getNodeData (): any;
-
-    /**
-     * @param data
-     */
-    updateNodeData (data: any): void;
 }
 }

+ 0 - 3
src/interfaces/custom-nodes/ICustomNodeWithIdentifier.d.ts

@@ -1,8 +1,5 @@
 import { ICustomNode } from './ICustomNode';
 import { ICustomNode } from './ICustomNode';
 
 
 export interface ICustomNodeWithIdentifier extends ICustomNode {
 export interface ICustomNodeWithIdentifier extends ICustomNode {
-    /**
-     * @returns {string}
-     */
     getNodeIdentifier (): string;
     getNodeIdentifier (): string;
 }
 }

+ 8 - 0
src/interfaces/event-emitters/IObfuscationEventEmitter.d.ts

@@ -0,0 +1,8 @@
+import Events = NodeJS.Events;
+
+import { TObfuscationEvent } from '../../types/event-emitters/TObfuscationEvent';
+
+export interface IObfuscationEventEmitter extends Events {
+    on(event: TObfuscationEvent, listener: Function): this;
+    once(event: TObfuscationEvent, listener: Function): this;
+}

+ 13 - 0
src/interfaces/node-transformers/IControlFlowReplacer.d.ts

@@ -0,0 +1,13 @@
+import * as ESTree from 'estree';
+
+import { ICustomNode } from '../custom-nodes/ICustomNode';
+import { IStorage } from '../storages/IStorage';
+
+export interface IControlFlowReplacer {
+    replace (
+        node: ESTree.Node,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageCustomNodeName: string
+    ): ICustomNode | undefined;
+}

+ 5 - 0
src/interfaces/node-transformers/INodeTransformer.d.ts

@@ -0,0 +1,5 @@
+import * as ESTree from 'estree';
+
+export interface INodeTransformer {
+    transformNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
+}

+ 3 - 0
src/interfaces/node-transformers/IReplacer.d.ts

@@ -0,0 +1,3 @@
+export interface IReplacer {
+    replace (nodeValue: any): string;
+}

+ 3 - 2
src/interfaces/IOptions.d.ts → src/interfaces/options/IOptions.d.ts

@@ -1,8 +1,9 @@
-import { TSourceMapMode } from '../types/TSourceMapMode';
-import { TStringArrayEncoding } from '../types/TStringArrayEncoding';
+import { TSourceMapMode } from '../../types/TSourceMapMode';
+import { TStringArrayEncoding } from '../../types/options/TStringArrayEncoding';
 
 
 export interface IOptions {
 export interface IOptions {
     readonly compact: boolean;
     readonly compact: boolean;
+    readonly controlFlowFlattening: boolean;
     readonly debugProtection: boolean;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly disableConsoleOutput: boolean;

+ 3 - 1
src/interfaces/stack-trace-analyzer/ICalleeDataExtractor.d.ts

@@ -1,5 +1,7 @@
+import * as ESTree from 'estree';
+
 import { ICalleeData } from './ICalleeData';
 import { ICalleeData } from './ICalleeData';
 
 
 export interface ICalleeDataExtractor {
 export interface ICalleeDataExtractor {
-    extract (): ICalleeData|null;
+    extract (blockScopeBody: ESTree.Node[], callee: ESTree.Node): ICalleeData|null;
 }
 }

+ 3 - 1
src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts

@@ -1,5 +1,7 @@
+import * as ESTree from 'estree';
+
 import { IStackTraceData } from './IStackTraceData';
 import { IStackTraceData } from './IStackTraceData';
 
 
 export interface IStackTraceAnalyzer {
 export interface IStackTraceAnalyzer {
-    analyze (): IStackTraceData[];
+    analyze (blockScopeBody: ESTree.Node[]): IStackTraceData[];
 }
 }

+ 11 - 0
src/interfaces/storages/IStorage.d.ts

@@ -0,0 +1,11 @@
+import { IInitializable } from '../IInitializable';
+
+export interface IStorage <T> extends IInitializable {
+    get (key: string | number): T;
+    getKeyOf (value: T): string | number | null;
+    getLength (): number;
+    getStorage (): any;
+    initialize (...args: any[]): void;
+    set (key: string | number | null, value: T): void;
+    toString (): string;
+}

+ 0 - 49
src/node-groups/AbstractNodesGroup.ts

@@ -1,49 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { INodesGroup } from '../interfaces/INodesGroup';
-import { IOptions } from '../interfaces/IOptions';
-import { IStackTraceData } from '../interfaces/stack-trace-analyzer/IStackTraceData';
-
-import { AppendState } from '../enums/AppendState';
-
-export abstract class AbstractNodesGroup implements INodesGroup {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @type {IOptions}
-     */
-    protected options: IOptions;
-
-    /**
-     * @param stackTraceData
-     * @param options
-     */
-    constructor (stackTraceData: IStackTraceData[], options: IOptions) {
-        this.stackTraceData = stackTraceData;
-        this.options = options;
-    }
-
-    /**
-     * @returns {Map<string, ICustomNode> | undefined}
-     */
-    public abstract getNodes (): Map <string, ICustomNode> | undefined;
-
-    /**
-     * @param customNodes
-     * @returns {Map<string, ICustomNode>}
-     */
-    protected syncCustomNodesWithNodesGroup (customNodes: Map <string, ICustomNode>): Map <string, ICustomNode> {
-        customNodes.forEach((node: ICustomNode) => {
-            node.setAppendState(this.appendState);
-        });
-
-        return customNodes;
-    }
-}

+ 0 - 43
src/node-groups/ConsoleOutputNodesGroup.ts

@@ -1,43 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-
-import { ConsoleOutputDisableExpressionNode } from '../custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode';
-import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
-
-import { AbstractNodesGroup } from './AbstractNodesGroup';
-import { NodeAppender } from '../node/NodeAppender';
-import { Utils } from '../Utils';
-
-export class ConsoleOutputNodesGroup extends AbstractNodesGroup {
-    /**
-     * @returns {Map<string, ICustomNode>}
-     */
-    public getNodes (): Map <string, ICustomNode> | undefined {
-        if (!this.options.disableConsoleOutput) {
-            return;
-        }
-
-        const callsControllerFunctionName: string = Utils.getRandomVariableName();
-        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(this.stackTraceData.length);
-
-        return this.syncCustomNodesWithNodesGroup(new Map <string, ICustomNode> ([
-            [
-                'consoleOutputDisableExpressionNode',
-                new ConsoleOutputDisableExpressionNode(
-                    this.stackTraceData,
-                    callsControllerFunctionName,
-                    randomStackTraceIndex,
-                    this.options
-                )
-            ],
-            [
-                'ConsoleOutputNodeCallsControllerFunctionNode',
-                new NodeCallsControllerFunctionNode(
-                    this.stackTraceData,
-                    callsControllerFunctionName,
-                    randomStackTraceIndex,
-                    this.options
-                )
-            ]
-        ]));
-    }
-}

+ 0 - 40
src/node-groups/DebugProtectionNodesGroup.ts

@@ -1,40 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-
-import { DebugProtectionFunctionCallNode } from '../custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode';
-import { DebugProtectionFunctionIntervalNode } from '../custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode';
-import { DebugProtectionFunctionNode } from '../custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode';
-
-import { AbstractNodesGroup } from './AbstractNodesGroup';
-import { Utils } from '../Utils';
-
-export class DebugProtectionNodesGroup extends AbstractNodesGroup {
-    /**
-     * @returns {Map<string, ICustomNode> | undefined}
-     */
-    public getNodes (): Map <string, ICustomNode> | undefined {
-        if (!this.options.debugProtection) {
-            return;
-        }
-
-        const debugProtectionFunctionName: string = Utils.getRandomVariableName();
-        const customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ([
-            [
-                'debugProtectionFunctionNode',
-                new DebugProtectionFunctionNode(debugProtectionFunctionName, this.options)
-            ],
-            [
-                'debugProtectionFunctionCallNode',
-                new DebugProtectionFunctionCallNode(debugProtectionFunctionName, this.options)
-            ]
-        ]);
-
-        if (this.options.debugProtectionInterval) {
-            customNodes.set(
-                'debugProtectionFunctionIntervalNode',
-                new DebugProtectionFunctionIntervalNode(debugProtectionFunctionName, this.options)
-            );
-        }
-
-        return this.syncCustomNodesWithNodesGroup(customNodes);
-    }
-}

+ 0 - 43
src/node-groups/DomainLockNodesGroup.ts

@@ -1,43 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-
-import { DomainLockNode } from '../custom-nodes/domain-lock-nodes/DomainLockNode';
-import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
-
-import { AbstractNodesGroup } from './AbstractNodesGroup';
-import { NodeAppender } from '../node/NodeAppender';
-import { Utils } from '../Utils';
-
-export class DomainLockNodesGroup extends AbstractNodesGroup {
-    /**
-     * @returns {Map<string, ICustomNode> | undefined}
-     */
-    public getNodes (): Map <string, ICustomNode> | undefined {
-        if (!this.options.domainLock.length) {
-            return;
-        }
-
-        const callsControllerFunctionName: string = Utils.getRandomVariableName();
-        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(this.stackTraceData.length);
-
-        return this.syncCustomNodesWithNodesGroup(new Map <string, ICustomNode> ([
-            [
-                'DomainLockNode',
-                new DomainLockNode(
-                    this.stackTraceData,
-                    callsControllerFunctionName,
-                    randomStackTraceIndex,
-                    this.options
-                )
-            ],
-            [
-                'DomainLockNodeCallsControllerFunctionNode',
-                new NodeCallsControllerFunctionNode(
-                    this.stackTraceData,
-                    callsControllerFunctionName,
-                    randomStackTraceIndex,
-                    this.options
-                )
-            ]
-        ]));
-    }
-}

+ 0 - 50
src/node-groups/SelfDefendingNodesGroup.ts

@@ -1,50 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-
-import { AppendState } from '../enums/AppendState';
-
-import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
-import { SelfDefendingUnicodeNode } from '../custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode';
-
-import { AbstractNodesGroup } from './AbstractNodesGroup';
-import { NodeAppender } from '../node/NodeAppender';
-import { Utils } from '../Utils';
-
-export class SelfDefendingNodesGroup extends AbstractNodesGroup {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @returns {Map<string, ICustomNode> | undefined}
-     */
-    public getNodes (): Map <string, ICustomNode> | undefined {
-        if (!this.options.selfDefending) {
-            return;
-        }
-
-        const callsControllerFunctionName: string = Utils.getRandomVariableName();
-        const randomStackTraceIndex: number = NodeAppender.getRandomStackTraceIndex(this.stackTraceData.length);
-
-        return this.syncCustomNodesWithNodesGroup(new Map <string, ICustomNode> ([
-            [
-                'selfDefendingUnicodeNode',
-                new SelfDefendingUnicodeNode(
-                    this.stackTraceData,
-                    callsControllerFunctionName,
-                    randomStackTraceIndex,
-                    this.options
-                )
-            ],
-            [
-                'SelfDefendingNodeCallsControllerFunctionNode',
-                new NodeCallsControllerFunctionNode(
-                    this.stackTraceData,
-                    callsControllerFunctionName,
-                    randomStackTraceIndex,
-                    this.options
-                )
-            ]
-        ]));
-    }
-}

+ 0 - 84
src/node-groups/StringArrayNodesGroup.ts

@@ -1,84 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-
-import { AppendState } from '../enums/AppendState';
-
-import { StringArrayCallsWrapper } from '../custom-nodes/string-array-nodes/StringArrayCallsWrapper';
-import { StringArrayNode } from '../custom-nodes/string-array-nodes/StringArrayNode';
-import { StringArrayRotateFunctionNode } from '../custom-nodes/string-array-nodes/StringArrayRotateFunctionNode';
-
-import { AbstractNodesGroup } from './AbstractNodesGroup';
-import { StringArray } from '../StringArray';
-import { Utils } from '../Utils';
-
-export class StringArrayNodesGroup extends AbstractNodesGroup {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @type {string}
-     */
-    private stringArrayName: string = Utils.getRandomVariableName(StringArrayNode.ARRAY_RANDOM_LENGTH);
-
-    /**
-     * @type {string}
-     */
-    private stringArrayCallsWrapper: string = Utils.getRandomVariableName(StringArrayNode.ARRAY_RANDOM_LENGTH);
-
-    /**
-     * @type {number}
-     */
-    private stringArrayRotateValue: number;
-
-    /**
-     * @returns {Map<string, ICustomNode> | undefined}
-     */
-    public getNodes (): Map <string, ICustomNode> | undefined {
-        if (!this.options.stringArray) {
-            return;
-        }
-
-        if (this.options.rotateStringArray) {
-            this.stringArrayRotateValue = Utils.getRandomInteger(100, 500);
-        } else {
-            this.stringArrayRotateValue = 0;
-        }
-
-        const stringArray: StringArray = new StringArray();
-        const stringArrayNode: ICustomNode = new StringArrayNode(
-            stringArray,
-            this.stringArrayName,
-            this.stringArrayRotateValue,
-            this.options
-        );
-        const customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ([
-            [
-                'stringArrayNode', stringArrayNode,
-            ],
-            [
-                'stringArrayCallsWrapper',
-                new StringArrayCallsWrapper(
-                    this.stringArrayCallsWrapper,
-                    this.stringArrayName,
-                    stringArray,
-                    this.options
-                )
-            ]
-        ]);
-
-        if (this.options.rotateStringArray) {
-            customNodes.set(
-                'stringArrayRotateFunctionNode',
-                new StringArrayRotateFunctionNode(
-                    this.stringArrayName,
-                    stringArray,
-                    this.stringArrayRotateValue,
-                    this.options
-                )
-            );
-        }
-
-        return this.syncCustomNodesWithNodesGroup(customNodes);
-    }
-}

+ 0 - 32
src/node-obfuscators/AbstractNodeObfuscator.ts

@@ -1,32 +0,0 @@
-import * as ESTree from 'estree';
-
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { INodeObfuscator } from '../interfaces/INodeObfuscator';
-import { IOptions } from '../interfaces/IOptions';
-
-export abstract class AbstractNodeObfuscator implements INodeObfuscator {
-    /**
-     * @type Map <string, AbstractCustomNode>
-     */
-    protected nodes: Map <string, ICustomNode>;
-
-    /**
-     * @type {IOptions}
-     */
-    protected options: IOptions;
-
-    /**
-     * @param nodes
-     * @param options
-     */
-    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
-        this.nodes = nodes;
-        this.options = options;
-    }
-
-    /**
-     * @param node
-     * @param parentNode
-     */
-    public abstract obfuscateNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
-}

+ 0 - 67
src/node-obfuscators/CatchClauseObfuscator.ts

@@ -1,67 +0,0 @@
-import * as estraverse from 'estraverse';
-import * as ESTree from 'estree';
-
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
-
-import { NodeType } from '../enums/NodeType';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
-
-/**
- * replaces:
- *     try {} catch (e) { console.log(e); };
- *
- * on:
- *     try {} catch (_0x12d45f) { console.log(_0x12d45f); };
- *
- */
-export class CatchClauseObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @type {IdentifierReplacer}
-     */
-    private identifierReplacer: IdentifierReplacer;
-
-    /**
-     * @param nodes
-     * @param options
-     */
-    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
-        super(nodes, options);
-
-        this.identifierReplacer = new IdentifierReplacer(this.nodes, this.options);
-    }
-
-    /**
-     * @param catchClauseNode
-     */
-    public obfuscateNode (catchClauseNode: ESTree.CatchClause): void {
-        this.storeCatchClauseParam(catchClauseNode);
-        this.replaceCatchClauseParam(catchClauseNode);
-    }
-
-    /**
-     * @param catchClauseNode
-     */
-    private storeCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
-        NodeUtils.typedReplace(catchClauseNode.param, NodeType.Identifier, {
-            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
-        });
-    }
-
-    /**
-     * @param catchClauseNode
-     */
-    private replaceCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
-        estraverse.replace(catchClauseNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
-                }
-            }
-        });
-    }
-}

+ 0 - 78
src/node-obfuscators/FunctionDeclarationObfuscator.ts

@@ -1,78 +0,0 @@
-import * as estraverse from 'estraverse';
-import * as ESTree from 'estree';
-
-import { TNodeWithBlockStatement } from '../types/TNodeWithBlockStatement';
-
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
-
-import { NodeType } from '../enums/NodeType';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
-
-/**
- * replaces:
- *     function foo () { //... };
- *     foo();
- *
- * on:
- *     function _0x12d45f () { //... };
- *     _0x12d45f();
- */
-export class FunctionDeclarationObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @type {IdentifierReplacer}
-     */
-    private identifierReplacer: IdentifierReplacer;
-
-    /**
-     * @param nodes
-     * @param options
-     */
-    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
-        super(nodes, options);
-
-        this.identifierReplacer = new IdentifierReplacer(this.nodes, this.options);
-    }
-
-    /**
-     * @param functionDeclarationNode
-     * @param parentNode
-     */
-    public obfuscateNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): void {
-        const blockScopeOfFunctionDeclarationNode: TNodeWithBlockStatement = NodeUtils
-            .getBlockScopeOfNode(functionDeclarationNode);
-
-        if (blockScopeOfFunctionDeclarationNode.type === NodeType.Program) {
-            return;
-        }
-
-        this.storeFunctionName(functionDeclarationNode);
-        this.replaceFunctionName(blockScopeOfFunctionDeclarationNode);
-    }
-
-    /**
-     * @param functionDeclarationNode
-     */
-    private storeFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration): void {
-        NodeUtils.typedReplace(functionDeclarationNode.id, NodeType.Identifier, {
-            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
-        });
-    }
-
-    /**
-     * @param scopeNode
-     */
-    private replaceFunctionName (scopeNode: ESTree.Node): void {
-        estraverse.replace(scopeNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
-                }
-            }
-        });
-    }
-}

+ 0 - 82
src/node-obfuscators/FunctionObfuscator.ts

@@ -1,82 +0,0 @@
-import * as estraverse from 'estraverse';
-import * as ESTree from 'estree';
-
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
-
-import { NodeType } from '../enums/NodeType';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
-
-/**
- * replaces:
- *     function foo (argument1) { return argument1; };
- *
- * on:
- *     function foo (_0x12d45f) { return _0x12d45f; };
- *
- */
-export class FunctionObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @type {IdentifierReplacer}
-     */
-    private identifierReplacer: IdentifierReplacer;
-
-    /**
-     * @param nodes
-     * @param options
-     */
-    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
-        super(nodes, options);
-
-        this.identifierReplacer = new IdentifierReplacer(this.nodes, this.options);
-    }
-
-    /**
-     * @param functionNode
-     */
-    public obfuscateNode (functionNode: ESTree.Function): void {
-        this.storeFunctionParams(functionNode);
-        this.replaceFunctionParams(functionNode);
-    }
-
-    /**
-     * @param functionNode
-     */
-    private storeFunctionParams (functionNode: ESTree.Function): void {
-        functionNode.params
-            .forEach((paramsNode: ESTree.Node) => {
-                NodeUtils.typedReplace(paramsNode, NodeType.Identifier, {
-                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
-                });
-            });
-    }
-
-    /**
-     * @param functionNode
-     */
-    private replaceFunctionParams (functionNode: ESTree.Function): void {
-        let replaceVisitor: estraverse.Visitor = {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
-                    const newNodeName: string = this.identifierReplacer.replace(node.name);
-
-                    if (node.name !== newNodeName) {
-                        node.name = newNodeName;
-                        node.obfuscated = true;
-                    }
-                }
-            }
-        };
-
-        functionNode.params
-            .forEach((paramsNode: ESTree.Node) => {
-                estraverse.replace(paramsNode, replaceVisitor);
-            });
-
-        estraverse.replace(functionNode.body, replaceVisitor);
-    }
-}

+ 0 - 75
src/node-obfuscators/LabeledStatementObfuscator.ts

@@ -1,75 +0,0 @@
-import * as estraverse from 'estraverse';
-import * as ESTree from 'estree';
-
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../interfaces/IOptions';
-
-import { NodeType } from '../enums/NodeType';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { IdentifierReplacer } from './replacers/IdentifierReplacer';
-import { Node } from '../node/Node';
-import { NodeUtils } from '../node/NodeUtils';
-
-/**
- * replaces:
- *     label: {
- *          for (var i = 0; i < 1000; i++) {
- *              break label;
- *          }
- *     }
- *
- * on:
- *     _0x12d45f: {
- *          for (var i = 0; i < 1000; i++) {
- *              break _0x12d45f;
- *          }
- *     }
- *
- */
-export class LabeledStatementObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @type {IdentifierReplacer}
-     */
-    private identifierReplacer: IdentifierReplacer;
-
-    /**
-     * @param nodes
-     * @param options
-     */
-    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
-        super(nodes, options);
-
-        this.identifierReplacer = new IdentifierReplacer(this.nodes, this.options);
-    }
-
-    /**
-     * @param labeledStatementNode
-     */
-    public obfuscateNode (labeledStatementNode: ESTree.LabeledStatement): void {
-        this.storeLabeledStatementName(labeledStatementNode);
-        this.replaceLabeledStatementName(labeledStatementNode);
-    }
-
-    /**
-     * @param labeledStatementNode
-     */
-    private storeLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement): void {
-        NodeUtils.typedReplace(labeledStatementNode.label, NodeType.Identifier, {
-            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
-        });
-    }
-
-    /**
-     * @param labeledStatementNode
-     */
-    private replaceLabeledStatementName (labeledStatementNode: ESTree.LabeledStatement): void {
-        estraverse.replace(labeledStatementNode, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (Node.isLabelIdentifierNode(node, parentNode)) {
-                    node.name = this.identifierReplacer.replace(node.name);
-                }
-            }
-        });
-    }
-}

+ 0 - 50
src/node-obfuscators/LiteralObfuscator.ts

@@ -1,50 +0,0 @@
-import * as escodegen from 'escodegen';
-import * as ESTree from 'estree';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { BooleanLiteralReplacer } from './replacers/BooleanLiteralReplacer';
-import { Node } from '../node/Node';
-import { NumberLiteralReplacer } from './replacers/NumberLiteralReplacer';
-import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
-
-export class LiteralObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @param literalNode
-     * @param parentNode
-     */
-    public obfuscateNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
-        if (Node.isPropertyNode(parentNode) && parentNode.key === literalNode) {
-            return;
-        }
-
-        let content: string;
-
-        switch (typeof literalNode.value) {
-            case 'boolean':
-                content = new BooleanLiteralReplacer(this.nodes, this.options)
-                    .replace(<boolean>literalNode.value);
-
-                break;
-
-            case 'number':
-                content = new NumberLiteralReplacer(this.nodes, this.options)
-                    .replace(<number>literalNode.value);
-
-                break;
-
-            case 'string':
-                content = new StringLiteralReplacer(this.nodes, this.options)
-                        .replace(<string>literalNode.value);
-
-                break;
-
-            default:
-                return;
-        }
-
-        literalNode['x-verbatim-property'] = {
-            content : content,
-            precedence: escodegen.Precedence.Primary
-        };
-    }
-}

+ 0 - 82
src/node-obfuscators/MemberExpressionObfuscator.ts

@@ -1,82 +0,0 @@
-import * as escodegen from 'escodegen';
-import * as estraverse from 'estraverse';
-import * as ESTree from 'estree';
-
-import { NodeType } from '../enums/NodeType';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { Node } from '../node/Node';
-import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
-
-export class MemberExpressionObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @param memberExpressionNode
-     */
-    public obfuscateNode (memberExpressionNode: ESTree.MemberExpression): void {
-        estraverse.replace(memberExpressionNode.property, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (Node.isLiteralNode(node)) {
-                    this.obfuscateLiteralProperty(node);
-
-                    return;
-                }
-
-                if (Node.isIdentifierNode(node)) {
-                    if (memberExpressionNode.computed) {
-                        return;
-                    }
-
-                    memberExpressionNode.computed = true;
-                    this.obfuscateIdentifierProperty(node);
-                }
-            }
-        });
-    }
-
-    /**
-     * replaces:
-     *     object.identifier = 1;
-     *
-     * on:
-     *     object[_0x23d45[25]] = 1;
-     *
-     * and skip:
-     *     object[identifier] = 1;
-     *
-     * @param node
-     */
-    private obfuscateIdentifierProperty (node: ESTree.Identifier): void {
-        let nodeValue: string = node.name,
-            literalNode: ESTree.Literal = {
-                raw: `'${nodeValue}'`,
-                'x-verbatim-property': {
-                    content : new StringLiteralReplacer(this.nodes, this.options).replace(nodeValue),
-                    precedence: escodegen.Precedence.Primary
-                },
-                type: NodeType.Literal,
-                value: nodeValue
-            };
-
-        delete node.name;
-
-        Object.assign(node, literalNode);
-    }
-
-    /**
-     * replaces:
-     *     object['literal'] = 1;
-     *
-     * on:
-     *     object[_0x23d45[25]] = 1;
-     *
-     * @param node
-     */
-    private obfuscateLiteralProperty (node: ESTree.Literal): void {
-        if (typeof node.value === 'string' && !node['x-verbatim-property']) {
-            node['x-verbatim-property'] = {
-                content : new StringLiteralReplacer(this.nodes, this.options).replace(node.value),
-                precedence: escodegen.Precedence.Primary
-            };
-        }
-    }
-}

+ 0 - 52
src/node-obfuscators/MethodDefinitionObfuscator.ts

@@ -1,52 +0,0 @@
-import * as estraverse from 'estraverse';
-import * as ESTree from 'estree';
-
-import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
-import { Node } from '../node/Node';
-import { Utils } from '../Utils';
-import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
-
-/**
- * replaces:
- *     foo () { //... };
- *
- * on:
- *     [_0x9a4e('0x0')] { //... };
- */
-export class MethodDefinitionObfuscator extends AbstractNodeObfuscator {
-    /**
-     * @type {string[]}
-     */
-    private static ignoredNames: string[] = ['constructor'];
-
-    /**
-     * @param methodDefinitionNode
-     * @param parentNode
-     */
-    public obfuscateNode (methodDefinitionNode: ESTree.MethodDefinition, parentNode: ESTree.Node): void {
-        this.replaceMethodName(methodDefinitionNode);
-    }
-
-    /**
-     * @param methodDefinitionNode
-     */
-    private replaceMethodName (methodDefinitionNode: ESTree.MethodDefinition): void {
-        estraverse.replace(methodDefinitionNode.key, {
-            enter: (node: ESTree.Node): any => {
-                if (
-                    Node.isIdentifierNode(node) &&
-                    !Utils.arrayContains(MethodDefinitionObfuscator.ignoredNames, node.name) &&
-                    methodDefinitionNode.computed === false
-                ) {
-                    methodDefinitionNode.computed = true;
-                    node.name = new StringLiteralReplacer(this.nodes, this.options)
-                        .replace(node.name);
-
-                    return;
-                }
-
-                return estraverse.VisitorOption.Skip;
-            }
-        });
-    }
-}

+ 0 - 31
src/node-obfuscators/replacers/AbstractReplacer.ts

@@ -1,31 +0,0 @@
-import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
-import { IOptions } from '../../interfaces/IOptions';
-import { IReplacer } from '../../interfaces/IReplacer';
-
-export abstract class AbstractReplacer implements IReplacer {
-    /**
-     * @type Map <string, AbstractCustomNode>
-     */
-    protected nodes: Map <string, ICustomNode>;
-
-    /**
-     * @type {IOptions}
-     */
-    protected options : IOptions;
-
-    /**
-     * @param nodes
-     * @param options
-     */
-    constructor (nodes: Map <string, ICustomNode>, options: IOptions) {
-        this.nodes = nodes;
-        this.options = options;
-    }
-
-    /**
-     * @param nodeValue
-     * @param namesMap
-     * @returns {string}
-     */
-    public abstract replace (nodeValue: any, namesMap?: Map <string, string>): string;
-}

+ 0 - 13
src/node-obfuscators/replacers/BooleanLiteralReplacer.ts

@@ -1,13 +0,0 @@
-import { JSFuck } from '../../enums/JSFuck';
-
-import { AbstractReplacer } from './AbstractReplacer';
-
-export class BooleanLiteralReplacer extends AbstractReplacer {
-    /**
-     * @param nodeValue
-     * @returns {string}
-     */
-    public replace (nodeValue: boolean): string {
-        return nodeValue ? JSFuck.True : JSFuck.False;
-    }
-}

+ 0 - 18
src/node-obfuscators/replacers/NumberLiteralReplacer.ts

@@ -1,18 +0,0 @@
-import { AbstractReplacer } from './AbstractReplacer';
-import { Utils } from '../../Utils';
-
-export class NumberLiteralReplacer extends AbstractReplacer {
-    /**
-     * @param nodeValue
-     * @returns {string}
-     */
-    public replace (nodeValue: number): string {
-        const prefix: string = '0x';
-
-        if (!Utils.isInteger(nodeValue)) {
-            return String(nodeValue);
-        }
-
-        return `${prefix}${Utils.decToHex(nodeValue)}`;
-    }
-}

+ 0 - 97
src/node-obfuscators/replacers/StringLiteralReplacer.ts

@@ -1,97 +0,0 @@
-import { TStringArrayCallsWrapper } from '../../types/custom-nodes/TStringArrayCallsWrapper';
-import { TStringArrayNode } from '../../types/custom-nodes/TStringArrayNode';
-
-import { StringArrayEncoding } from '../../enums/StringArrayEncoding';
-
-import { AbstractReplacer } from './AbstractReplacer';
-import { NumberLiteralReplacer } from './NumberLiteralReplacer';
-import { StringArray } from '../../StringArray';
-import { Utils } from '../../Utils';
-
-export class StringLiteralReplacer extends AbstractReplacer {
-    /**
-     * @type {number}
-     */
-    private static minimumLengthForStringArray: number = 3;
-
-    /**
-     * @type {string[]}
-     */
-    private static rc4Keys: string[] = Utils.getRandomGenerator()
-        .n(() => Utils.getRandomGenerator().string({length: 4}), 50);
-
-    /**
-     * @param nodeValue
-     * @returns {string}
-     */
-    public replace (nodeValue: string): string {
-        const replaceWithStringArrayFlag: boolean = (
-            nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
-        );
-
-        if (this.options.stringArray && replaceWithStringArrayFlag) {
-            return this.replaceStringLiteralWithStringArrayCall(nodeValue);
-        }
-
-        return `'${Utils.stringToUnicodeEscapeSequence(nodeValue)}'`;
-    }
-
-    /**
-     * @param value
-     * @returns {string}
-     */
-    private replaceStringLiteralWithStringArrayCall (value: string): string {
-        const stringArrayNode: TStringArrayNode = <TStringArrayNode>this.nodes.get('stringArrayNode');
-
-        if (!stringArrayNode) {
-            throw new ReferenceError('`stringArrayNode` node is not found in Map with custom node.');
-        }
-
-        let rc4Key: string = '';
-
-        switch (this.options.stringArrayEncoding) {
-            case StringArrayEncoding.base64:
-                value = Utils.btoa(value);
-
-                break;
-
-            case StringArrayEncoding.rc4:
-                rc4Key = Utils.getRandomGenerator().pickone(StringLiteralReplacer.rc4Keys);
-                value = Utils.btoa(Utils.rc4(value, rc4Key));
-
-                break;
-        }
-
-        if (this.options.unicodeEscapeSequence) {
-            value = Utils.stringToUnicodeEscapeSequence(value);
-        }
-
-        let stringArray: StringArray = stringArrayNode.getNodeData(),
-            indexOfExistingValue: number = stringArray.getIndexOf(value),
-            indexOfValue: number,
-            hexadecimalIndex: string;
-
-        if (indexOfExistingValue >= 0) {
-            indexOfValue = indexOfExistingValue;
-        } else {
-            indexOfValue = stringArray.getLength();
-            stringArrayNode.updateNodeData(value);
-        }
-
-        hexadecimalIndex = new NumberLiteralReplacer(this.nodes, this.options)
-            .replace(indexOfValue);
-
-        const stringArrayCallsWrapper: TStringArrayCallsWrapper = <TStringArrayCallsWrapper>this.nodes.get('stringArrayCallsWrapper');
-
-        if (!stringArrayCallsWrapper) {
-            throw new ReferenceError('`stringArrayCallsWrapper` node is not found in Map with custom node.');
-        }
-
-        if (this.options.stringArrayEncoding === StringArrayEncoding.rc4) {
-            return `${stringArrayCallsWrapper.getNodeIdentifier()}('${hexadecimalIndex}', '${Utils.stringToUnicodeEscapeSequence(rc4Key)}')`;
-        }
-
-        return `${stringArrayCallsWrapper.getNodeIdentifier()}('${hexadecimalIndex}')`;
-    }
-}

+ 30 - 0
src/node-transformers/AbstractNodeTransformer.ts

@@ -0,0 +1,30 @@
+import { injectable, inject } from 'inversify';
+
+import * as ESTree from 'estree';
+
+import { INodeTransformer } from '../interfaces/node-transformers/INodeTransformer';
+import { IOptions } from '../interfaces/options/IOptions';
+import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
+
+@injectable()
+export abstract class AbstractNodeTransformer implements INodeTransformer {
+    /**
+     * @type {IOptions}
+     */
+    protected readonly options: IOptions;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.options = options;
+    }
+
+    /**
+     * @param node
+     * @param parentNode
+     */
+    public abstract transformNode (node: ESTree.Node, parentNode?: ESTree.Node): void;
+}

+ 126 - 0
src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -0,0 +1,126 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TControlFlowReplacerFactory } from '../../types/container/TControlFlowReplacerFactory';
+import { TControlFlowStorageFactory } from '../../types/container/TControlFlowStorageFactory';
+import { TCustomNodeFactory } from '../../types/container/TCustomNodeFactory';
+import { TStatement } from '../../types/node/TStatement';
+
+import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IStorage } from '../../interfaces/storages/IStorage';
+
+import { CustomNodes } from '../../enums/container/CustomNodes';
+import { NodeType } from '../../enums/NodeType';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { Node } from '../../node/Node';
+import { NodeAppender } from '../../node/NodeAppender';
+import { NodeControlFlowReplacers } from '../../enums/container/NodeControlFlowReplacers';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+
+@injectable()
+export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {Map <string, NodeControlFlowReplacers>}
+     */
+    private static readonly controlFlowReplacersMap: Map <string, NodeControlFlowReplacers> = new Map <string, NodeControlFlowReplacers> ([
+        [NodeType.BinaryExpression, NodeControlFlowReplacers.BinaryExpressionControlFlowReplacer]
+    ]);
+
+    /**
+     * @type {TControlFlowReplacerFactory}
+     */
+    private readonly controlFlowReplacerFactory: TControlFlowReplacerFactory;
+
+    /**
+     * @type {TControlFlowStorageFactory}
+     */
+    private readonly controlFlowStorageFactory: TControlFlowStorageFactory;
+
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @param controlFlowStorageFactory
+     * @param controlFlowReplacerFactory
+     * @param customNodeFactory
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<IStorage<ICustomNode>>']) controlFlowStorageFactory: TControlFlowStorageFactory,
+        @inject(ServiceIdentifiers['Factory<IControlFlowReplacer>']) controlFlowReplacerFactory: TControlFlowReplacerFactory,
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.controlFlowStorageFactory = controlFlowStorageFactory;
+        this.controlFlowReplacerFactory = controlFlowReplacerFactory;
+        this.customNodeFactory = customNodeFactory;
+    }
+
+    /**
+     * @param functionNode
+     */
+    public transformNode (functionNode: ESTree.Function): void {
+        this.changeFunctionBodyControlFlow(functionNode);
+    }
+
+    /**
+     * @param functionNode
+     */
+    private changeFunctionBodyControlFlow (functionNode: ESTree.Function): void {
+        if (Node.isArrowFunctionExpressionNode(functionNode)) {
+            return;
+        }
+
+        const controlFlowStorage: IStorage <ICustomNode> = this.controlFlowStorageFactory();
+        const controlFlowStorageCustomNodeName: string = RandomGeneratorUtils.getRandomVariableName(6);
+
+        estraverse.replace(functionNode.body, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                const controlFlowReplacerName: NodeControlFlowReplacers | undefined = FunctionControlFlowTransformer
+                    .controlFlowReplacersMap.get(node.type);
+
+                if (controlFlowReplacerName === undefined) {
+                    return;
+                }
+
+                const controlFlowStorageCallCustomNode: ICustomNode | undefined = this.controlFlowReplacerFactory(controlFlowReplacerName)
+                    .replace(node, parentNode, controlFlowStorage, controlFlowStorageCustomNodeName);
+
+                if (!controlFlowStorageCallCustomNode) {
+                    return;
+                }
+
+                // controlFlowStorageCallCustomNode will always have only one TStatement node,
+                // so we can get it by index `0`
+                // also we need to return `expression` property of `ExpressionStatement` node because bug:
+                // https://github.com/estools/escodegen/issues/289
+                const statementNode: TStatement = controlFlowStorageCallCustomNode.getNode()[0];
+
+                if (!statementNode || !Node.isExpressionStatementNode(statementNode)) {
+                    throw new Error(`\`controlFlowStorageCallCustomNode.getNode()\` should returns array with \`ExpressionStatement\` node`);
+                }
+
+                return statementNode.expression;
+            }
+        });
+
+        if (!controlFlowStorage.getLength()) {
+            return;
+        }
+
+        const controlFlowStorageCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageNode);
+
+        controlFlowStorageCustomNode.initialize(controlFlowStorage, controlFlowStorageCustomNodeName);
+
+        NodeAppender.prependNode(functionNode.body, controlFlowStorageCustomNode.getNode());
+    }
+}

+ 52 - 0
src/node-transformers/node-control-flow-transformers/control-flow-replacers/AbstractControlFlowReplacer.ts

@@ -0,0 +1,52 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as ESTree from 'estree';
+
+import { IControlFlowReplacer } from '../../../interfaces/node-transformers/IControlFlowReplacer';
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStorage } from '../../../interfaces/storages/IStorage';
+
+import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils';
+
+@injectable()
+export abstract class AbstractControlFlowReplacer implements IControlFlowReplacer {
+    /**
+     * @type {IOptions}
+     */
+    protected readonly options : IOptions;
+
+    /**
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        this.options = options;
+    }
+
+    /**
+     * @returns {string}
+     */
+    protected static getStorageKey (): string {
+        return RandomGeneratorUtils.getRandomGenerator().string({
+            length: 3,
+            pool: RandomGeneratorUtils.randomGeneratorPool
+        });
+    }
+
+    /**
+     * @param node
+     * @param parentNode
+     * @param controlFlowStorage
+     * @param controlFlowStorageCustomNodeName
+     * @returns {ICustomNode | undefined}
+     */
+    public abstract replace (
+        node: ESTree.Node,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageCustomNodeName: string
+    ): ICustomNode | undefined;
+}

+ 76 - 0
src/node-transformers/node-control-flow-transformers/control-flow-replacers/BinaryExpressionControlFlowReplacer.ts

@@ -0,0 +1,76 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers';
+
+import * as escodegen from 'escodegen';
+import * as ESTree from 'estree';
+
+import { TCustomNodeFactory } from '../../../types/container/TCustomNodeFactory';
+
+import { ICustomNode } from '../../../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../../../interfaces/options/IOptions';
+import { IStorage } from '../../../interfaces/storages/IStorage';
+
+import { CustomNodes } from '../../../enums/container/CustomNodes';
+
+import { AbstractControlFlowReplacer } from './AbstractControlFlowReplacer';
+
+@injectable()
+export class BinaryExpressionControlFlowReplacer extends AbstractControlFlowReplacer {
+    /**
+     * @type {TCustomNodeFactory}
+     */
+    private readonly customNodeFactory: TCustomNodeFactory;
+
+    /**
+     * @param customNodeFactory
+     * @param options
+     */
+    constructor (
+        @inject(ServiceIdentifiers['Factory<ICustomNode>']) customNodeFactory: TCustomNodeFactory,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.customNodeFactory = customNodeFactory;
+    }
+
+    /**
+     * @param expressionNode
+     * @returns {string}
+     */
+    private static getExpressionValue (expressionNode: ESTree.Expression): string {
+        return escodegen.generate(expressionNode, {
+            sourceMapWithCode: true
+        }).code;
+    }
+
+    /**
+     * @param binaryExpressionNode
+     * @param parentNode
+     * @param controlFlowStorage
+     * @param controlFlowStorageCustomNodeName
+     * @returns {ICustomNode}
+     */
+    public replace (
+        binaryExpressionNode: ESTree.BinaryExpression,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage <ICustomNode>,
+        controlFlowStorageCustomNodeName: string
+    ): ICustomNode {
+        const key: string = AbstractControlFlowReplacer.getStorageKey();
+        const binaryExpressionFunctionNode: ICustomNode = this.customNodeFactory(CustomNodes.BinaryExpressionFunctionNode);
+        const controlFlowStorageCallNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageCallNode);
+
+        binaryExpressionFunctionNode.initialize(binaryExpressionNode.operator);
+        controlFlowStorageCallNode.initialize(
+            controlFlowStorageCustomNodeName,
+            key,
+            BinaryExpressionControlFlowReplacer.getExpressionValue(binaryExpressionNode.left),
+            BinaryExpressionControlFlowReplacer.getExpressionValue(binaryExpressionNode.right)
+        );
+
+        controlFlowStorage.set(key, binaryExpressionFunctionNode);
+
+        return controlFlowStorageCallNode;
+    }
+}

+ 81 - 0
src/node-transformers/node-obfuscators/CatchClauseObfuscator.ts

@@ -0,0 +1,81 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+
+import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
+import { NodeType } from '../../enums/NodeType';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+
+/**
+ * replaces:
+ *     try {} catch (e) { console.log(e); };
+ *
+ * on:
+ *     try {} catch (_0x12d45f) { console.log(_0x12d45f); };
+ *
+ */
+@injectable()
+export class CatchClauseObfuscator extends AbstractNodeTransformer {
+    /**
+     * @type {IdentifierReplacer}
+     */
+    private readonly identifierReplacer: IReplacer & IdentifierReplacer;
+
+    /**
+     * @param replacersFactory
+     * @param options
+     */
+    constructor(
+        @inject(ServiceIdentifiers['Factory<IReplacer>']) replacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.identifierReplacer = <IdentifierReplacer>replacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+    }
+
+    /**
+     * @param catchClauseNode
+     */
+    public transformNode (catchClauseNode: ESTree.CatchClause): void {
+        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
+            length: 5,
+            pool: RandomGeneratorUtils.randomGeneratorPool
+        }));
+
+        this.storeCatchClauseParam(catchClauseNode);
+        this.replaceCatchClauseParam(catchClauseNode);
+    }
+
+    /**
+     * @param catchClauseNode
+     */
+    private storeCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
+        NodeUtils.typedTraverse(catchClauseNode.param, NodeType.Identifier, {
+            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+        });
+    }
+
+    /**
+     * @param catchClauseNode
+     */
+    private replaceCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
+        estraverse.replace(catchClauseNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
+                    node.name = this.identifierReplacer.replace(node.name);
+                }
+            }
+        });
+    }
+}

+ 92 - 0
src/node-transformers/node-obfuscators/FunctionDeclarationObfuscator.ts

@@ -0,0 +1,92 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+
+import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
+import { NodeType } from '../../enums/NodeType';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+
+/**
+ * replaces:
+ *     function foo () { //... };
+ *     foo();
+ *
+ * on:
+ *     function _0x12d45f () { //... };
+ *     _0x12d45f();
+ */
+@injectable()
+export class FunctionDeclarationObfuscator extends AbstractNodeTransformer {
+    /**
+     * @type {IdentifierReplacer}
+     */
+    private readonly identifierReplacer: IdentifierReplacer;
+
+    /**
+     * @param nodeObfuscatorsReplacersFactory
+     * @param options
+     */
+    constructor(
+        @inject(ServiceIdentifiers['Factory<IReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.identifierReplacer = <IdentifierReplacer>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+    }
+
+    /**
+     * @param functionDeclarationNode
+     * @param parentNode
+     */
+    public transformNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): void {
+        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
+            length: 5,
+            pool: RandomGeneratorUtils.randomGeneratorPool
+        }));
+
+        const blockScopeOfFunctionDeclarationNode: TNodeWithBlockStatement = NodeUtils
+            .getBlockScopeOfNode(functionDeclarationNode);
+
+        if (blockScopeOfFunctionDeclarationNode.type === NodeType.Program) {
+            return;
+        }
+
+        this.storeFunctionName(functionDeclarationNode);
+        this.replaceFunctionName(blockScopeOfFunctionDeclarationNode);
+    }
+
+    /**
+     * @param functionDeclarationNode
+     */
+    private storeFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration): void {
+        NodeUtils.typedTraverse(functionDeclarationNode.id, NodeType.Identifier, {
+            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+        });
+    }
+
+    /**
+     * @param scopeNode
+     */
+    private replaceFunctionName (scopeNode: ESTree.Node): void {
+        estraverse.replace(scopeNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
+                    node.name = this.identifierReplacer.replace(node.name);
+                }
+            }
+        });
+    }
+}

+ 93 - 0
src/node-transformers/node-obfuscators/FunctionObfuscator.ts

@@ -0,0 +1,93 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IReplacer } from '../../interfaces/node-transformers/IReplacer';
+
+import { NodeObfuscatorsReplacers } from '../../enums/container/NodeObfuscatorsReplacers';
+import { NodeType } from '../../enums/NodeType';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+
+/**
+ * replaces:
+ *     function foo (argument1) { return argument1; };
+ *
+ * on:
+ *     function foo (_0x12d45f) { return _0x12d45f; };
+ *
+ */
+@injectable()
+export class FunctionObfuscator extends AbstractNodeTransformer {
+    /**
+     * @type {IdentifierReplacer}
+     */
+    private readonly identifierReplacer: IdentifierReplacer;
+
+    /**
+     * @param nodeObfuscatorsReplacersFactory
+     * @param options
+     */
+    constructor(
+        @inject(ServiceIdentifiers['Factory<IReplacer>']) nodeObfuscatorsReplacersFactory: (replacer: NodeObfuscatorsReplacers) => IReplacer,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(options);
+
+        this.identifierReplacer = <IdentifierReplacer>nodeObfuscatorsReplacersFactory(NodeObfuscatorsReplacers.IdentifierReplacer);
+    }
+
+    /**
+     * @param functionNode
+     */
+    public transformNode (functionNode: ESTree.Function): void {
+        this.identifierReplacer.setPrefix(RandomGeneratorUtils.getRandomGenerator().string({
+            length: 5,
+            pool: RandomGeneratorUtils.randomGeneratorPool
+        }));
+
+        this.storeFunctionParams(functionNode);
+        this.replaceFunctionParams(functionNode);
+    }
+
+    /**
+     * @param functionNode
+     */
+    private storeFunctionParams (functionNode: ESTree.Function): void {
+        functionNode.params
+            .forEach((paramsNode: ESTree.Node) => {
+                NodeUtils.typedTraverse(paramsNode, NodeType.Identifier, {
+                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
+                });
+            });
+    }
+
+    /**
+     * @param functionNode
+     */
+    private replaceFunctionParams (functionNode: ESTree.Function): void {
+        let traverseVisitor: estraverse.Visitor = {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
+                    const newNodeName: string = this.identifierReplacer.replace(node.name);
+
+                    if (node.name !== newNodeName) {
+                        node.name = newNodeName;
+                        node.obfuscated = true;
+                    }
+                }
+            }
+        };
+
+        functionNode.params.forEach((paramsNode: ESTree.Node) => estraverse.replace(paramsNode, traverseVisitor));
+
+        estraverse.replace(functionNode.body, traverseVisitor);
+    }
+}

Some files were not shown because too many files changed in this diff