فهرست منبع

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 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;
 * compatible with ES6;
@@ -118,6 +118,7 @@ Following options available for the JS Obfuscator:
 ```javascript
 {
     compact: true,
+    controlFlowFlattening: false,
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
@@ -144,6 +145,7 @@ Following options available for the JS Obfuscator:
     -o, --output
 
     --compact <boolean>
+    --controlFlowFlattening <boolean>
     --debugProtection <boolean>
     --debugProtectionInterval <boolean>
     --disableConsoleOutput <boolean>
@@ -166,6 +168,48 @@ Type: `boolean` Default: `true`
 
 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`
 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.
 
 Example:
-```javascript
+```ts
 	{
 		reservedNames: [
 			'^someVariable',

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 458 - 415
dist/index.js


+ 0 - 4
index.ts

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

+ 18 - 15
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.8.3",
+  "version": "0.9.0-dev.1",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -20,44 +20,47 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
   },
   "dependencies": {
-    "babel-polyfill": "6.16.0",
+    "babel-polyfill": "6.20.0",
     "chance": "1.0.4",
     "class-validator": "0.6.6",
     "commander": "2.9.0",
     "escodegen": "1.8.1",
-    "esprima": "3.1.1",
+    "esprima": "3.1.2",
     "estraverse": "4.2.0",
-    "format-unicorn": "1.1.0",
+    "inversify": "^3.0.0-rc.1",
+    "is-equal": "^1.5.3",
     "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": {
     "@types/chai": "3.4.34",
     "@types/chance": "0.7.31",
-    "@types/commander": "2.3.30",
+    "@types/commander": "2.3.31",
     "@types/escodegen": "0.0.6",
     "@types/esprima": "2.1.33",
     "@types/estraverse": "0.0.6",
     "@types/estree": "0.0.34",
-    "@types/format-unicorn": "0.0.29",
     "@types/joi": "9.0.33",
     "@types/mkdirp": "0.3.29",
     "@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-loader": "6.2.7",
+    "babel-loader": "6.2.9",
     "babel-preset-es2015": "6.18.0",
     "chai": "3.5.0",
     "coveralls": "2.11.15",
     "istanbul": "1.1.0-alpha.1",
-    "mocha": "3.1.2",
+    "mocha": "3.2.0",
     "sinon": "2.0.0-pre.3",
     "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"
   },
   "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 { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 
+import { InversifyContainerFacade } from './container/InversifyContainerFacade';
 import { JavaScriptObfuscatorCLI } from './cli/JavaScriptObfuscatorCLI';
-import { JavaScriptObfuscatorInternal } from './JavaScriptObfuscatorInternal';
+import { ServiceIdentifiers } from './container/ServiceIdentifiers';
 
 export class JavaScriptObfuscator {
     /**
      * @param sourceCode
-     * @param obfuscatorOptions
+     * @param inputOptions
      * @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 escodegen from 'escodegen';
 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 { 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}
      */
-    private static escodegenParams: escodegen.GenerateOptions = {
+    private static readonly escodegenParams: escodegen.GenerateOptions = {
         verbatim: 'x-verbatim-property',
         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}
      */
-    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 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.sourceContent = sourceCode;
         }
 
         escodegenParams.format = {
-            compact: options.compact
+            compact: this.options.compact
         };
 
         const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
@@ -76,30 +87,34 @@ export class JavaScriptObfuscatorInternal {
     }
 
     /**
+     * @param generatorOutput
      * @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) {
-            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 { initializable } from './decorators/Initializable';
+
+@injectable()
 export class ObfuscationResult implements IObfuscationResult {
     /**
      * @type {string}
      */
+    @initializable()
     private obfuscatedCode: string;
 
     /**
      * @type {string}
      */
+    @initializable()
     private sourceMap: string;
 
     /**
      * @param obfuscatedCode
      * @param sourceMap
      */
-    constructor (obfuscatedCode: string, sourceMap: string) {
+    public initialize (obfuscatedCode: string, sourceMap: string): void {
         this.obfuscatedCode = obfuscatedCode;
         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 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 { 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 { IStorage } from './interfaces/storages/IStorage';
 
-import { AppendState } from './enums/AppendState';
+import { NodeTransformers } from './enums/container/NodeTransformers';
 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 { 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 {
     /**
-     * @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, [
-            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
+     * @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 { IOptions } from './interfaces/options/IOptions';
 import { ISourceMapCorrector } from './interfaces/ISourceMapCorrector';
 
-import { TSourceMapMode } from './types/TSourceMapMode';
-
 import { SourceMapMode } from './enums/SourceMapMode';
 
-import { ObfuscationResult } from './ObfuscationResult';
-import { Utils } from './Utils';
+import { CryptUtils } from './utils/CryptUtils';
 
+@injectable()
 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 (
-        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}
+     * @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}
      */
-    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=';
 
-        switch (this.sourceMapMode) {
+        switch (this.options.sourceMapMode) {
             case SourceMapMode.Inline:
-                sourceMappingUrl += `data:application/json;base64,${Utils.btoa(this.sourceMap)}`;
+                sourceMappingUrl += `data:application/json;base64,${CryptUtils.btoa(sourceMap)}`;
 
                 break;
 
             case SourceMapMode.Separate:
             default:
-                if (!this.sourceMapUrl) {
-                    return this.obfuscatedCode;
+                if (!sourceMapUrl) {
+                    return obfuscatedCode;
                 }
 
-                sourceMappingUrl += this.sourceMapUrl;
+                sourceMappingUrl += sourceMapUrl;
 
                 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 { Utils } from '../Utils';
+import { Utils } from '../utils/Utils';
 
 export class CLIUtils {
     /**
      * @type {string[]}
      */
-    private static availableInputExtensions: string[] = [
+    private static readonly availableInputExtensions: string[] = [
         '.js'
     ];
 
     /**
      * @type {BufferEncoding}
      */
-    private static encoding: BufferEncoding = 'utf8';
+    private static readonly encoding: BufferEncoding = 'utf8';
 
     /**
      * @param outputPath

+ 106 - 34
src/cli/JavaScriptObfuscatorCLI.ts

@@ -1,10 +1,10 @@
 import * as commander from 'commander';
 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 { IObfuscatorOptions } from '../interfaces/IObfuscatorOptions';
 
 import { SourceMapMode } from '../enums/SourceMapMode';
 import { StringArrayEncoding } from '../enums/StringArrayEncoding';
@@ -13,13 +13,13 @@ import { DEFAULT_PRESET } from '../preset-options/DefaultPreset';
 
 import { CLIUtils } from './CLIUtils';
 import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
-import { Utils } from '../Utils';
+import { Utils } from '../utils/Utils';
 
 export class JavaScriptObfuscatorCLI {
     /**
      * @type {string[]}
      */
-    private arguments: string[];
+    private readonly arguments: string[];
 
     /**
      * @type {commander.ICommand}
@@ -69,7 +69,7 @@ export class JavaScriptObfuscatorCLI {
      * @returns {string}
      */
     private static parseSourceMapMode (value: string): string {
-        let availableMode: boolean = Object
+        const availableMode: boolean = Object
             .keys(SourceMapMode)
             .some((key: string): boolean => {
                 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) {
             if (!this.commands.hasOwnProperty(option)) {
@@ -133,38 +133,110 @@ export class JavaScriptObfuscatorCLI {
                 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 {
         this.commands = new commander.Command()
             .version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version')
             .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(
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',
                 JavaScriptObfuscatorCLI.parseSourceMapMode
             )
-            .option('--stringArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean)
-            .option('--stringArrayEncoding <boolean|string> [true, false, base64, rc4]', 'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed', JavaScriptObfuscatorCLI.parseStringArrayEncoding)
-            .option('--stringArrayThreshold <number>', 'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)', parseFloat)
-            .option('--unicodeEscapeSequence <boolean>', 'Allows to enable/disable string conversion to unicode escape sequence', JavaScriptObfuscatorCLI.parseBoolean)
+            .option(
+                '--stringArray <boolean>',
+                'Disables gathering of all literal strings into an array and replacing every literal string with an array call',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
+            .option(
+                '--stringArrayEncoding <boolean|string> [true, false, base64, rc4]',
+                'Encodes all strings in strings array using base64 or rc4 (this option can slow down your code speed',
+                JavaScriptObfuscatorCLI.parseStringArrayEncoding
+            )
+            .option(
+                '--stringArrayThreshold <number>',
+                'The probability that the literal string will be inserted into stringArray (Default: 0.8, Min: 0, Max: 1)',
+                parseFloat
+            )
+            .option(
+                '--unicodeEscapeSequence <boolean>',
+                'Allows to enable/disable string conversion to unicode escape sequence',
+                JavaScriptObfuscatorCLI.parseBoolean
+            )
             .parse(this.rawArguments);
 
         this.commands.on('--help', () => {
@@ -180,8 +252,8 @@ export class JavaScriptObfuscatorCLI {
     }
 
     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) {
             this.processDataWithSourceMap(outputCodePath, options);
@@ -194,8 +266,8 @@ export class JavaScriptObfuscatorCLI {
      * @param outputCodePath
      * @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);
     }
@@ -204,8 +276,8 @@ export class JavaScriptObfuscatorCLI {
      * @param outputCodePath
      * @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,
             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 { 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 {
-    /**
-     * @type {AppendState}
-     */
-    protected abstract appendState: AppendState;
-
     /**
      * @type {IOptions}
      */
-    protected options: IOptions;
+    protected readonly options: IOptions;
 
     /**
      * @param options
      */
-    constructor (options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         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[]}
@@ -43,15 +40,10 @@ export abstract class AbstractCustomNode implements ICustomNode {
         return this.getNodeStructure();
     }
 
-    /**
-     * @param appendState
-     */
-    public setAppendState (appendState: AppendState): void {
-        this.appendState = appendState;
-    }
-
     /**
      * @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 { initializable } from '../../decorators/Initializable';
+
 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 {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
      * @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
      */
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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 { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
+@injectable()
 export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
      * @type {string}
      */
+    @initializable()
     private debugProtectionFunctionName: string;
 
     /**
-     * @param debugProtectionFunctionName
      * @param options
      */
-    constructor (debugProtectionFunctionName: string, options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         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 { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
+@injectable()
 export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
      * @type {string}
      */
+    @initializable()
     private debugProtectionFunctionName: string;
 
     /**
-     * @param debugProtectionFunctionName
      * @param options
      */
-    constructor (debugProtectionFunctionName: string, options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         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 { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
-import { Utils } from '../../Utils';
 
+@injectable()
 export class DebugProtectionFunctionNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
      * @type {string}
      */
+    @initializable()
     private debugProtectionFunctionName: string;
 
     /**
-     * @param debugProtectionFunctionName
      * @param options
      */
-    constructor (debugProtectionFunctionName: string, options: IOptions) {
+    constructor (
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
         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}
      */
-    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 { 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 {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
-
     /**
      * @type {string}
      */
+    @initializable()
     protected callsControllerFunctionName: string;
 
     /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      */
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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(';'),
-            [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';
 
@@ -14,86 +17,57 @@ import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeAppender } from '../../node/NodeAppender';
-import { NodeUtils } from '../../node/NodeUtils';
 
+@injectable()
 export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
     /**
-     * @type {AppendState}
+     * @type {TObfuscationEvent}
      */
-    protected appendState: AppendState = AppendState.BeforeObfuscation;
+    @initializable()
+    private appendEvent: TObfuscationEvent;
 
     /**
      * @type {string}
      */
+    @initializable()
     protected callsControllerFunctionName: string;
 
     /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      */
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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 { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
-import { NodeAppender } from '../../node/NodeAppender';
 import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { NodeUtils } from '../../node/NodeUtils';
-import { Utils } from '../../Utils';
+import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 
+@injectable()
 export class SelfDefendingUnicodeNode extends AbstractCustomNode {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
     /**
      * @type {string}
      */
+    @initializable()
     protected callsControllerFunctionName: string;
 
     /**
-     * @type {number}
-     */
-    protected randomStackTraceIndex: number;
-
-    /**
-     * @type {IStackTraceData[]}
-     */
-    protected stackTraceData: IStackTraceData[];
-
-    /**
-     * @param stackTraceData
-     * @param callsControllerFunctionName
-     * @param randomStackTraceIndex
      * @param options
      */
     constructor (
-        stackTraceData: IStackTraceData[],
-        callsControllerFunctionName: string,
-        randomStackTraceIndex: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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 { initializable } from '../../decorators/Initializable';
+
 import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
 import { AtobTemplate } from '../../templates/custom-nodes/AtobTemplate';
@@ -19,59 +22,68 @@ import { StringArrayRc4DecodeNodeTemplate } from '../../templates/custom-nodes/s
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 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}
      */
-    private stringArrayName: string;
+    @initializable()
+    private stringArrayCallsWrapperName: string;
 
     /**
      * @type {string}
      */
-    private stringArrayCallsWrapperName: string;
+    @initializable()
+    private stringArrayName: string;
 
     /**
-     * @param stringArrayCallsWrapperName
-     * @param stringArrayName
-     * @param stringArray
      * @param options
      */
     constructor (
-        stringArrayCallsWrapperName: string,
-        stringArrayName: string,
-        stringArray: StringArray,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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.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;
     };
 
-    /**
-     * @returns {TStatement[]}
-     */
-    public getNode (): TStatement[] {
-        return super.getNode();
-    }
-
     /**
      * @returns {string}
      */
-    protected getDecodeStringArrayTemplate (): string {
+    private getDecodeStringArrayTemplate (): string {
         let decodeStringArrayTemplate: string = '',
             selfDefendingCode: string = '';
 
         if (this.options.selfDefending) {
-            selfDefendingCode = SelfDefendingTemplate().formatUnicorn({
+            selfDefendingCode = format(SelfDefendingTemplate(), {
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayName: this.stringArrayName
             });
@@ -104,7 +109,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
 
         switch (this.options.stringArrayEncoding) {
             case StringArrayEncoding.base64:
-                decodeStringArrayTemplate = StringArrayBase64DecodeNodeTemplate().formatUnicorn({
+                decodeStringArrayTemplate = format(StringArrayBase64DecodeNodeTemplate(), {
                     atobPolyfill: AtobTemplate(),
                     selfDefendingCode,
                     stringArrayCallsWrapperName: this.stringArrayCallsWrapperName
@@ -113,7 +118,7 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
                 break;
 
             case StringArrayEncoding.rc4:
-                decodeStringArrayTemplate = StringArrayRc4DecodeNodeTemplate().formatUnicorn({
+                decodeStringArrayTemplate = format(StringArrayRc4DecodeNodeTemplate(), {
                     atobPolyfill: AtobTemplate(),
                     rc4Polyfill: Rc4Template(),
                     selfDefendingCode,
@@ -125,24 +130,4 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
 
         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 { 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}
      */
     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}
      */
+    @initializable()
     private stringArrayName: string;
 
     /**
      * @type {number}
      */
+    @initializable()
     private stringArrayRotateValue: number;
 
     /**
-     * @param stringArray
-     * @param stringArrayName
-     * @param stringArrayRotateValue
      * @param options
      */
     constructor (
-        stringArray: StringArray,
-        stringArrayName: string,
-        stringArrayRotateValue: number = 0,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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}
      */
-    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;
     }
 
@@ -89,27 +86,8 @@ export class StringArrayNode extends AbstractCustomNode {
      * @returns {TStatement[]}
      */
     public getNode (): TStatement[] {
-        this.stringArray.rotateArray(this.stringArrayRotateValue);
+        (<StringArrayStorage>this.stringArray).rotateArray(this.stringArrayRotateValue);
 
         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';
 
@@ -14,79 +15,63 @@ import { StringArrayRotateFunctionTemplate } from '../../templates/custom-nodes/
 
 import { AbstractCustomNode } from '../AbstractCustomNode';
 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 {
     /**
-     * @type {AppendState}
+     * @type {IStorage <string>}
      */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @type {StringArray}
-     */
-    private stringArray: StringArray;
+    @initializable()
+    private stringArray: IStorage <string>;
 
     /**
      * @type {string}
      */
+    @initializable()
     private stringArrayName: string;
 
     /**
      * @param {number}
      */
+    @initializable()
     private stringArrayRotateValue: number;
 
     /**
-     * @param stringArrayName
-     * @param stringArray
-     * @param stringArrayRotateValue
      * @param options
      */
     constructor (
-        stringArrayName: string,
-        stringArray: StringArray,
-        stringArrayRotateValue: number,
-        options: IOptions
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
     ) {
         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 = '',
-            timesName: string = Utils.getRandomVariableName(),
-            whileFunctionName: string = Utils.getRandomVariableName();
+            timesName: string = RandomGeneratorUtils.getRandomVariableName(),
+            whileFunctionName: string = RandomGeneratorUtils.getRandomVariableName();
 
         if (this.options.selfDefending) {
-            code = SelfDefendingTemplate().formatUnicorn({
+            code = format(SelfDefendingTemplate(), {
                 timesName,
                 whileFunctionName
             });
@@ -94,19 +79,18 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
             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({
     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}
      */

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

@@ -1,5 +1,5 @@
 import * as ESTree from 'estree';
 
 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';
 
 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[]
      */
     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
      */
     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';
 
 export interface ICustomNodeWithIdentifier extends ICustomNode {
-    /**
-     * @returns {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 {
     readonly compact: boolean;
+    readonly controlFlowFlattening: boolean;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: 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';
 
 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';
 
 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);
+    }
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است