瀏覽代碼

Merge branch 'dev'

# Conflicts:
#	CHANGELOG.md
#	dist/index.js
#	package.json
#	src/Nodes.ts
#	src/Obfuscator.ts
#	src/interfaces/nodes/INode.d.ts
#	src/node-obfuscators/CatchClauseObfuscator.ts
#	src/node-obfuscators/FunctionDeclarationObfuscator.ts
#	src/node-obfuscators/FunctionObfuscator.ts
#	src/node-obfuscators/MemberExpressionObfuscator.ts
#	src/node-obfuscators/ObjectExpressionObfuscator.ts
#	src/node-obfuscators/VariableDeclarationObfuscator.ts
#	test/functional-tests/node-obfuscators/FunctionObfuscator.spec.ts
#	test/functional-tests/node-obfuscators/VariableDeclarationObfuscator.spec.ts
#	test/index.spec.ts
#	test/mocks/NodeMocks.ts
#	test/unit-tests/NodeUtils.spec.ts
sanex3339 8 年之前
父節點
當前提交
6ab6b9bf07
共有 100 個文件被更改,包括 2438 次插入1431 次删除
  1. 3 0
      .babelrc
  2. 2 0
      .gitignore
  3. 1 0
      .npmignore
  4. 2 2
      .travis.yml
  5. 7 5
      CHANGELOG.md
  6. 84 43
      README.md
  7. 4 0
      bin/javascript-obfuscator.js
  8. 613 283
      dist/index.js
  9. 41 39
      package.json
  10. 2 2
      scripts/build
  11. 1 1
      scripts/test-compile
  12. 3 3
      scripts/test-coveralls
  13. 1 1
      scripts/test-dev
  14. 3 0
      scripts/test-dev-compile-performance
  15. 3 0
      scripts/test-dev-runtime-performance
  16. 3 3
      scripts/test-full
  17. 2 2
      scripts/test-mocha
  18. 1 1
      scripts/travis
  19. 4 4
      src/JavaScriptObfuscator.ts
  20. 19 34
      src/JavaScriptObfuscatorInternal.ts
  21. 177 0
      src/NodeAppender.ts
  22. 54 63
      src/NodeUtils.ts
  23. 84 24
      src/Nodes.ts
  24. 1 1
      src/ObfuscationResult.ts
  25. 61 29
      src/Obfuscator.ts
  26. 13 24
      src/SourceMapCorrector.ts
  27. 51 0
      src/UnicodeArray.ts
  28. 125 8
      src/Utils.ts
  29. 18 9
      src/cli/CLIUtils.ts
  30. 41 23
      src/cli/JavaScriptObfuscatorCLI.ts
  31. 17 8
      src/custom-nodes/AbstractCustomNode.ts
  32. 59 11
      src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts
  33. 12 11
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts
  34. 11 10
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts
  35. 12 11
      src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts
  36. 86 0
      src/custom-nodes/domain-lock-nodes/DomainLockNode.ts
  37. 94 0
      src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
  38. 59 24
      src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
  39. 74 23
      src/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.ts
  40. 0 99
      src/custom-nodes/unicode-array-nodes/UnicodeArrayDecodeNode.ts
  41. 26 21
      src/custom-nodes/unicode-array-nodes/UnicodeArrayNode.ts
  42. 23 21
      src/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.ts
  43. 18 0
      src/declarations/ESTree.d.ts
  44. 1 1
      src/enums/NodeType.ts
  45. 4 0
      src/enums/UnicodeArrayEncoding.ts
  46. 1 1
      src/interfaces/IGenerator.d.ts
  47. 0 0
      src/interfaces/IGeneratorOutput.d.ts
  48. 2 2
      src/interfaces/INodeObfuscator.d.ts
  49. 2 2
      src/interfaces/INodesGroup.d.ts
  50. 2 2
      src/interfaces/IObfuscator.d.ts
  51. 6 3
      src/interfaces/IObfuscatorOptions.d.ts
  52. 6 3
      src/interfaces/IOptions.d.ts
  53. 0 0
      src/interfaces/IPackageConfig.d.ts
  54. 3 0
      src/interfaces/IReplacer.d.ts
  55. 1 1
      src/interfaces/ISourceMapCorrector.d.ts
  56. 11 4
      src/interfaces/custom-nodes/ICustomNode.d.ts
  57. 1 1
      src/interfaces/custom-nodes/ICustomNodeWithData.d.ts
  58. 1 1
      src/interfaces/custom-nodes/ICustomNodeWithIdentifier.d.ts
  59. 0 3
      src/interfaces/nodes/IArrowFunctionExpressionNode.d.ts
  60. 0 7
      src/interfaces/nodes/IBlockStatementNode.d.ts
  61. 0 9
      src/interfaces/nodes/ICallExpressionNode.d.ts
  62. 0 8
      src/interfaces/nodes/ICatchClauseNode.d.ts
  63. 0 7
      src/interfaces/nodes/IExpressionStatementNode.d.ts
  64. 0 3
      src/interfaces/nodes/IFunctionDeclarationNode.d.ts
  65. 0 3
      src/interfaces/nodes/IFunctionExpressionNode.d.ts
  66. 0 14
      src/interfaces/nodes/IFunctionNode.d.ts
  67. 0 5
      src/interfaces/nodes/IIdentifierNode.d.ts
  68. 0 8
      src/interfaces/nodes/IIfStatementNode.ts
  69. 0 7
      src/interfaces/nodes/ILiteralNode.d.ts
  70. 0 9
      src/interfaces/nodes/IMemberExpressionNode.d.ts
  71. 0 12
      src/interfaces/nodes/IMethodDefinitionNode.d.ts
  72. 0 5
      src/interfaces/nodes/INode.d.ts
  73. 0 6
      src/interfaces/nodes/IObjectExpressionNode.d.ts
  74. 0 8
      src/interfaces/nodes/IProgramNode.d.ts
  75. 0 12
      src/interfaces/nodes/IPropertyNode.d.ts
  76. 0 7
      src/interfaces/nodes/ISpreadElementNode.d.ts
  77. 0 7
      src/interfaces/nodes/IVariableDeclarationNode.d.ts
  78. 0 7
      src/interfaces/nodes/IVariableDeclaratorNode.d.ts
  79. 6 0
      src/interfaces/stack-trace-analyzer/ICalleeData.d.ts
  80. 5 0
      src/interfaces/stack-trace-analyzer/ICalleeDataExtractor.d.ts
  81. 5 0
      src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts
  82. 5 0
      src/interfaces/stack-trace-analyzer/IStackTraceData.d.ts
  83. 49 0
      src/node-groups/AbstractNodesGroup.ts
  84. 33 12
      src/node-groups/ConsoleOutputNodesGroup.ts
  85. 23 25
      src/node-groups/DebugProtectionNodesGroup.ts
  86. 43 0
      src/node-groups/DomainLockNodesGroup.ts
  87. 0 27
      src/node-groups/NodesGroup.ts
  88. 39 11
      src/node-groups/SelfDefendingNodesGroup.ts
  89. 36 41
      src/node-groups/UnicodeArrayNodesGroup.ts
  90. 32 0
      src/node-obfuscators/AbstractNodeObfuscator.ts
  91. 31 13
      src/node-obfuscators/CatchClauseObfuscator.ts
  92. 31 16
      src/node-obfuscators/FunctionDeclarationObfuscator.ts
  93. 33 23
      src/node-obfuscators/FunctionObfuscator.ts
  94. 14 15
      src/node-obfuscators/LiteralObfuscator.ts
  95. 13 16
      src/node-obfuscators/MemberExpressionObfuscator.ts
  96. 11 11
      src/node-obfuscators/MethodDefinitionObfuscator.ts
  97. 0 175
      src/node-obfuscators/NodeObfuscator.ts
  98. 11 16
      src/node-obfuscators/ObjectExpressionObfuscator.ts
  99. 31 19
      src/node-obfuscators/VariableDeclarationObfuscator.ts
  100. 31 0
      src/node-obfuscators/replacers/AbstractReplacer.ts

+ 3 - 0
.babelrc

@@ -1,3 +1,6 @@
 {
+  "generatorOpts": {
+    "retainFunctionParens": true
+  },
   "presets": ["es2015"]
 }

+ 2 - 0
.gitignore

@@ -1,5 +1,7 @@
+.awcache
 .idea
 npm-debug.log
+yarn.lock
 *.js.map
 /coverage
 /node_modules

+ 1 - 0
.npmignore

@@ -0,0 +1 @@
+test/fixtures/compile-performance.js

+ 2 - 2
.travis.yml

@@ -7,9 +7,9 @@ node_js:
   - "4"
   - "6"
   - "stable"
-
+  
 script: "npm run travis"
 
 after_success:
   - npm run test:coveralls
-  - rm -rf ./coverage
+  - rm -rf ./coverage

+ 7 - 5
CHANGELOG.md

@@ -1,17 +1,19 @@
 Change Log
 ===
 
-v0.7.2
+v0.8.0
 ---
-* runtime error fix [#7](https://github.com/sanex3339/webpack-obfuscator/issues/7)
-
-* shorthand object expression fix [#16](https://github.com/sanex3339/javascript-obfuscator/issues/16)
+* **Breaking options change:** `encodeUnicodeArray` has been renamed to `unicodeArrayEncoding` and now accepts following values: `true|false|'base64'|'rc4'`.
+* **Breaking change:** option `wrapUnicodeArrayCalls` was removed and now all calls to `unicodeArray` are always wrapped by special wrapper function.
+* New option `sourceMapBaseUrl` sets base url to the source map import url when `sourceMapMode: 'separate'`.
+* Custom nodes like `selfDefendingNode` or `consoleOutputNode` now inserted into deepest stack trace function call.
+* Rewrite of many custom nodes.
 
 v0.7.1
 ---
 * IE error fix [#14](https://github.com/sanex3339/javascript-obfuscator/issues/14)
 
-v0.7.0-dev.3
+v0.7.0
 ---
 * Obfuscator now returns an empty string instead of obfuscated code if source code is empty
 

+ 84 - 43
README.md

@@ -12,14 +12,21 @@ JavaScript obfuscator for Node.js is a free alternative to [js-obfuscator](https
 * compatible with ES6;
 * tested on Angular2 bundle.
 
-https://gist.github.com/sanex3339/ffc2876123b52e6d11ce45369fd53acf
+Online version: [javascriptobfuscator.herokuapp.com](https://javascriptobfuscator.herokuapp.com)
+
+Example of obfuscated code: [gist.github.com](https://gist.github.com/sanex3339/ffc2876123b52e6d11ce45369fd53acf)
+
+#### Plugins:
+* Webpack: [webpack-obfuscator](https://github.com/javascript-obfuscator/webpack-obfuscator)
+* Gulp: [gulp-javascript-obfuscator](https://github.com/javascript-obfuscator/gulp-javascript-obfuscator)
+* Grunt: [grunt-contrib-obfuscator](https://github.com/javascript-obfuscator/grunt-contrib-obfuscator)
 
 [![npm version](https://badge.fury.io/js/javascript-obfuscator.svg)](https://badge.fury.io/js/javascript-obfuscator)
-[![Build Status](https://travis-ci.org/sanex3339/javascript-obfuscator.svg?branch=master)](https://travis-ci.org/sanex3339/javascript-obfuscator)
-[![Coverage Status](https://coveralls.io/repos/github/sanex3339/javascript-obfuscator/badge.svg?branch=master)](https://coveralls.io/github/sanex3339/javascript-obfuscator?branch=master)
+[![Build Status](https://travis-ci.org/javascript-obfuscator/javascript-obfuscator.svg?branch=master)](https://travis-ci.org/javascript-obfuscator/javascript-obfuscator)
+[![Coverage Status](https://coveralls.io/repos/github/javascript-obfuscator/javascript-obfuscator/badge.svg?branch=master)](https://coveralls.io/github/javascript-obfuscator/javascript-obfuscator?branch=master)
 
-[![Dependency Status](https://david-dm.org/sanex3339/javascript-obfuscator.svg)](https://david-dm.org/sanex3339/javascript-obfuscator)
-[![devDependency Status](https://david-dm.org/sanex3339/javascript-obfuscator/dev-status.svg)](https://david-dm.org/sanex3339/javascript-obfuscator#info=devDependencies)
+[![Dependency Status](https://david-dm.org/javascript-obfuscator/javascript-obfuscator.svg)](https://david-dm.org/javascript-obfuscator/javascript-obfuscator)
+[![devDependency Status](https://david-dm.org/javascript-obfuscator/javascript-obfuscator/dev-status.svg)](https://david-dm.org/javascript-obfuscator/javascript-obfuscator#info=devDependencies)
 
 ## Installation
 
@@ -109,15 +116,16 @@ Following options available for the JS Obfuscator:
     debugProtection: false,
     debugProtectionInterval: false,
     disableConsoleOutput: true,
-    encodeUnicodeLiterals: false,
     reservedNames: [],
     rotateUnicodeArray: true,
     selfDefending: true,
     sourceMap: false,
+    sourceMapBaseUrl: '',
+    sourceMapFileName: '',
     sourceMapMode: 'separate',
     unicodeArray: true,
-    unicodeArrayThreshold: 0.8,
-    wrapUnicodeArrayCalls: true
+    unicodeArrayEncoding: false,
+    unicodeArrayThreshold: 0.8
 }
 ```
 
@@ -132,15 +140,16 @@ Following options available for the JS Obfuscator:
     --debugProtection <boolean>
     --debugProtectionInterval <boolean>
     --disableConsoleOutput <boolean>
-    --encodeUnicodeLiterals <boolean>
     --reservedNames <list> (comma separated)
     --rotateUnicodeArray <boolean>
     --selfDefending <boolean>
     --sourceMap <boolean>
+    --sourceMapBaseUrl <string>
+    --sourceMapFileName <string>
     --sourceMapMode <string> [inline, separate]
     --unicodeArray <boolean>
+    --unicodeArrayEncoding <boolean|string> [true, false, base64, rc4]
     --unicodeArrayThreshold <number>
-    --wrapUnicodeArrayCalls <boolean>
 ```
 
 ### `compact`
@@ -151,9 +160,9 @@ Compact code output on one line.
 ### `debugProtection`
 Type: `boolean` Default: `false`
 
-##### :warning: Can freeze browser while Developer Tools are enabled! Use at own risk.
+##### :warning: Can freeze your browser if you open the Developer Tools.
 
-Force enable debug mode on page load if Developer Tools panel is enabled (in some, mainly WebKit-based, browsers). This makes it almost impossible to use the Console (the debug panel).
+This option makes it almost impossible to use the `console` tab of the Developer Tools (both on WebKit-based and Mozilla Firefox).
 
 * WebKit-based: blocks the site window, but you still can navigate through Developer Tools panel.
 * Firefox: does *not* block the site window, but still won't let you use DevTools.
@@ -161,31 +170,27 @@ Force enable debug mode on page load if Developer Tools panel is enabled (in som
 ### `debugProtectionInterval`
 Type: `boolean` Default: `false`
 
-##### :warning: Can freeze browser even while Developer Tools are disabled! Use at own risk.
-
-Works if `debugProtection` is enabled.
+##### :warning: Can freeze your browser! Use at own risk.
 
-Force enable debug mode in some browsers (mainly WebKit-based) when Developer Tools panel is enabled, even after page is loaded.
+If checked, an interval is used to force the debug mode on the Console tab, making it harder to use other features of the Developer Tools. Works if `debugProtection` is enabled.
 
 ### `disableConsoleOutput`
 Type: `boolean` Default: `true`
 
-Disable `console.log`, `console.info`, `console.error` and `console.warn` messages output into the browser console.
-
-### `encodeUnicodeLiterals`
-Type: `boolean` Default: `false`
+Disables the use of `console.log`, `console.info`, `console.error` and `console.warn` by replacing them with empty functions. This makes the use of the debugger harder.
 
-##### :warning: `unicodeArray` option must be enabled
+### `domainLock`
+Type: `string[]` Default: `[]`
 
-This option can slightly slow down your code speed.
+Locks the obfuscated source code so it only runs on specific domains and/or sub-domains. This makes really hard for someone just copy and paste your source code and run elsewhere.
 
-All literals in Unicode array become encoded in Base64.
-To decode strings, a special function will be inserted on the page under `unicodeArray` node.
+##### Multiple domains and sub-domains
+It's possible to lock your code to more than one domain or sub-domain. For instance, to lock it so the code only runs on **www.example.com** add `www.example.com`, to make it work on any sub-domain from example.com, use `.example.com`.
 
 ### `reservedNames`
 Type: `string[]` Default: `[]`
 
-Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp pattern.
+Disables the obfuscation of variables names, function names and function parameters that match the Regular Expression used.
 
 Example:
 ```javascript
@@ -202,24 +207,55 @@ Type: `boolean` Default: `true`
 
 ##### :warning: `unicodeArray` must be enabled
 
-Shift the `unicodeArray` values by a random number of places during the code obfuscation and insert a helper function for shifting the array back into the source code. (It works just like the Caesar cypher.)
+Shift the `unicodeArray` array by a fixed and random (generated at the code obfuscation) places. This makes it harder to match the order of the removed strings to their original place.
 
-Keep in mind that this option affects only how the code is visually organised, since the original arrays can be easily accessed during the debug process.
+This option is recommended if your original source code isn't small, as the helper function can attract attention.
 
-It is also not recommended to enable `rotateUnicodeArray` for small source code because a helper function might attract attention.
 
 ### `selfDefending`
 Type: `boolean` Default: `true`
 
 ##### :warning: this option forcibly set `compact` value to `true`
 
-Enables self-defending for obfuscated code. If obfuscated compact code is formatted, it will not work any more.
+This option makes the output code resilient against formatting and variable renaming. If one tries to use a JavaScript beautifier on the obfuscated code, the code won't work anymore, making it harder to understand and modify it.
 
 ### `sourceMap`
 Type: `boolean` Default: `false`
 
 Enables source map generation for obfuscated code.
 
+Source maps can be useful to help you debug your obfuscated Java Script source code. If you want or need to debug in production, you can upload the separate source map file to a secret location and then point your browser there. 
+
+### `sourceMapBaseUrl`
+Type: `string` Default: ``
+
+Sets base url to the source map import url when `sourceMapMode: 'separate'`.
+ 
+CLI example:
+```
+javascript-obfuscator input.js --output out.js --sourceMap true --sourceMapBaseUrl 'http://localhost:9000'
+```
+
+Result: 
+```
+//# sourceMappingURL=http://localhost:9000/out.js.map
+```
+
+### `sourceMapFileName`
+Type: `string` Default: ``
+
+Sets file name for output source map when `sourceMapMode: 'separate'`.
+
+CLI example:
+```
+javascript-obfuscator input.js --output out.js --sourceMap true --sourceMapBaseUrl 'http://localhost:9000' --sourceMapFileName example
+```
+
+Result: 
+```
+//# sourceMappingURL=http://localhost:9000/example.js.map
+```
+
 ### `sourceMapMode`
 Type: `string` Default: `separate`
 
@@ -230,28 +266,33 @@ Specifies source map generation mode:
 ### `unicodeArray`
 Type: `boolean` Default: `true`
 
-Put all literal strings into an array and replace every literal string by an array call.
-
-### `unicodeArrayThreshold`
-Type: `number` Default: `0.8` Min: `0` Max: `1`
+Removes string literals and place them in a special array. For instance the string `"Hello World"` in `var m = "Hello World";` will be replaced with something like `var m = _0x12c456[0x1];`
+    
+### `unicodeArrayEncoding`
+Type: `boolean|string` Default: `false`
 
 ##### :warning: `unicodeArray` option must be enabled
 
-The probability that the literal string will be inserted into `unicodeArray`.
-Use this option for huge source code size, because many calls to `unicodeArray` will slow down code performance.
+This option can slightly slow down your script.
 
-`unicodeArrayThreshold: 0` equals to `unicodeArray: false`.
+Encode all string literals of the `unicodeArray` using `base64` or `rc4` and inserts a special code that used to decode it back at runtime.
 
-### `wrapUnicodeArrayCalls`
-Type: `boolean` Default: `true`
+Available values:
+* `true` (`boolean`): encode `unicodeArray` values using `base64`
+* `false` (`boolean`): don't encode `unicodeArray` values
+* `'base64'` (`string`): encode `unicodeArray` values using `base64`
+* `'rc4'` (`string`): encode `unicodeArray` values using `rc4`. **About 30-35% slower then `base64`, but more harder to get initial values**
+    
+### `unicodeArrayThreshold`
+Type: `number` Default: `0.8` Min: `0` Max: `1`
 
 ##### :warning: `unicodeArray` option must be enabled
 
-Instead of using direct calls to `unicodeArray` items `var t = _0x43a123[0x0]`, when index `0x0` can be easily reverted to `0` with few js beautifiers, this option will wrap all calls to special function instead.
+You can use this setting to adjust the probability (from 0 to 1) that a string literal will be inserted into the `unicodeArray`.
 
-```javascript
-var t = _0x12a634('0x0')
-```
+This setting is useful with large code size because repeatdely calls to the `Unicode Array` function can slow down your code.
+
+`unicodeArrayThreshold: 0` equals to `unicodeArray: false`.
 
 ## License
 Copyright (C) 2016 [Timofey Kachalov](http://github.com/sanex3339).
@@ -274,4 +315,4 @@ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 4 - 0
bin/javascript-obfuscator.js

@@ -1,3 +1,7 @@
 #!/usr/bin/env node
 
+if (!global._babelPolyfill) {
+    require('babel-polyfill');
+}
+
 require('../dist/index').runCLI(process.argv);

File diff suppressed because it is too large
+ 613 - 283
dist/index.js


+ 41 - 39
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.7.2",
+  "version": "0.8.0-dev.3",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -20,49 +20,49 @@
     "javascript-obfuscator": "./bin/javascript-obfuscator.js"
   },
   "dependencies": {
-    "babel-polyfill": "^6.13.0",
-    "chance": "^1.0.4",
-    "class-validator": "^0.6.1",
-    "commander": "^2.9.0",
-    "escodegen": "^1.8.1",
-    "esprima": "^3.0.0",
-    "estraverse": "^4.2.0",
-    "format-unicorn": "^1.1.0",
-    "mkdirp": "^0.5.1",
-    "source-map-support": "^0.4.2"
+    "babel-polyfill": "6.16.0",
+    "chance": "1.0.4",
+    "class-validator": "0.6.4",
+    "commander": "2.9.0",
+    "escodegen": "1.8.1",
+    "esprima": "3.1.1",
+    "estraverse": "4.2.0",
+    "format-unicorn": "1.1.0",
+    "mkdirp": "0.5.1",
+    "source-map-support": "0.4.6"
   },
   "devDependencies": {
-    "@types/chai": "^3.4.32",
-    "@types/chance": "^0.7.28",
-    "@types/commander": "^2.3.29",
-    "@types/escodegen": "0.0.3",
-    "@types/esprima": "^2.1.31",
-    "@types/estraverse": "0.0.3",
-    "@types/format-unicorn": "^0.0.29",
-    "@types/joi": "^9.0.31",
-    "@types/mkdirp": "^0.3.28",
-    "@types/mocha": "^2.2.31",
-    "@types/node": "^4.0.30",
-    "@types/sinon": "^1.16.29",
-    "babel-cli": "^6.14.0",
-    "babel-loader": "^6.2.5",
-    "babel-preset-es2015": "^6.14.0",
-    "chai": "^3.5.0",
-    "coveralls": "^2.11.14",
+    "@types/chai": "3.4.34",
+    "@types/chance": "0.7.30",
+    "@types/commander": "2.3.30",
+    "@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.32",
+    "@types/mkdirp": "0.3.29",
+    "@types/mocha": "2.2.32",
+    "@types/node": "6.0.46",
+    "@types/sinon": "1.16.31",
+    "awesome-typescript-loader": "2.2.4",
+    "babel-cli": "6.18.0",
+    "babel-loader": "6.2.7",
+    "babel-preset-es2015": "6.18.0",
+    "chai": "3.5.0",
+    "coveralls": "2.11.14",
     "istanbul": "1.1.0-alpha.1",
-    "mocha": "^3.0.2",
-    "optimize-js-plugin": "0.0.4",
-    "sinon": "^2.0.0-pre.3",
-    "ts-loader": "^0.8.2",
-    "ts-node": "^1.3.0",
-    "tslint": "^3.15.1",
-    "typescript": "^2.0.0",
-    "webpack": "^2.1.0-beta.25",
-    "webpack-node-externals": "^1.4.3"
+    "mocha": "3.1.2",
+    "sinon": "2.0.0-pre.3",
+    "ts-node": "1.6.1",
+    "tslint": "3.15.1",
+    "typescript": "2.0.7",
+    "webpack": "2.1.0-beta.25",
+    "webpack-node-externals": "1.5.4"
   },
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/sanex3339/javascript-obfuscator.git"
+    "url": "git+https://github.com/javascript-obfuscator/javascript-obfuscator.git"
   },
   "scripts": {
     "start": "scripts/start",
@@ -71,6 +71,8 @@
     "watch": "scripts/watch",
     "test:compile": "scripts/test-compile",
     "test:dev": "scripts/test-dev",
+    "test:devCompilePerformance": "scripts/test-dev-compile-performance",
+    "test:devRuntimePerformance": "scripts/test-dev-runtime-performance",
     "test:full": "scripts/test-full",
     "test:coveralls": "scripts/test-coveralls",
     "test:mocha": "scripts/test-mocha",
@@ -83,4 +85,4 @@
     "name": "Timofey Kachalov"
   },
   "license": "BSD-2-Clause"
-}
+}

+ 2 - 2
scripts/build

@@ -1,5 +1,5 @@
 #!/bin/bash
 
-npm run webpack
-npm run tslint
+npm run webpack &&
+npm run tslint &&
 npm test

+ 1 - 1
scripts/test-compile

@@ -1,4 +1,4 @@
 #!/bin/bash
 
-$(npm bin)/tsc -p tsconfig-test.json
+$(npm bin)/tsc -p tsconfig-test.json &&
 $(npm bin)/babel test-tmp --out-dir test-tmp --source-maps inline --presets es2015

+ 3 - 3
scripts/test-coveralls

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-npm run test:compile
-$(npm bin)/istanbul cover $(npm bin)/_mocha -- test-tmp/test/index.spec.js --report lcovonly
-cat ./coverage/lcov.info | $(npm bin)/coveralls
+npm run test:compile &&
+$(npm bin)/istanbul cover $(npm bin)/_mocha -- test-tmp/test/index.spec.js --report lcovonly &&
+cat ./coverage/lcov.info | $(npm bin)/coveralls &&
 npm run test:removeTmpDir

+ 1 - 1
scripts/test-dev

@@ -1,3 +1,3 @@
 #!/bin/bash
 
-$(npm bin)/ts-node test/dev/test.ts
+$(npm bin)/ts-node test/dev/dev.ts

+ 3 - 0
scripts/test-dev-compile-performance

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$(npm bin)/ts-node test/dev/dev-compile-performance.ts

+ 3 - 0
scripts/test-dev-runtime-performance

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$(npm bin)/ts-node test/dev/dev-runtime-performance.ts

+ 3 - 3
scripts/test-full

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-npm run test:compile
-node test-tmp/test/dev/test.js
-$(npm bin)/istanbul cover $(npm bin)/_mocha -- test-tmp/test/index.spec.js
+npm run test:compile &&
+node test-tmp/test/dev/dev.js &&
+$(npm bin)/istanbul cover $(npm bin)/_mocha -- test-tmp/test/index.spec.js &&
 npm run test:removeTmpDir

+ 2 - 2
scripts/test-mocha

@@ -1,5 +1,5 @@
 #!/bin/bash
 
-npm run test:compile
-$(npm bin)/mocha test-tmp/test/index.spec.js
+npm run test:compile &&
+$(npm bin)/mocha test-tmp/test/index.spec.js &&
 npm run test:removeTmpDir

+ 1 - 1
scripts/travis

@@ -1,4 +1,4 @@
 #!/bin/bash
 
-npm run tslint
+npm run tslint &&
 npm test

+ 4 - 4
src/JavaScriptObfuscator.ts

@@ -1,8 +1,8 @@
-import { IObfuscationResult } from "./interfaces/IObfuscationResult";
-import { IObfuscatorOptions } from "./interfaces/IObfuscatorOptions";
+import { IObfuscationResult } from './interfaces/IObfuscationResult';
+import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 
-import { JavaScriptObfuscatorCLI } from "./cli/JavaScriptObfuscatorCLI";
-import { JavaScriptObfuscatorInternal } from "./JavaScriptObfuscatorInternal";
+import { JavaScriptObfuscatorCLI } from './cli/JavaScriptObfuscatorCLI';
+import { JavaScriptObfuscatorInternal } from './JavaScriptObfuscatorInternal';
 
 export class JavaScriptObfuscator {
     /**

+ 19 - 34
src/JavaScriptObfuscatorInternal.ts

@@ -1,16 +1,16 @@
 import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
+import * as ESTree from 'estree';
 
-import { IObfuscatorOptions } from "./interfaces/IObfuscatorOptions";
-import { IGeneratorOutput } from "./interfaces/IGeneratorOutput";
-import { INode } from './interfaces/nodes/INode';
-import { IObfuscationResult } from "./interfaces/IObfuscationResult";
+import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
+import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
+import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IOptions } from './interfaces/IOptions';
 
-import { ObfuscationResult } from "./ObfuscationResult";
-import { Obfuscator } from "./Obfuscator";
-import { Options } from "./options/Options";
-import { SourceMapCorrector } from "./SourceMapCorrector";
+import { ObfuscationResult } from './ObfuscationResult';
+import { Obfuscator } from './Obfuscator';
+import { Options } from './options/Options';
+import { SourceMapCorrector } from './SourceMapCorrector';
 
 export class JavaScriptObfuscatorInternal {
     /**
@@ -36,21 +36,13 @@ export class JavaScriptObfuscatorInternal {
      */
     private sourceCode: string;
 
-    /**
-     * @type {string}
-     */
-    private sourceMapUrl: string = '';
-
     /**
      * @param sourceCode
      * @param obfuscatorOptions
      */
-    constructor (sourceCode: string, obfuscatorOptions?: IObfuscatorOptions) {
+    constructor (sourceCode: string, obfuscatorOptions: IObfuscatorOptions = {}) {
         this.sourceCode = sourceCode;
-
-        if (obfuscatorOptions) {
-            this.options = new Options(obfuscatorOptions);
-        }
+        this.options = new Options(obfuscatorOptions);
     }
 
     /**
@@ -58,12 +50,11 @@ export class JavaScriptObfuscatorInternal {
      * @param astTree
      * @param options
      */
-    private static generateCode (sourceCode: string, astTree: INode, options: IOptions): IGeneratorOutput {
-        let escodegenParams: escodegen.GenerateOptions = Object.assign(
-                {},
-                JavaScriptObfuscatorInternal.escodegenParams
-            ),
-            generatorOutput: IGeneratorOutput;
+    private static generateCode (sourceCode: string, astTree: ESTree.Node, options: IOptions): IGeneratorOutput {
+        const escodegenParams: escodegen.GenerateOptions = Object.assign(
+            {},
+            JavaScriptObfuscatorInternal.escodegenParams
+        );
 
         if (options.sourceMap) {
             escodegenParams.sourceMap = 'sourceMap';
@@ -74,7 +65,8 @@ export class JavaScriptObfuscatorInternal {
             compact: options.compact
         };
 
-        generatorOutput = escodegen.generate(astTree, escodegenParams);
+        const generatorOutput: IGeneratorOutput = escodegen.generate(astTree, escodegenParams);
+
         generatorOutput.map = generatorOutput.map ? generatorOutput.map.toString() : '';
 
         return generatorOutput;
@@ -89,13 +81,13 @@ export class JavaScriptObfuscatorInternal {
                 this.generatorOutput.code,
                 this.generatorOutput.map
             ),
-            this.sourceMapUrl,
+            this.options.sourceMapBaseUrl + this.options.sourceMapFileName,
             this.options.sourceMapMode
         ).correct();
     }
 
     public obfuscate (): void {
-        let astTree: INode = esprima.parse(this.sourceCode, {
+        let astTree: ESTree.Node = esprima.parse(this.sourceCode, {
             loc: true
         });
 
@@ -103,11 +95,4 @@ export class JavaScriptObfuscatorInternal {
 
         this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
     }
-
-    /**
-     * @param url
-     */
-    public setSourceMapUrl (url: string): void {
-        this.sourceMapUrl = url;
-    }
 }

+ 177 - 0
src/NodeAppender.ts

@@ -0,0 +1,177 @@
+import * as ESTree from 'estree';
+
+import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
+import { TStatement } from './types/TStatement';
+
+import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { Utils } from './Utils';
+
+/**
+ * This class appends node into a first deepest BlockStatement in order of function calls
+ *
+ * For example:
+ *
+ * function Foo () {
+ *     var baz = function () {
+ *
+ *     }
+ *
+ *     baz();
+ * }
+ *
+ * foo();
+ *
+ * Appends node into block statement of `baz` function expression.
+ */
+export class NodeAppender {
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     */
+    public static appendNode (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): void {
+        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
+        }
+
+        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...blockScopeNode.body,
+            ...nodeBodyStatements
+        ];
+    }
+
+    /**
+     * @param blockScopeStackTraceData
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     * @param index
+     */
+    public static appendNodeToOptimalBlockScope (
+        blockScopeStackTraceData: IStackTraceData[],
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[],
+        index: number = 0
+    ): void {
+        let targetBlockScope: TNodeWithBlockStatement;
+
+        if (!blockScopeStackTraceData.length) {
+            targetBlockScope = blockScopeNode;
+        } else {
+            targetBlockScope = NodeAppender.getOptimalBlockScope(
+                blockScopeStackTraceData,
+                index
+            );
+        }
+
+        NodeAppender.prependNode(targetBlockScope, nodeBodyStatements);
+    }
+
+    /**
+     * Returns deepest block scope node at given deep.
+     *
+     * @param blockScopeTraceData
+     * @param index
+     * @param deep
+     * @returns {ESTree.BlockStatement}
+     */
+    public static getOptimalBlockScope (
+        blockScopeTraceData: IStackTraceData[],
+        index: number,
+        deep: number = Infinity
+    ): ESTree.BlockStatement {
+        const firstCall: IStackTraceData = blockScopeTraceData[index];
+
+        if (deep <= 0) {
+            throw new Error(`Invalid \`deep\` argument value. Value should be bigger then 0.`);
+        }
+
+        if (deep > 1 && firstCall.stackTrace.length) {
+            return NodeAppender.getOptimalBlockScope(firstCall.stackTrace, 0, --deep);
+        } else {
+            return firstCall.callee;
+        }
+    }
+
+    /**
+     * @param stackTraceRootLength
+     */
+    public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
+        return Utils.getRandomGenerator().integer({
+            min: 0,
+            max: Math.max(0, Math.round(stackTraceRootLength - 1))
+        });
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     * @param index
+     */
+    public static insertNodeAtIndex (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[],
+        index: number
+    ): void {
+        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
+        }
+
+        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...blockScopeNode.body.slice(0, index),
+            ...nodeBodyStatements,
+            ...blockScopeNode.body.slice(index)
+        ];
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     */
+    public static prependNode (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): void {
+        if (!NodeAppender.validateBodyStatements(nodeBodyStatements)) {
+            nodeBodyStatements = [];
+        }
+
+        nodeBodyStatements = NodeAppender.parentizeBodyStatementsBeforeAppend(blockScopeNode, nodeBodyStatements);
+
+        blockScopeNode.body = [
+            ...nodeBodyStatements,
+            ...blockScopeNode.body,
+        ];
+    }
+
+    /**
+     * @param blockScopeNode
+     * @param nodeBodyStatements
+     */
+    private static parentizeBodyStatementsBeforeAppend (
+        blockScopeNode: TNodeWithBlockStatement,
+        nodeBodyStatements: TStatement[]
+    ): TStatement[] {
+        for (let statement of nodeBodyStatements) {
+            statement.parentNode = blockScopeNode;
+        }
+
+        return nodeBodyStatements;
+    }
+
+    /**
+     * @param nodeBodyStatements
+     * @returns {boolean}
+     */
+    private static validateBodyStatements (nodeBodyStatements: TStatement[]): boolean {
+        return nodeBodyStatements.every(statementNode => {
+            return !!statementNode && statementNode.hasOwnProperty('type');
+        });
+    }
+}

+ 54 - 63
src/NodeUtils.ts

@@ -1,16 +1,15 @@
 import * as escodegen from 'escodegen';
 import * as esprima from 'esprima';
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { INode } from './interfaces/nodes/INode';
+import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
+import { TStatement } from './types/TStatement';
 
-import { TNodeWithBlockStatement } from "./types/TNodeWithBlockStatement";
-import { TStatement } from "./types/nodes/TStatement";
+import { NodeType } from './enums/NodeType';
 
-import { NodeType } from "./enums/NodeType";
-
-import { Nodes } from "./Nodes";
-import { Utils } from "./Utils";
+import { Nodes } from './Nodes';
+import { Utils } from './Utils';
 
 export class NodeUtils {
     /**
@@ -27,50 +26,36 @@ export class NodeUtils {
     /**
      * @param node
      */
-    public static addXVerbatimPropertyToLiterals (node: INode): void {
-        estraverse.replace(node, {
-            enter: (node: INode, parentNode: INode): any => {
-                if (Nodes.isLiteralNode(node)) {
-                   node['x-verbatim-property'] = {
-                        content : node.raw,
-                        precedence: escodegen.Precedence.Primary
-                    };
-                }
+    public static addXVerbatimPropertyToLiterals (node: ESTree.Node): void {
+        NodeUtils.typedReplace(node, NodeType.Literal, {
+            leave: (node: ESTree.Literal) => {
+                node['x-verbatim-property'] = {
+                    content : node.raw,
+                    precedence: escodegen.Precedence.Primary
+                };
             }
         });
     }
 
-    /**
-     * @param blockScopeBody
-     * @param node
-     */
-    public static appendNode (blockScopeBody: INode[], node: INode): void {
-        if (!NodeUtils.validateNode(node)) {
-            return;
-        }
-
-        blockScopeBody.push(node);
-    }
-
     /**
      * @param code
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    public static convertCodeToStructure (code: string): INode {
-        let structure: INode = esprima.parse(code);
+    public static convertCodeToStructure (code: string): TStatement[] {
+        let structure: ESTree.Program = esprima.parse(code);
 
         NodeUtils.addXVerbatimPropertyToLiterals(structure);
         NodeUtils.parentize(structure);
 
-        return NodeUtils.getBlockStatementNodeByIndex(structure);
+        return <TStatement[]>structure.body;
     }
 
     /**
      * @param node
      * @param index
-     * @returns {INode}
+     * @returns {ESTree.Node}
      */
-    public static getBlockStatementNodeByIndex (node: INode, index: number = 0): INode {
+    public static getBlockStatementNodeByIndex (node: ESTree.Node, index: number = 0): ESTree.Node {
         if (Nodes.isNodeHasBlockStatement(node)) {
             if (node.body[index] === undefined) {
                 throw new ReferenceError(`Wrong index \`${index}\`. Block-statement body length is \`${node.body.length}\``);
@@ -85,10 +70,10 @@ export class NodeUtils {
     /**
      * @param node
      * @param depth
-     * @returns {INode}
+     * @returns {ESTree.Node}
      */
-    public static getBlockScopeOfNode (node: INode, depth: number = 0): TNodeWithBlockStatement {
-        let parentNode: INode | undefined = node.parentNode;
+    public static getBlockScopeOfNode (node: ESTree.Node, depth: number = 0): TNodeWithBlockStatement {
+        let parentNode: ESTree.Node | undefined = node.parentNode;
 
         if (!parentNode) {
             throw new ReferenceError('`parentNode` property of given node is `undefined`');
@@ -115,28 +100,15 @@ export class NodeUtils {
         return NodeUtils.getBlockScopeOfNode(parentNode);
     }
 
-    /**
-     * @param blockScopeBody
-     * @param node
-     * @param index
-     */
-    public static insertNodeAtIndex (blockScopeBody: INode[], node: INode, index: number): void {
-        if (!NodeUtils.validateNode(node)) {
-            return;
-        }
-
-        blockScopeBody.splice(index, 0, node);
-    }
-
     /**
      * @param node
      */
-    public static parentize (node: INode): void {
+    public static parentize (node: ESTree.Node): void {
         let isRootNode: boolean = true;
 
         estraverse.replace(node, {
-            enter: (node: INode, parentNode: INode): any => {
-                let value: INode;
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                let value: ESTree.Node;
 
                 if (isRootNode) {
                     if (node.type === NodeType.Program) {
@@ -158,22 +130,41 @@ export class NodeUtils {
     }
 
     /**
-     * @param blockScopeBody
      * @param node
+     * @param nodeType
+     * @param visitor
      */
-    public static prependNode (blockScopeBody: INode[], node: INode): void {
-        if (!NodeUtils.validateNode(node)) {
-            return;
-        }
-
-        blockScopeBody.unshift(node);
+    public static typedReplace (
+        node: ESTree.Node,
+        nodeType: string,
+        visitor: {enter?: (node: ESTree.Node) => void, leave?: (node: ESTree.Node) => void},
+    ): void {
+        NodeUtils.typedTraverse(node, nodeType, visitor, 'replace');
     }
 
     /**
      * @param node
-     * @returns {boolean}
+     * @param nodeType
+     * @param visitor
+     * @param traverseType
      */
-    private static validateNode (node: INode): boolean {
-        return !!node && node.hasOwnProperty('type');
+    public static typedTraverse (
+        node: ESTree.Node,
+        nodeType: string,
+        visitor: estraverse.Visitor,
+        traverseType: string = 'traverse'
+    ): void {
+        (<any>estraverse)[traverseType](node, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (node.type === nodeType && visitor.enter) {
+                    visitor.enter(node, parentNode);
+                }
+            },
+            leave: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (node.type === nodeType && visitor.leave) {
+                    visitor.leave(node, parentNode);
+                }
+            }
+        });
     }
 }

+ 84 - 24
src/Nodes.ts

@@ -1,25 +1,16 @@
-import { IBlockStatementNode } from "./interfaces/nodes/IBlockStatementNode";
-import { IIdentifierNode } from "./interfaces/nodes/IIdentifierNode";
-import { ILiteralNode } from "./interfaces/nodes/ILiteralNode";
-import { IMemberExpressionNode } from "./interfaces/nodes/IMemberExpressionNode";
-import { INode } from "./interfaces/nodes/INode";
-import { IProgramNode } from "./interfaces/nodes/IProgramNode";
-import { IPropertyNode } from "./interfaces/nodes/IPropertyNode";
-import { IVariableDeclaratorNode } from "./interfaces/nodes/IVariableDeclaratorNode";
+import * as ESTree from 'estree';
 
-import { TStatement } from "./types/nodes/TStatement";
+import { TNodeWithBlockStatement } from './types/TNodeWithBlockStatement';
+import { TStatement } from './types/TStatement';
 
-import { TNodeWithBlockStatement } from "./types/TNodeWithBlockStatement";
-
-import { NodeType } from "./enums/NodeType";
-import { ICallExpressionNode } from "./interfaces/nodes/ICallExpressionNode";
+import { NodeType } from './enums/NodeType';
 
 export class Nodes {
     /**
      * @param bodyNode
-     * @returns IProgramNode
+     * @returns ESTree.Program
      */
-    public static getProgramNode (bodyNode: TStatement[]): IProgramNode {
+    public static getProgramNode (bodyNode: TStatement[]): ESTree.Program {
         return {
             'type': NodeType.Program,
             'body': bodyNode,
@@ -32,7 +23,15 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isBlockStatementNode (node: INode): node is IBlockStatementNode {
+    public static isArrowFunctionExpressionNode (node: ESTree.Node): node is ESTree.ArrowFunctionExpression {
+        return node.type === NodeType.ArrowFunctionExpression;
+    }
+
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isBlockStatementNode (node: ESTree.Node): node is ESTree.BlockStatement {
         return node.type === NodeType.BlockStatement;
     }
 
@@ -40,7 +39,7 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isCallExpressionNode (node: INode): node is ICallExpressionNode {
+    public static isCallExpressionNode (node: ESTree.Node): node is ESTree.CallExpression {
         return node.type === NodeType.CallExpression;
     }
 
@@ -48,7 +47,31 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isIdentifierNode (node: INode): node is IIdentifierNode {
+    public static isExpressionStatementNode (node: ESTree.Node): node is ESTree.ExpressionStatement {
+        return node.type === NodeType.ExpressionStatement;
+    }
+
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isFunctionDeclarationNode (node: ESTree.Node): node is ESTree.FunctionDeclaration {
+        return node.type === NodeType.FunctionDeclaration;
+    }
+
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isFunctionExpressionNode (node: ESTree.Node): node is ESTree.FunctionExpression {
+        return node.type === NodeType.FunctionExpression;
+    }
+
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isIdentifierNode (node: ESTree.Node): node is ESTree.Identifier {
         return node.type === NodeType.Identifier;
     }
 
@@ -56,7 +79,7 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isLiteralNode (node: INode): node is ILiteralNode {
+    public static isLiteralNode (node: ESTree.Node): node is ESTree.Literal {
         return node.type === NodeType.Literal;
     }
 
@@ -64,16 +87,24 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isMemberExpressionNode (node: INode): node is IMemberExpressionNode {
+    public static isMemberExpressionNode (node: ESTree.Node): node is ESTree.MemberExpression {
         return node.type === NodeType.MemberExpression;
     }
 
+    /**
+     * @param node
+     * @returns {boolean}
+     */
+    public static isObjectExpressionNode (node: ESTree.Node): node is ESTree.ObjectExpression {
+        return node.type === NodeType.ObjectExpression;
+    }
+
     /**
      *
      * @param node
      * @returns {boolean}
      */
-    public static isProgramNode (node: INode): node is IProgramNode {
+    public static isProgramNode (node: ESTree.Node): node is ESTree.Program {
         return node.type === NodeType.Program;
     }
 
@@ -82,16 +113,45 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isPropertyNode (node: INode): node is IPropertyNode {
+    public static isPropertyNode (node: ESTree.Node): node is ESTree.Property {
         return node.type === NodeType.Property;
     }
 
+    /**
+     * @param node
+     * @param parentNode
+     * @returns {boolean}
+     */
+    public static isReplaceableIdentifierNode (node: ESTree.Node, parentNode: ESTree.Node): node is ESTree.Identifier {
+        if (!Nodes.isIdentifierNode(node)) {
+            return false;
+        }
+
+        const parentNodeIsPropertyNode: boolean = Nodes.isPropertyNode(parentNode) && parentNode.key === node;
+        const parentNodeIsMemberExpressionNode: boolean = (
+            Nodes.isMemberExpressionNode(parentNode) &&
+            parentNode.computed === false &&
+            parentNode.property === node
+        );
+
+        return !parentNodeIsPropertyNode && !parentNodeIsMemberExpressionNode;
+    }
+
+    /**
+     *
+     * @param node
+     * @returns {boolean}
+     */
+    public static isVariableDeclarationNode (node: ESTree.Node): node is ESTree.VariableDeclaration {
+        return node.type === NodeType.VariableDeclaration;
+    }
+
     /**
      *
      * @param node
      * @returns {boolean}
      */
-    public static isVariableDeclaratorNode (node: INode): node is IVariableDeclaratorNode {
+    public static isVariableDeclaratorNode (node: ESTree.Node): node is ESTree.VariableDeclarator {
         return node.type === NodeType.VariableDeclarator;
     }
 
@@ -99,7 +159,7 @@ export class Nodes {
      * @param node
      * @returns {boolean}
      */
-    public static isNodeHasBlockStatement (node: INode): node is TNodeWithBlockStatement {
+    public static isNodeHasBlockStatement (node: ESTree.Node): node is TNodeWithBlockStatement {
         return node.hasOwnProperty('body') && Array.isArray((<TNodeWithBlockStatement>node).body);
     }
 }

+ 1 - 1
src/ObfuscationResult.ts

@@ -1,4 +1,4 @@
-import { IObfuscationResult } from "./interfaces/IObfuscationResult";
+import { IObfuscationResult } from './interfaces/IObfuscationResult';
 
 export class ObfuscationResult implements IObfuscationResult {
     /**

+ 61 - 29
src/Obfuscator.ts

@@ -1,40 +1,50 @@
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
 import { ICustomNode } from './interfaces/custom-nodes/ICustomNode';
-import { INode } from './interfaces/nodes/INode';
-import { IObfuscator } from "./interfaces/IObfuscator";
-import { IOptions } from "./interfaces/IOptions";
+import { IObfuscator } from './interfaces/IObfuscator';
+import { IOptions } from './interfaces/IOptions';
+import { IStackTraceData } from './interfaces/stack-trace-analyzer/IStackTraceData';
 
-import { TNodeObfuscator } from "./types/TNodeObfuscator";
+import { TNodeGroup } from './types/TNodeGroup';
+import { TNodeObfuscator } from './types/TNodeObfuscator';
 
 import { AppendState } from './enums/AppendState';
 import { NodeType } from './enums/NodeType';
 
 import { CatchClauseObfuscator } from './node-obfuscators/CatchClauseObfuscator';
-import { ConsoleOutputNodesGroup } from "./node-groups/ConsoleOutputNodesGroup";
+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 { LiteralObfuscator } from './node-obfuscators/LiteralObfuscator';
 import { MemberExpressionObfuscator } from './node-obfuscators/MemberExpressionObfuscator';
 import { MethodDefinitionObfuscator } from './node-obfuscators/MethodDefinitionObfuscator';
-import { Nodes } from "./Nodes";
-import { NodeUtils } from "./NodeUtils";
+import { Nodes } from './Nodes';
+import { NodeUtils } from './NodeUtils';
 import { ObjectExpressionObfuscator } from './node-obfuscators/ObjectExpressionObfuscator';
-import { SelfDefendingNodesGroup } from "./node-groups/SelfDefendingNodesGroup";
+import { SelfDefendingNodesGroup } from './node-groups/SelfDefendingNodesGroup';
 import { UnicodeArrayNodesGroup } from './node-groups/UnicodeArrayNodesGroup';
 import { VariableDeclarationObfuscator } from './node-obfuscators/VariableDeclarationObfuscator';
+import { StackTraceAnalyzer } from './stack-trace-analyzer/StackTraceAnalyzer';
 
 export class Obfuscator implements IObfuscator {
     /**
-     * @type {Map<string, Node>}
+     * @type {TNodeGroup[]}
      */
-    private nodes: Map <string, ICustomNode>;
+    private static nodeGroups: TNodeGroup[] = [
+        DomainLockNodesGroup,
+        SelfDefendingNodesGroup,
+        ConsoleOutputNodesGroup,
+        DebugProtectionNodesGroup,
+        UnicodeArrayNodesGroup
+    ];
 
     /**
      * @type {Map<string, TNodeObfuscator[]>}
      */
-    private nodeObfuscators: Map <string, TNodeObfuscator[]> = new Map <string, TNodeObfuscator[]> ([
+    private static nodeObfuscators: Map <string, TNodeObfuscator[]> = new Map <string, TNodeObfuscator[]> ([
         [NodeType.ArrowFunctionExpression, [FunctionObfuscator]],
         [NodeType.ClassDeclaration, [FunctionDeclarationObfuscator]],
         [NodeType.CatchClause, [CatchClauseObfuscator]],
@@ -50,6 +60,11 @@ export class Obfuscator implements IObfuscator {
         [NodeType.Literal, [LiteralObfuscator]]
     ]);
 
+    /**
+     * @type {Map<string, AbstractCustomNode>}
+     */
+    private customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ();
+
     /**
      * @type {IOptions}
      */
@@ -60,26 +75,23 @@ export class Obfuscator implements IObfuscator {
      */
     constructor (options: IOptions) {
         this.options = options;
-
-        this.nodes = new Map <string, ICustomNode> ([
-            ...new SelfDefendingNodesGroup(this.options).getNodes(),
-            ...new ConsoleOutputNodesGroup(this.options).getNodes(),
-            ...new DebugProtectionNodesGroup(this.options).getNodes(),
-            ...new UnicodeArrayNodesGroup(this.options).getNodes()
-        ]);
     }
 
     /**
      * @param node
-     * @returns {INode}
+     * @returns {ESTree.Node}
      */
-    public obfuscateNode (node: INode): INode {
+    public obfuscateNode (node: ESTree.Program): ESTree.Node {
         if (Nodes.isProgramNode(node) && !node.body.length) {
             return node;
         }
 
         NodeUtils.parentize(node);
 
+        const stackTraceData: IStackTraceData[] = new StackTraceAnalyzer(node.body).analyze();
+
+        this.initializeCustomNodes(stackTraceData);
+
         this.beforeObfuscation(node);
         this.obfuscate(node);
         this.afterObfuscation(node);
@@ -90,8 +102,8 @@ export class Obfuscator implements IObfuscator {
     /**
      * @param astTree
      */
-    private afterObfuscation (astTree: INode): void {
-        this.nodes.forEach((node: ICustomNode) => {
+    private afterObfuscation (astTree: ESTree.Node): void {
+        this.customNodes.forEach((node: ICustomNode) => {
             if (node.getAppendState() === AppendState.AfterObfuscation) {
                 node.appendNode(astTree);
             }
@@ -101,37 +113,57 @@ export class Obfuscator implements IObfuscator {
     /**
      * @param astTree
      */
-    private beforeObfuscation (astTree: INode): void {
-        this.nodes.forEach((node: ICustomNode) => {
+    private beforeObfuscation (astTree: ESTree.Node): void {
+        this.customNodes.forEach((node: ICustomNode) => {
             if (node.getAppendState() === AppendState.BeforeObfuscation) {
                 node.appendNode(astTree);
             }
         });
     };
 
+    /**
+     * @param stackTraceData
+     */
+    private initializeCustomNodes (stackTraceData: IStackTraceData[]): void {
+        Obfuscator.nodeGroups.forEach((nodeGroupConstructor: TNodeGroup) => {
+            const nodeGroupNodes: Map <string, ICustomNode> | undefined = new nodeGroupConstructor(
+                stackTraceData, this.options
+            ).getNodes();
+
+            if (!nodeGroupNodes) {
+                return;
+            }
+
+            this.customNodes = new Map <string, ICustomNode> ([
+                ...this.customNodes,
+                ...nodeGroupNodes
+            ]);
+        });
+    }
+
 
     /**
      * @param node
      * @param parentNode
      */
-    private initializeNodeObfuscators (node: INode, parentNode: INode): void {
-        let nodeObfuscators: TNodeObfuscator[] | undefined = this.nodeObfuscators.get(node.type);
+    private initializeNodeObfuscators (node: ESTree.Node, parentNode: ESTree.Node): void {
+        let nodeObfuscators: TNodeObfuscator[] | undefined = Obfuscator.nodeObfuscators.get(node.type);
 
         if (!nodeObfuscators) {
             return;
         }
 
         nodeObfuscators.forEach((obfuscator: TNodeObfuscator) => {
-            new obfuscator(this.nodes, this.options).obfuscateNode(node, parentNode);
+            new obfuscator(this.customNodes, this.options).obfuscateNode(node, parentNode);
         });
     }
 
     /**
      * @param node
      */
-    private obfuscate (node: INode): void {
+    private obfuscate (node: ESTree.Node): void {
         estraverse.replace(node, {
-            enter: (node: INode, parentNode: INode): any => {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 this.initializeNodeObfuscators(node, parentNode);
             }
         });

+ 13 - 24
src/SourceMapCorrector.ts

@@ -1,12 +1,12 @@
-import { IObfuscationResult } from "./interfaces/IObfuscationResult";
-import { ISourceMapCorrector } from "./interfaces/ISourceMapCorrector";
+import { IObfuscationResult } from './interfaces/IObfuscationResult';
+import { ISourceMapCorrector } from './interfaces/ISourceMapCorrector';
 
-import { TSourceMapMode } from "./types/TSourceMapMode";
+import { TSourceMapMode } from './types/TSourceMapMode';
 
-import { SourceMapMode } from "./enums/SourceMapMode";
+import { SourceMapMode } from './enums/SourceMapMode';
 
-import { ObfuscationResult } from "./ObfuscationResult";
-import { Utils } from "./Utils";
+import { ObfuscationResult } from './ObfuscationResult';
+import { Utils } from './Utils';
 
 export class SourceMapCorrector implements ISourceMapCorrector {
     /**
@@ -52,7 +52,7 @@ export class SourceMapCorrector implements ISourceMapCorrector {
     public correct (): IObfuscationResult {
         return new ObfuscationResult(
             this.correctObfuscatedCode(),
-            this.correctSourceMap()
+            this.sourceMap
         );
     }
 
@@ -68,32 +68,21 @@ export class SourceMapCorrector implements ISourceMapCorrector {
 
         switch (this.sourceMapMode) {
             case SourceMapMode.Inline:
-                sourceMappingUrl += `data:application/json;base64,${Utils.btoa(this.sourceMapUrl || this.sourceMap, false)}`;
+                sourceMappingUrl += `data:application/json;base64,${Utils.btoa(this.sourceMap)}`;
 
                 break;
 
             case SourceMapMode.Separate:
             default:
-                if (this.sourceMapUrl) {
-                    sourceMappingUrl += this.sourceMapUrl;
-
-                    break;
+                if (!this.sourceMapUrl) {
+                    return this.obfuscatedCode;
                 }
 
-                return this.obfuscatedCode;
+                sourceMappingUrl += this.sourceMapUrl;
+
+                break;
         }
 
         return `${this.obfuscatedCode}\n${sourceMappingUrl}`;
     };
-
-    /**
-     * @returns {string}
-     */
-    private correctSourceMap (): string {
-        if (this.sourceMapMode === SourceMapMode.Inline) {
-            return '';
-        }
-
-        return this.sourceMap;
-    }
 }

+ 51 - 0
src/UnicodeArray.ts

@@ -0,0 +1,51 @@
+import { Utils } from './Utils';
+
+export class UnicodeArray {
+    /**
+     * @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.toString();
+    }
+}

+ 125 - 8
src/Utils.ts

@@ -44,12 +44,31 @@ export class Utils {
 
     /**
      * @param string
-     * @param encode
      */
-    public static btoa (string: string, encode: boolean = true): string {
-        return new Buffer(
-            encode ? encodeURI(string) : string
-        ).toString('base64');
+    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;
     }
 
     /**
@@ -62,6 +81,24 @@ export class Utils {
         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;
+    }
+
     /**
      * @returns {Chance.Chance}
      */
@@ -71,7 +108,7 @@ export class Utils {
 
     /**
      * @param length
-     * @returns any
+     * @returns {string}
      */
     public static getRandomVariableName (length: number = 6): string {
         const rangeMinInteger: number = 10000,
@@ -88,6 +125,46 @@ export class Utils {
         ).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 (Math.random() < 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}
@@ -96,6 +173,46 @@ export class Utils {
         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 obj
      * @returns {T}
@@ -134,9 +251,9 @@ export class Utils {
                 template = '0'.repeat(2);
             } else {
                 prefix = '\\u';
-                template = '0'.repeat(4);  
+                template = '0'.repeat(4);
             }
-            
+
             return `${prefix}${(template + escape.charCodeAt(0).toString(radix)).slice(-template.length)}`;
         })}'`;
     }

+ 18 - 9
src/cli/CLIUtils.ts

@@ -2,9 +2,9 @@ import * as fs from 'fs';
 import * as mkdirp from 'mkdirp';
 import * as path from 'path';
 
-import { IPackageConfig } from "../interfaces/IPackageConfig";
+import { IPackageConfig } from '../interfaces/IPackageConfig';
 
-import { Utils } from "../Utils";
+import { Utils } from '../Utils';
 
 export class CLIUtils {
     /**
@@ -38,15 +38,24 @@ export class CLIUtils {
     }
 
     /**
+     * @param outputCodePath
+     * @param sourceMapFileName
      * @returns {string}
      */
-    public static getOutputSourceMapPath (outputCodePath: string): string {
-        return outputCodePath
-            .split('.')
-            .map<string>((value: string, index: number, array: string[]) => {
-                return index === array.length - 1 ? `${value}.map` : value;
-            })
-            .join('.');
+    public static getOutputSourceMapPath (outputCodePath: string, sourceMapFileName: string = ''): string {
+        if (sourceMapFileName) {
+            outputCodePath = `${outputCodePath.substr(
+                0, outputCodePath.lastIndexOf('/')
+            )}/${sourceMapFileName}`;
+        }
+
+        if (!/\.js\.map$/.test(outputCodePath)) {
+            outputCodePath = `${outputCodePath.split('.')[0]}.js.map`;
+        } else if (/\.js$/.test(outputCodePath)) {
+            outputCodePath += '.map';
+        }
+
+        return outputCodePath;
     }
 
     /**

+ 41 - 23
src/cli/JavaScriptObfuscatorCLI.ts

@@ -1,17 +1,19 @@
 import * as commander from 'commander';
 import * as path from 'path';
 
-import { IObfuscationResult } from "../interfaces/IObfuscationResult";
-import { IObfuscatorOptions } from "../interfaces/IObfuscatorOptions";
+import { TUnicodeArrayEncoding } from '../types/TUnicodeArrayEncoding';
 
-import { SourceMapMode } from "../enums/SourceMapMode";
+import { IObfuscationResult } from '../interfaces/IObfuscationResult';
+import { IObfuscatorOptions } from '../interfaces/IObfuscatorOptions';
 
-import { DEFAULT_PRESET } from "../preset-options/DefaultPreset";
+import { SourceMapMode } from '../enums/SourceMapMode';
+import { UnicodeArrayEncoding } from '../enums/UnicodeArrayEncoding';
 
-import { CLIUtils } from "./CLIUtils";
-import { JavaScriptObfuscator } from "../JavaScriptObfuscator";
-import { JavaScriptObfuscatorInternal } from "../JavaScriptObfuscatorInternal";
-import { Utils } from "../Utils";
+import { DEFAULT_PRESET } from '../preset-options/DefaultPreset';
+
+import { CLIUtils } from './CLIUtils';
+import { JavaScriptObfuscator } from '../JavaScriptObfuscator';
+import { Utils } from '../Utils';
 
 export class JavaScriptObfuscatorCLI {
     /**
@@ -80,6 +82,25 @@ export class JavaScriptObfuscatorCLI {
         return value;
     }
 
+    /**
+     * @param value
+     * @returns {TUnicodeArrayEncoding}
+     */
+    private static parseUnicodeArrayEncoding (value: string): TUnicodeArrayEncoding {
+        switch (value) {
+            case 'true':
+            case '1':
+            case UnicodeArrayEncoding.base64:
+                return true;
+
+            case UnicodeArrayEncoding.rc4:
+                return UnicodeArrayEncoding.rc4;
+
+            default:
+                return false;
+        }
+    }
+
     public run (): void {
         this.configureCommands();
 
@@ -103,7 +124,7 @@ export class JavaScriptObfuscatorCLI {
         let obfuscatorOptions: IObfuscatorOptions = {},
             availableOptions: string[] = Object.keys(DEFAULT_PRESET);
 
-        for (let option in this.commands) {
+        for (const option in this.commands) {
             if (!this.commands.hasOwnProperty(option)) {
                 continue;
             }
@@ -127,19 +148,21 @@ export class JavaScriptObfuscatorCLI {
             .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('--encodeUnicodeLiterals <boolean>', 'All literals in Unicode array become encoded in Base64 (this option can slightly slow down your code speed)', 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('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')
+            .option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`')
             .option(
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',
                 JavaScriptObfuscatorCLI.parseSourceMapMode
             )
             .option('--unicodeArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--unicodeArrayEncoding <boolean|string> [true, false, base64, rc4]', 'All literals in Unicode array become encoded in using base64 or rc4 (this option can slightly slow down your code speed', JavaScriptObfuscatorCLI.parseUnicodeArrayEncoding)
             .option('--unicodeArrayThreshold <number>', 'The probability that the literal string will be inserted into unicodeArray (Default: 0.8, Min: 0, Max: 1)', parseFloat)
-            .option('--wrapUnicodeArrayCalls <boolean>', 'Disables usage of special access function instead of direct array call', JavaScriptObfuscatorCLI.parseBoolean)
             .parse(this.rawArguments);
 
         this.commands.on('--help', () => {
@@ -180,23 +203,18 @@ export class JavaScriptObfuscatorCLI {
      * @param options
      */
     private processDataWithSourceMap (outputCodePath: string, options: IObfuscatorOptions): void {
-        let javaScriptObfuscator: JavaScriptObfuscatorInternal = new JavaScriptObfuscatorInternal(this.data, options),
-            obfuscationResult: IObfuscationResult,
-            outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(outputCodePath);
+        let outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(
+            outputCodePath,
+            options.sourceMapFileName || ''
+        );
 
-        javaScriptObfuscator.obfuscate();
-
-        if (options.sourceMapMode === SourceMapMode.Separate) {
-            javaScriptObfuscator.setSourceMapUrl(
-                path.basename(outputSourceMapPath)
-            );
-        }
+        options.sourceMapFileName = path.basename(outputSourceMapPath);
 
-        obfuscationResult = javaScriptObfuscator.getObfuscationResult();
+        const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(this.data, options);
 
         CLIUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
 
-        if (obfuscationResult.getSourceMap()) {
+        if (options.sourceMapMode === 'separate' && obfuscationResult.getSourceMap()) {
             CLIUtils.writeFile(outputSourceMapPath, obfuscationResult.getSourceMap());
         }
     }

+ 17 - 8
src/custom-nodes/Node.ts → src/custom-nodes/AbstractCustomNode.ts

@@ -1,10 +1,12 @@
+import * as ESTree from 'estree';
+
 import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { INode } from '../interfaces/nodes/INode';
-import { IOptions } from "../interfaces/IOptions";
+import { IOptions } from '../interfaces/IOptions';
+import { TStatement } from '../types/TStatement';
 
 import { AppendState } from '../enums/AppendState';
 
-export abstract class Node implements ICustomNode {
+export abstract class AbstractCustomNode implements ICustomNode {
     /**
      * @type {AppendState}
      */
@@ -25,7 +27,7 @@ export abstract class Node implements ICustomNode {
     /**
      * @param astTree
      */
-    public abstract appendNode (astTree: INode): void;
+    public abstract appendNode (astTree: ESTree.Node): void;
 
     /**
      * @returns {AppendState}
@@ -35,14 +37,21 @@ export abstract class Node implements ICustomNode {
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    public getNode (): INode {
+    public getNode (): TStatement[] {
         return this.getNodeStructure();
     }
 
     /**
-     * @returns {INode}
+     * @param appendState
+     */
+    public setAppendState (appendState: AppendState): void {
+        this.appendState = appendState;
+    }
+
+    /**
+     * @returns {TStatement[]}
      */
-    protected abstract getNodeStructure (): INode;
+    protected abstract getNodeStructure (): TStatement[];
 }

+ 59 - 11
src/custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode.ts

@@ -1,25 +1,70 @@
-import { INode } from "../../interfaces/nodes/INode";
+import 'format-unicorn';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { AppendState } from "../../enums/AppendState";
+import { IOptions } from '../../interfaces/IOptions';
+import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
-import { ConsoleOutputDisableExpressionTemplate } from "../../templates/custom-nodes/console-output-nodes/console-output-disable-expression-node/ConsoleOutputDisableExpressionTemplate";
+import { AppendState } from '../../enums/AppendState';
 
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
+import { ConsoleOutputDisableExpressionTemplate } from '../../templates/custom-nodes/console-output-nodes/console-output-disable-expression-node/ConsoleOutputDisableExpressionTemplate';
 
-export class ConsoleOutputDisableExpressionNode extends Node {
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+import { Utils } from '../../Utils';
+
+export class ConsoleOutputDisableExpressionNode extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
     protected appendState: AppendState = AppendState.BeforeObfuscation;
 
+    /**
+     * @type {string}
+     */
+    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
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
     /**
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+        NodeAppender.appendNodeToOptimalBlockScope(
+            this.stackTraceData,
+            blockScopeNode,
+            this.getNode(),
+            this.randomStackTraceIndex
+        );
     }
 
     /**
@@ -36,11 +81,14 @@ export class ConsoleOutputDisableExpressionNode extends Node {
      *  _console
      *  })();
      *
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
-            ConsoleOutputDisableExpressionTemplate()
+            ConsoleOutputDisableExpressionTemplate().formatUnicorn({
+                consoleLogDisableFunctionName: Utils.getRandomVariableName(),
+                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+            })
         );
     }
 }

+ 12 - 11
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionCallNode.ts

@@ -1,18 +1,19 @@
 import 'format-unicorn';
 
-import { INode } from "../../interfaces/nodes/INode";
-import { IOptions } from "../../interfaces/IOptions";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
 
-import { AppendState } from "../../enums/AppendState";
+import { AppendState } from '../../enums/AppendState';
 
-import { DebugProtectionFunctionCallTemplate } from "../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-call-node/DebufProtectionFunctionCallTemplate";
+import { DebugProtectionFunctionCallTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-call-node/DebufProtectionFunctionCallTemplate';
 
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
 
-export class DebugProtectionFunctionCallNode extends Node {
+export class DebugProtectionFunctionCallNode extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
@@ -37,13 +38,13 @@ export class DebugProtectionFunctionCallNode extends Node {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.appendNode(blockScopeNode.body, this.getNode());
+        NodeAppender.appendNode(blockScopeNode, this.getNode());
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             DebugProtectionFunctionCallTemplate().formatUnicorn({
                 debugProtectionFunctionName: this.debugProtectionFunctionName

+ 11 - 10
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionIntervalNode.ts

@@ -1,18 +1,19 @@
 import 'format-unicorn';
 
-import { INode } from "../../interfaces/nodes/INode";
-import { IOptions } from "../../interfaces/IOptions";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
 
-import { AppendState } from "../../enums/AppendState";
+import { AppendState } from '../../enums/AppendState';
 
-import { DebugProtectionFunctionIntervalTemplate } from "../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-interval-node/DebugProtectionFunctionIntervalTemplate";
+import { DebugProtectionFunctionIntervalTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-interval-node/DebugProtectionFunctionIntervalTemplate';
 
-import { Node } from '../Node';
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
 
-export class DebugProtectionFunctionIntervalNode extends Node {
+export class DebugProtectionFunctionIntervalNode extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
@@ -37,13 +38,13 @@ export class DebugProtectionFunctionIntervalNode extends Node {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        NodeUtils.appendNode(blockScopeNode.body, this.getNode());
+        NodeAppender.appendNode(blockScopeNode, this.getNode());
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             DebugProtectionFunctionIntervalTemplate().formatUnicorn({
                 debugProtectionFunctionName: this.debugProtectionFunctionName

+ 12 - 11
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts

@@ -1,19 +1,20 @@
 import 'format-unicorn';
 
-import { INode } from "../../interfaces/nodes/INode";
-import { IOptions } from "../../interfaces/IOptions";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
 
-import { AppendState } from "../../enums/AppendState";
+import { AppendState } from '../../enums/AppendState';
 
-import { DebugProtectionFunctionTemplate } from "../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate";
+import { DebugProtectionFunctionTemplate } from '../../templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate';
 
-import { Node } from '../Node';
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
 import { NodeUtils } from '../../NodeUtils';
-import { Utils } from "../../Utils";
+import { Utils } from '../../Utils';
 
-export class DebugProtectionFunctionNode extends Node {
+export class DebugProtectionFunctionNode extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
@@ -44,7 +45,7 @@ export class DebugProtectionFunctionNode extends Node {
                 max: programBodyLength
             });
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), randomIndex);
+        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
     }
 
     /**
@@ -57,9 +58,9 @@ export class DebugProtectionFunctionNode extends Node {
     /**
      * Found this trick in JScrambler
      *
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             DebugProtectionFunctionTemplate().formatUnicorn({
                 debugProtectionFunctionName: this.debugProtectionFunctionName

+ 86 - 0
src/custom-nodes/domain-lock-nodes/DomainLockNode.ts

@@ -0,0 +1,86 @@
+import 'format-unicorn';
+
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
+import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { AppendState } from '../../enums/AppendState';
+
+import { DomainLockNodeTemplate } from '../../templates/custom-nodes/domain-lock-nodes/domain-lock-node/DomainLockNodeTemplate';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+import { Utils } from '../../Utils';
+
+export class DomainLockNode extends AbstractCustomNode {
+    /**
+     * @type {AppendState}
+     */
+    protected appendState: AppendState = AppendState.BeforeObfuscation;
+
+    /**
+     * @type {string}
+     */
+    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
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.appendNodeToOptimalBlockScope(
+            this.stackTraceData,
+            blockScopeNode,
+            this.getNode(),
+            this.randomStackTraceIndex
+        );
+    }
+
+    /**
+     * @returns {TStatement[]}
+     */
+    protected getNodeStructure (): TStatement[] {
+        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
+            })
+        );
+    }
+}

+ 94 - 0
src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts

@@ -0,0 +1,94 @@
+import 'format-unicorn';
+
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
+
+import { IOptions } from '../../interfaces/IOptions';
+import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
+
+import { AppendState } from '../../enums/AppendState';
+
+import { SingleNodeCallControllerTemplate } from '../../templates/custom-nodes/SingleNodeCallControllerTemplate';
+
+import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
+
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+
+export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
+    /**
+     * @type {AppendState}
+     */
+    protected appendState: AppendState = AppendState.BeforeObfuscation;
+
+    /**
+     * @type {string}
+     */
+    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
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    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());
+    }
+
+    /**
+     * @returns {TStatement[]}
+     */
+    protected getNodeStructure (): TStatement[] {
+        if (this.appendState === AppendState.AfterObfuscation) {
+            return NodeUtils.convertCodeToStructure(
+                JavaScriptObfuscator.obfuscate(SingleNodeCallControllerTemplate().formatUnicorn({
+                    singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+                }), NO_CUSTOM_NODES_PRESET).getObfuscatedCode()
+            );
+        }
+
+        return NodeUtils.convertCodeToStructure(
+            SingleNodeCallControllerTemplate().formatUnicorn({
+                singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+            })
+        );
+    }
+}

+ 59 - 24
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -1,48 +1,83 @@
-import { INode } from "../../interfaces/nodes/INode";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
+import { IStackTraceData } from '../../interfaces/stack-trace-analyzer/IStackTraceData';
 
-import { AppendState } from "../../enums/AppendState";
+import { AppendState } from '../../enums/AppendState';
 
-import { NO_CUSTOM_NODES_PRESET } from "../../preset-options/NoCustomNodesPreset";
+import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
-import { SelfDefendingTemplate } from "../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate";
+import { SelfDefendingTemplate } from '../../templates/custom-nodes/self-defending-nodes/self-defending-unicode-node/SelfDefendingTemplate';
 
-import { JavaScriptObfuscator } from "../../JavaScriptObfuscator";
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
-import { Utils } from "../../Utils";
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
+import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
+import { NodeUtils } from '../../NodeUtils';
+import { Utils } from '../../Utils';
 
-export class SelfDefendingUnicodeNode extends Node {
+export class SelfDefendingUnicodeNode extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @param blockScopeNode
+     * @type {string}
      */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        let programBodyLength: number = blockScopeNode.body.length,
-            randomIndex: number = 0;
+    protected callsControllerFunctionName: string;
 
-        if (programBodyLength > 2) {
-            randomIndex = Utils.getRandomGenerator().integer({
-                min: programBodyLength / 2,
-                max: programBodyLength - 1
-            });
-        }
+    /**
+     * @type {number}
+     */
+    protected randomStackTraceIndex: number;
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), randomIndex);
+    /**
+     * @type {IStackTraceData[]}
+     */
+    protected stackTraceData: IStackTraceData[];
+
+    /**
+     * @param stackTraceData
+     * @param callsControllerFunctionName
+     * @param randomStackTraceIndex
+     * @param options
+     */
+    constructor (
+        stackTraceData: IStackTraceData[],
+        callsControllerFunctionName: string,
+        randomStackTraceIndex: number,
+        options: IOptions
+    ) {
+        super(options);
+
+        this.stackTraceData = stackTraceData;
+        this.callsControllerFunctionName = callsControllerFunctionName;
+        this.randomStackTraceIndex = randomStackTraceIndex;
+    }
+
+    /**
+     * @param blockScopeNode
+     */
+    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
+        NodeAppender.appendNodeToOptimalBlockScope(
+            this.stackTraceData,
+            blockScopeNode,
+            this.getNode(),
+            this.randomStackTraceIndex
+        );
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             JavaScriptObfuscator.obfuscate(
-                SelfDefendingTemplate(),
+                SelfDefendingTemplate().formatUnicorn({
+                    selfDefendingFunctionName: Utils.getRandomVariableName(),
+                    singleNodeCallControllerFunctionName: this.callsControllerFunctionName
+                }),
                 NO_CUSTOM_NODES_PRESET
             ).getObfuscatedCode()
         );

+ 74 - 23
src/custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper.ts

@@ -1,28 +1,38 @@
 import 'format-unicorn';
 
-import { INode } from "../../interfaces/nodes/INode";
-import { IOptions } from "../../interfaces/IOptions";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
 
-import { AppendState } from "../../enums/AppendState";
+import { AppendState } from '../../enums/AppendState';
+import { UnicodeArrayEncoding } from '../../enums/UnicodeArrayEncoding';
 
-import { UnicodeArrayCallsWrapperTemplate } from "../../templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/UnicodeArrayCallsWrapperTemplate";
+import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
-import { Utils } from "../../Utils";
+import { AtobTemplate } from '../../templates/custom-nodes/AtobTemplate';
+import { Rc4Template } from '../../templates/custom-nodes/Rc4Template';
+import { SelfDefendingTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/SelfDefendingTemplate';
+import { UnicodeArrayBase64DecodeNodeTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/UnicodeArrayBase64DecodeNodeTemplate';
+import { UnicodeArrayCallsWrapperTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/UnicodeArrayCallsWrapperTemplate';
+import { UnicodeArrayRc4DecodeNodeTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-calls-wrapper/UnicodeArrayRC4DecodeNodeTemplate';
 
-export class UnicodeArrayCallsWrapper extends Node {
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+import { UnicodeArray } from '../../UnicodeArray';
+
+export class UnicodeArrayCallsWrapper extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {string[]}
+     * @type {UnicodeArray}
      */
-    private unicodeArray: string[];
+    private unicodeArray: UnicodeArray;
 
     /**
      * @type {string}
@@ -43,7 +53,7 @@ export class UnicodeArrayCallsWrapper extends Node {
     constructor (
         unicodeArrayCallsWrapperName: string,
         unicodeArrayName: string,
-        unicodeArray: string[],
+        unicodeArray: UnicodeArray,
         options: IOptions
     ) {
         super(options);
@@ -57,11 +67,11 @@ export class UnicodeArrayCallsWrapper extends Node {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.unicodeArray.length) {
+        if (!this.unicodeArray.getLength()) {
             return;
         }
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), 1);
+        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
     /**
@@ -72,24 +82,65 @@ export class UnicodeArrayCallsWrapper extends Node {
     };
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    public getNode (): INode {
+    public getNode (): TStatement[] {
         return super.getNode();
     }
 
     /**
-     * @returns {INode}
+     * @returns {string}
      */
-    protected getNodeStructure (): INode {
-        let keyName: string = Utils.getRandomVariableName();
+    protected getDecodeUnicodeArrayTemplate (): string {
+        let decodeUnicodeArrayTemplate: string = '',
+            selfDefendingCode: string = '';
 
-        return NodeUtils.convertCodeToStructure(
-            UnicodeArrayCallsWrapperTemplate().formatUnicorn({
-                keyName: keyName,
+        if (this.options.selfDefending) {
+            selfDefendingCode = SelfDefendingTemplate().formatUnicorn({
                 unicodeArrayCallsWrapperName: this.unicodeArrayCallsWrapperName,
                 unicodeArrayName: this.unicodeArrayName
-            })
+            });
+        }
+
+        switch (this.options.unicodeArrayEncoding) {
+            case UnicodeArrayEncoding.base64:
+                decodeUnicodeArrayTemplate = UnicodeArrayBase64DecodeNodeTemplate().formatUnicorn({
+                    atobPolyfill: AtobTemplate(),
+                    selfDefendingCode,
+                    unicodeArrayCallsWrapperName: this.unicodeArrayCallsWrapperName
+                });
+
+                break;
+
+            case UnicodeArrayEncoding.rc4:
+                decodeUnicodeArrayTemplate = UnicodeArrayRc4DecodeNodeTemplate().formatUnicorn({
+                    atobPolyfill: AtobTemplate(),
+                    rc4Polyfill: Rc4Template(),
+                    selfDefendingCode,
+                    unicodeArrayCallsWrapperName: this.unicodeArrayCallsWrapperName
+                });
+
+                break;
+        }
+
+        return decodeUnicodeArrayTemplate;
+    }
+
+    /**
+     * @returns {TStatement[]}
+     */
+    protected getNodeStructure (): TStatement[] {
+        const decodeNodeTemplate: string = this.getDecodeUnicodeArrayTemplate();
+
+        return NodeUtils.convertCodeToStructure(
+            JavaScriptObfuscator.obfuscate(
+                UnicodeArrayCallsWrapperTemplate().formatUnicorn({
+                    decodeNodeTemplate,
+                    unicodeArrayCallsWrapperName: this.unicodeArrayCallsWrapperName,
+                    unicodeArrayName: this.unicodeArrayName
+                }),
+                NO_CUSTOM_NODES_PRESET
+            ).getObfuscatedCode()
         );
     }
 }

+ 0 - 99
src/custom-nodes/unicode-array-nodes/UnicodeArrayDecodeNode.ts

@@ -1,99 +0,0 @@
-import 'format-unicorn';
-
-import { INode } from "../../interfaces/nodes/INode";
-import { IOptions } from "../../interfaces/IOptions";
-
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
-
-import { AppendState } from "../../enums/AppendState";
-
-import { NO_CUSTOM_NODES_PRESET } from "../../preset-options/NoCustomNodesPreset";
-
-import { AtobTemplate } from "../../templates/custom-nodes/AtobTemplate";
-import { SelfDefendingTemplate } from "../../templates/custom-nodes/unicode-array-nodes/unicode-array-decode-node/SelfDefendingTemplate";
-import { UnicodeArrayDecodeTemplate } from "../../templates/custom-nodes/unicode-array-nodes/unicode-array-decode-node/UnicodeArrayDecodeTemplate";
-
-import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
-
-export class UnicodeArrayDecodeNode extends Node {
-    /**
-     * @type {AppendState}
-     */
-    protected appendState: AppendState = AppendState.AfterObfuscation;
-
-    /**
-     * @type {string[]}
-     */
-    private unicodeArray: string[];
-
-    /**
-     * @type {string}
-     */
-    private unicodeArrayName: string;
-
-    /**
-     * @param unicodeArrayName
-     * @param unicodeArray
-     * @param options
-     */
-    constructor (
-        unicodeArrayName: string,
-        unicodeArray: string[],
-        options: IOptions
-    ) {
-        super(options);
-
-        this.unicodeArrayName = unicodeArrayName;
-        this.unicodeArray = unicodeArray;
-    }
-
-    /**
-     * @param blockScopeNode
-     */
-    public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.unicodeArray.length) {
-            return;
-        }
-
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), 1);
-    }
-
-    /**
-     * @returns {INode}
-     */
-    public getNode (): INode {
-        return super.getNode();
-    }
-
-    /**
-     * @returns {INode}
-     */
-    protected getNodeStructure (): INode {
-        const forLoopFunctionName: string = 'forLoopFunc';
-
-        let code: string;
-
-        if (this.options.selfDefending) {
-            code = SelfDefendingTemplate().formatUnicorn({
-                forLoopFunctionName,
-                unicodeArrayName: this.unicodeArrayName
-            });
-        } else {
-            code = `${forLoopFunctionName}();`;
-        }
-
-        return NodeUtils.convertCodeToStructure(
-            JavaScriptObfuscator.obfuscate(
-                UnicodeArrayDecodeTemplate().formatUnicorn({
-                    atobPolyfill: AtobTemplate(),
-                    code,
-                    forLoopFunctionName,
-                    unicodeArrayName: this.unicodeArrayName
-                }),
-                NO_CUSTOM_NODES_PRESET
-            ).getObfuscatedCode()
-        );
-    }
-}

+ 26 - 21
src/custom-nodes/unicode-array-nodes/UnicodeArrayNode.ts

@@ -1,19 +1,21 @@
 import 'format-unicorn';
 
-import { INode } from '../../interfaces/nodes/INode';
-import { IOptions } from "../../interfaces/IOptions";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
 
 import { AppendState } from '../../enums/AppendState';
 
-import { UnicodeArrayTemplate } from "../../templates/custom-nodes/unicode-array-nodes/unicode-array-node/UnicodeArrayTemplate";
+import { UnicodeArray } from '../../UnicodeArray';
 
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
-import { Utils } from '../../Utils';
+import { UnicodeArrayTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-node/UnicodeArrayTemplate';
 
-export class UnicodeArrayNode extends Node {
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+
+export class UnicodeArrayNode extends AbstractCustomNode {
     /**
      * @type {number}
      */
@@ -25,9 +27,9 @@ export class UnicodeArrayNode extends Node {
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {string[]}
+     * @type {UnicodeArray}
      */
-    private unicodeArray: string[] = [];
+    private unicodeArray: UnicodeArray;
 
     /**
      * @type {string}
@@ -40,17 +42,20 @@ export class UnicodeArrayNode extends Node {
     private unicodeArrayRotateValue: number;
 
     /**
+     * @param unicodeArray
      * @param unicodeArrayName
      * @param unicodeArrayRotateValue
      * @param options
      */
     constructor (
+        unicodeArray: UnicodeArray,
         unicodeArrayName: string,
         unicodeArrayRotateValue: number = 0,
         options: IOptions
     ) {
         super(options);
 
+        this.unicodeArray = unicodeArray;
         this.unicodeArrayName = unicodeArrayName;
         this.unicodeArrayRotateValue = unicodeArrayRotateValue;
     }
@@ -59,11 +64,11 @@ export class UnicodeArrayNode extends Node {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.unicodeArray.length) {
+        if (!this.unicodeArray.getLength()) {
             return;
         }
 
-        NodeUtils.prependNode(blockScopeNode.body, this.getNode());
+        NodeAppender.prependNode(blockScopeNode, this.getNode());
     }
 
     /**
@@ -74,17 +79,17 @@ export class UnicodeArrayNode extends Node {
     }
 
     /**
-     * @returns {string[]}
+     * @returns {UnicodeArray}
      */
-    public getNodeData (): string[] {
+    public getNodeData (): UnicodeArray {
         return this.unicodeArray;
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    public getNode (): INode {
-        Utils.arrayRotate <string> (this.unicodeArray, this.unicodeArrayRotateValue);
+    public getNode (): TStatement[] {
+        this.unicodeArray.rotateArray(this.unicodeArrayRotateValue);
 
         return super.getNode();
     }
@@ -93,17 +98,17 @@ export class UnicodeArrayNode extends Node {
      * @param data
      */
     public updateNodeData (data: string): void {
-        this.unicodeArray.push(data);
+        this.unicodeArray.addToArray(data);
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         return NodeUtils.convertCodeToStructure(
             UnicodeArrayTemplate().formatUnicorn({
                 unicodeArrayName: this.unicodeArrayName,
-                unicodeArray: this.unicodeArray.join(',')
+                unicodeArray: this.unicodeArray.toString()
             })
         );
     }

+ 23 - 21
src/custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode.ts

@@ -1,32 +1,34 @@
 import 'format-unicorn';
 
-import { INode } from "../../interfaces/nodes/INode";
-import { IOptions } from "../../interfaces/IOptions";
+import { TNodeWithBlockStatement } from '../../types/TNodeWithBlockStatement';
+import { TStatement } from '../../types/TStatement';
 
-import { TNodeWithBlockStatement } from "../../types/TNodeWithBlockStatement";
+import { IOptions } from '../../interfaces/IOptions';
 
-import { AppendState } from "../../enums/AppendState";
+import { AppendState } from '../../enums/AppendState';
 
-import { NO_CUSTOM_NODES_PRESET } from "../../preset-options/NoCustomNodesPreset";
+import { NO_CUSTOM_NODES_PRESET } from '../../preset-options/NoCustomNodesPreset';
 
-import { SelfDefendingTemplate } from "../../templates/custom-nodes/unicode-array-nodes/unicode-array-rotate-function-node/SelfDefendingTemplate";
-import { UnicodeArrayRotateFunctionTemplate } from "../../templates/custom-nodes/unicode-array-nodes/unicode-array-rotate-function-node/UnicodeArrayRotateFunctionTemplate";
+import { SelfDefendingTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-rotate-function-node/SelfDefendingTemplate';
+import { UnicodeArrayRotateFunctionTemplate } from '../../templates/custom-nodes/unicode-array-nodes/unicode-array-rotate-function-node/UnicodeArrayRotateFunctionTemplate';
 
-import { JavaScriptObfuscator } from "../../JavaScriptObfuscator";
-import { Node } from '../Node';
-import { NodeUtils } from "../../NodeUtils";
-import { Utils } from "../../Utils";
+import { AbstractCustomNode } from '../AbstractCustomNode';
+import { JavaScriptObfuscator } from '../../JavaScriptObfuscator';
+import { NodeAppender } from '../../NodeAppender';
+import { NodeUtils } from '../../NodeUtils';
+import { UnicodeArray } from '../../UnicodeArray';
+import { Utils } from '../../Utils';
 
-export class UnicodeArrayRotateFunctionNode extends Node {
+export class UnicodeArrayRotateFunctionNode extends AbstractCustomNode {
     /**
      * @type {AppendState}
      */
     protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {string[]}
+     * @type {UnicodeArray}
      */
-    private unicodeArray: string[];
+    private unicodeArray: UnicodeArray;
 
     /**
      * @type {string}
@@ -46,7 +48,7 @@ export class UnicodeArrayRotateFunctionNode extends Node {
      */
     constructor (
         unicodeArrayName: string,
-        unicodeArray: string[],
+        unicodeArray: UnicodeArray,
         unicodeArrayRotateValue: number,
         options: IOptions
     ) {
@@ -61,24 +63,24 @@ export class UnicodeArrayRotateFunctionNode extends Node {
      * @param blockScopeNode
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
-        if (!this.unicodeArray.length) {
+        if (!this.unicodeArray.getLength()) {
             return;
         }
 
-        NodeUtils.insertNodeAtIndex(blockScopeNode.body, this.getNode(), 1);
+        NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), 1);
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    public getNode (): INode {
+    public getNode (): TStatement[] {
         return super.getNode();
     }
 
     /**
-     * @returns {INode}
+     * @returns {TStatement[]}
      */
-    protected getNodeStructure (): INode {
+    protected getNodeStructure (): TStatement[] {
         let code: string = '',
             timesName: string = Utils.getRandomVariableName(),
             whileFunctionName: string = Utils.getRandomVariableName();

+ 18 - 0
src/declarations/ESTree.d.ts

@@ -0,0 +1,18 @@
+/* tslint:disable:interface-name */
+
+import * as ESTree from 'estree';
+
+declare module 'estree' {
+    interface BaseNode {
+        parentNode?: ESTree.Node;
+        obfuscated?: boolean;
+    }
+
+    interface SimpleLiteral extends ESTree.BaseNode, ESTree.BaseExpression {
+        'x-verbatim-property'?: any;
+    }
+
+    interface RegExpLiteral extends ESTree.BaseNode, ESTree.BaseExpression {
+        'x-verbatim-property'?: any;
+    }
+}

+ 1 - 1
src/enums/NodeType.ts

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

+ 4 - 0
src/enums/UnicodeArrayEncoding.ts

@@ -0,0 +1,4 @@
+export const UnicodeArrayEncoding: any = {
+    base64: 'base64',
+    rc4: 'rc4'
+};

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

@@ -1,4 +1,4 @@
-import { IGeneratorOutput } from "./IGeneratorOutput";
+import { IGeneratorOutput } from './IGeneratorOutput';
 
 declare module 'escodegen' {
     export function generate(ast: any, options?: GenerateOptions): IGeneratorOutput;

+ 0 - 0
src/interfaces/IGeneratorOutput.ts → src/interfaces/IGeneratorOutput.d.ts


+ 2 - 2
src/interfaces/INodeObfuscator.d.ts

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

+ 2 - 2
src/interfaces/INodesGroup.d.ts

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

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

@@ -1,5 +1,5 @@
-import { INode } from "./nodes/INode";
+import * as ESTree from 'estree';
 
 export interface IObfuscator {
-    obfuscateNode (node: INode): INode;
+    obfuscateNode (node: ESTree.Node): ESTree.Node;
 }

+ 6 - 3
src/interfaces/IObfuscatorOptions.d.ts

@@ -1,18 +1,21 @@
-import { TSourceMapMode } from "../types/TSourceMapMode";
+import { TSourceMapMode } from '../types/TSourceMapMode';
+import { TUnicodeArrayEncoding } from '../types/TUnicodeArrayEncoding';
 
 export interface IObfuscatorOptions {
     compact?: boolean;
     debugProtection?: boolean;
     debugProtectionInterval?: boolean;
     disableConsoleOutput?: boolean;
-    encodeUnicodeLiterals?: boolean;
+    domainLock?: string[];
     reservedNames?: string[];
     rotateUnicodeArray?: boolean;
     selfDefending?: boolean;
     sourceMap?: boolean;
+    sourceMapBaseUrl?: string;
+    sourceMapFileName?: string;
     sourceMapMode?: TSourceMapMode;
     unicodeArray?: boolean;
+    unicodeArrayEncoding?: TUnicodeArrayEncoding;
     unicodeArrayThreshold?: number;
-    wrapUnicodeArrayCalls?: boolean;
     [key: string]: any;
 }

+ 6 - 3
src/interfaces/IOptions.d.ts

@@ -1,17 +1,20 @@
-import { TSourceMapMode } from "../types/TSourceMapMode";
+import { TSourceMapMode } from '../types/TSourceMapMode';
+import { TUnicodeArrayEncoding } from '../types/TUnicodeArrayEncoding';
 
 export interface IOptions {
     readonly compact: boolean;
     readonly debugProtection: boolean;
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
-    readonly encodeUnicodeLiterals: boolean;
+    readonly domainLock: string[];
     readonly reservedNames: string[];
     readonly rotateUnicodeArray: boolean;
     readonly selfDefending: boolean;
     readonly sourceMap: boolean;
+    readonly sourceMapBaseUrl: string;
+    readonly sourceMapFileName: string;
     readonly sourceMapMode: TSourceMapMode;
     readonly unicodeArray: boolean;
+    readonly unicodeArrayEncoding: TUnicodeArrayEncoding;
     readonly unicodeArrayThreshold: number;
-    readonly wrapUnicodeArrayCalls: boolean;
 }

+ 0 - 0
src/interfaces/IPackageConfig.ts → src/interfaces/IPackageConfig.d.ts


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

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

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

@@ -1,4 +1,4 @@
-import { IObfuscationResult } from "./IObfuscationResult";
+import { IObfuscationResult } from './IObfuscationResult';
 
 export interface ISourceMapCorrector {
     correct (): IObfuscationResult;

+ 11 - 4
src/interfaces/custom-nodes/ICustomNode.d.ts

@@ -1,4 +1,6 @@
-import { INode } from '../nodes/INode';
+import * as ESTree from 'estree';
+
+import { TStatement } from '../../types/TStatement';
 
 import { AppendState } from '../../enums/AppendState';
 
@@ -6,7 +8,7 @@ export interface ICustomNode {
     /**
      * @param astTree
      */
-    appendNode (astTree: INode): void;
+    appendNode (astTree: ESTree.Node): void;
 
     /**
      * @returns {AppendState}
@@ -14,7 +16,12 @@ export interface ICustomNode {
     getAppendState (): AppendState;
 
     /**
-     * @returns INode
+     * @returns ESTree.Node[]
+     */
+    getNode (): TStatement[];
+
+    /**
+     * @param appendState
      */
-    getNode (): INode;
+    setAppendState (appendState: AppendState): void;
 }

+ 1 - 1
src/interfaces/custom-nodes/ICustomNodeWithData.d.ts

@@ -1,4 +1,4 @@
-import { ICustomNode } from "./ICustomNode";
+import { ICustomNode } from './ICustomNode';
 
 export interface ICustomNodeWithData extends ICustomNode {
     /**

+ 1 - 1
src/interfaces/custom-nodes/ICustomNodeWithIdentifier.d.ts

@@ -1,4 +1,4 @@
-import { ICustomNode } from "./ICustomNode";
+import { ICustomNode } from './ICustomNode';
 
 export interface ICustomNodeWithIdentifier extends ICustomNode {
     /**

+ 0 - 3
src/interfaces/nodes/IArrowFunctionExpressionNode.d.ts

@@ -1,3 +0,0 @@
-import { IFunctionNode } from "./IFunctionNode";
-
-export interface IArrowFunctionExpressionNode extends IFunctionNode {}

+ 0 - 7
src/interfaces/nodes/IBlockStatementNode.d.ts

@@ -1,7 +0,0 @@
-import { TStatement } from "../../types/nodes/TStatement";
-
-import { INode } from "./INode";
-
-export interface IBlockStatementNode extends INode {
-    body: TStatement[];
-}

+ 0 - 9
src/interfaces/nodes/ICallExpressionNode.d.ts

@@ -1,9 +0,0 @@
-import { TExpression } from "../../types/nodes/TExpression";
-
-import { INode } from "./INode";
-import { ISpreadElementNode } from "./ISpreadElementNode";
-
-export interface ICallExpressionNode extends INode {
-    callee: TExpression;
-    arguments: TExpression[] | ISpreadElementNode[];
-}

+ 0 - 8
src/interfaces/nodes/ICatchClauseNode.d.ts

@@ -1,8 +0,0 @@
-import { IBlockStatementNode } from "./IBlockStatementNode";
-import { IIdentifierNode } from "./IIdentifierNode";
-import { INode } from "./INode";
-
-export interface ICatchClauseNode extends INode {
-    param: IIdentifierNode;
-    body: IBlockStatementNode;
-}

+ 0 - 7
src/interfaces/nodes/IExpressionStatementNode.d.ts

@@ -1,7 +0,0 @@
-import { TExpression } from "../../types/nodes/TExpression";
-
-import { INode } from "./INode";
-
-export interface IExpressionStatementNode extends INode {
-    expression: TExpression;
-}

+ 0 - 3
src/interfaces/nodes/IFunctionDeclarationNode.d.ts

@@ -1,3 +0,0 @@
-import { IFunctionNode } from "./IFunctionNode";
-
-export interface IFunctionDeclarationNode extends IFunctionNode {}

+ 0 - 3
src/interfaces/nodes/IFunctionExpressionNode.d.ts

@@ -1,3 +0,0 @@
-import { IFunctionNode } from "./IFunctionNode";
-
-export interface IFunctionExpressionNode extends IFunctionNode {}

+ 0 - 14
src/interfaces/nodes/IFunctionNode.d.ts

@@ -1,14 +0,0 @@
-import { TExpression } from "../../types/nodes/TExpression";
-
-import { IBlockStatementNode } from "./IBlockStatementNode";
-import { IIdentifierNode } from "./IIdentifierNode";
-import { INode } from "./INode";
-
-export interface IFunctionNode extends INode {
-    id: IIdentifierNode;
-    params: IIdentifierNode[];
-    defaults?: TExpression[];
-    body: IBlockStatementNode;
-    generator: boolean;
-    expression: boolean;
-}

+ 0 - 5
src/interfaces/nodes/IIdentifierNode.d.ts

@@ -1,5 +0,0 @@
-import { INode } from "./INode";
-
-export interface IIdentifierNode extends INode {
-    name: string;
-}

+ 0 - 8
src/interfaces/nodes/IIfStatementNode.ts

@@ -1,8 +0,0 @@
-import { IBlockStatementNode } from "./IBlockStatementNode";
-import { INode } from "./INode";
-
-export interface IIfStatementNode extends INode {
-    test: any;
-    consequent: IBlockStatementNode;
-    alternate: IBlockStatementNode | null;
-}

+ 0 - 7
src/interfaces/nodes/ILiteralNode.d.ts

@@ -1,7 +0,0 @@
-import { INode } from "./INode";
-
-export interface ILiteralNode extends INode {
-    value: boolean|number|string;
-    raw: string;
-    'x-verbatim-property'?: any;
-}

+ 0 - 9
src/interfaces/nodes/IMemberExpressionNode.d.ts

@@ -1,9 +0,0 @@
-import { IIdentifierNode } from "./IIdentifierNode";
-import { ILiteralNode } from "./ILiteralNode";
-import { INode } from "./INode";
-
-export interface IMemberExpressionNode extends INode {
-    computed: boolean;
-    object: IIdentifierNode;
-    property: IIdentifierNode|ILiteralNode;
-}

+ 0 - 12
src/interfaces/nodes/IMethodDefinitionNode.d.ts

@@ -1,12 +0,0 @@
-import { IFunctionExpressionNode } from "./IFunctionExpressionNode";
-import { IIdentifierNode } from "./IIdentifierNode";
-import { ILiteralNode } from "./ILiteralNode";
-import { INode } from "./INode";
-
-export interface IMethodDefinitionNode extends INode {
-    key: IIdentifierNode|ILiteralNode;
-    computed: boolean;
-    value: IFunctionExpressionNode;
-    kind: string;
-    static: boolean;
-}

+ 0 - 5
src/interfaces/nodes/INode.d.ts

@@ -1,5 +0,0 @@
-export interface INode {
-    type: string;
-    parentNode?: INode;
-    obfuscated?: boolean;
-}

+ 0 - 6
src/interfaces/nodes/IObjectExpressionNode.d.ts

@@ -1,6 +0,0 @@
-import { IPropertyNode } from "./IPropertyNode";
-import { INode } from "./INode";
-
-export interface IObjectExpressionNode extends INode {
-    properties: IPropertyNode[];
-}

+ 0 - 8
src/interfaces/nodes/IProgramNode.d.ts

@@ -1,8 +0,0 @@
-import { TStatement } from "../../types/nodes/TStatement";
-
-import { INode } from "./INode";
-
-export interface IProgramNode extends INode {
-    body: TStatement[];
-    sourceType: string;
-}

+ 0 - 12
src/interfaces/nodes/IPropertyNode.d.ts

@@ -1,12 +0,0 @@
-import { IIdentifierNode } from "./IIdentifierNode";
-import { ILiteralNode } from "./ILiteralNode";
-import { INode } from "./INode";
-
-export interface IPropertyNode extends INode {
-    key: IIdentifierNode|ILiteralNode;
-    computed: boolean;
-    value: IIdentifierNode|ILiteralNode;
-    kind: string;
-    method: boolean;
-    shorthand: boolean;
-}

+ 0 - 7
src/interfaces/nodes/ISpreadElementNode.d.ts

@@ -1,7 +0,0 @@
-import { TExpression } from "../../types/nodes/TExpression";
-
-import { INode } from "./INode";
-
-export interface ISpreadElementNode extends INode {
-    argument: TExpression;
-}

+ 0 - 7
src/interfaces/nodes/IVariableDeclarationNode.d.ts

@@ -1,7 +0,0 @@
-import { INode } from "./INode";
-import { IVariableDeclaratorNode } from "./IVariableDeclaratorNode";
-
-export interface IVariableDeclarationNode extends INode {
-    declarations: IVariableDeclaratorNode[];
-    kind: string;
-}

+ 0 - 7
src/interfaces/nodes/IVariableDeclaratorNode.d.ts

@@ -1,7 +0,0 @@
-import { IIdentifierNode } from "./IIdentifierNode";
-import { INode } from "./INode";
-
-export interface IVariableDeclaratorNode extends INode {
-    id: IIdentifierNode;
-    init: any;
-}

+ 6 - 0
src/interfaces/stack-trace-analyzer/ICalleeData.d.ts

@@ -0,0 +1,6 @@
+import * as ESTree from 'estree';
+
+export interface ICalleeData {
+    callee: ESTree.BlockStatement;
+    name: string | null;
+}

+ 5 - 0
src/interfaces/stack-trace-analyzer/ICalleeDataExtractor.d.ts

@@ -0,0 +1,5 @@
+import { ICalleeData } from './ICalleeData';
+
+export interface ICalleeDataExtractor {
+    extract (): ICalleeData|null;
+}

+ 5 - 0
src/interfaces/stack-trace-analyzer/IStackTraceAnalyzer.d.ts

@@ -0,0 +1,5 @@
+import { IStackTraceData } from './IStackTraceData';
+
+export interface IStackTraceAnalyzer {
+    analyze (): IStackTraceData[];
+}

+ 5 - 0
src/interfaces/stack-trace-analyzer/IStackTraceData.d.ts

@@ -0,0 +1,5 @@
+import { ICalleeData } from './ICalleeData';
+
+export interface IStackTraceData extends ICalleeData {
+    stackTrace: IStackTraceData[];
+}

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

@@ -0,0 +1,49 @@
+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;
+    }
+}

+ 33 - 12
src/node-groups/ConsoleOutputNodesGroup.ts

@@ -1,22 +1,43 @@
-import { IOptions } from "../interfaces/IOptions";
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 
-import { ConsoleOutputDisableExpressionNode } from "../custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode";
-import { NodesGroup } from './NodesGroup';
+import { ConsoleOutputDisableExpressionNode } from '../custom-nodes/console-output-nodes/ConsoleOutputDisableExpressionNode';
+import { NodeCallsControllerFunctionNode } from '../custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode';
 
-export class ConsoleOutputNodesGroup extends NodesGroup {
+import { AbstractNodesGroup } from './AbstractNodesGroup';
+import { NodeAppender } from '../NodeAppender';
+import { Utils } from '../Utils';
+
+export class ConsoleOutputNodesGroup extends AbstractNodesGroup {
     /**
-     * @param options
+     * @returns {Map<string, ICustomNode>}
      */
-    constructor (options: IOptions) {
-        super(options);
-
+    public getNodes (): Map <string, ICustomNode> | undefined {
         if (!this.options.disableConsoleOutput) {
             return;
         }
 
-        this.nodes.set(
-            'consoleOutputDisableExpressionNode',
-            new ConsoleOutputDisableExpressionNode(this.options)
-        );
+        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
+                )
+            ]
+        ]));
     }
 }

+ 23 - 25
src/node-groups/DebugProtectionNodesGroup.ts

@@ -1,42 +1,40 @@
-import { IOptions } from "../interfaces/IOptions";
+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 { 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 { NodesGroup } from './NodesGroup';
+import { AbstractNodesGroup } from './AbstractNodesGroup';
 import { Utils } from '../Utils';
 
-export class DebugProtectionNodesGroup extends NodesGroup {
+export class DebugProtectionNodesGroup extends AbstractNodesGroup {
     /**
-     * @type {string}
+     * @returns {Map<string, ICustomNode> | undefined}
      */
-    private debugProtectionFunctionIdentifier: string = Utils.getRandomVariableName();
-
-    /**
-     * @param options
-     */
-    constructor (options: IOptions) {
-        super(options);
-
+    public getNodes (): Map <string, ICustomNode> | undefined {
         if (!this.options.debugProtection) {
             return;
         }
 
-        this.nodes.set(
-            'debugProtectionFunctionNode',
-            new DebugProtectionFunctionNode(this.debugProtectionFunctionIdentifier, this.options)
-        );
-        this.nodes.set(
-            'debugProtectionFunctionCallNode',
-            new DebugProtectionFunctionCallNode(this.debugProtectionFunctionIdentifier, this.options)
-        );
+        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) {
-            this.nodes.set(
+            customNodes.set(
                 'debugProtectionFunctionIntervalNode',
-                new DebugProtectionFunctionIntervalNode(this.debugProtectionFunctionIdentifier, this.options)
+                new DebugProtectionFunctionIntervalNode(debugProtectionFunctionName, this.options)
             );
         }
+
+        return this.syncCustomNodesWithNodesGroup(customNodes);
     }
 }

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

@@ -0,0 +1,43 @@
+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 '../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 - 27
src/node-groups/NodesGroup.ts

@@ -1,27 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-
-import { INodesGroup } from '../interfaces/INodesGroup';
-import { IOptions } from "../interfaces/IOptions";
-
-export abstract class NodesGroup implements INodesGroup {
-    /**
-     * @type {Map<string, Node>}
-     */
-    protected nodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ();
-
-    /**
-     * @type {IOptions}
-     */
-    protected options: IOptions;
-
-    constructor (options: IOptions) {
-        this.options = options;
-    }
-
-    /**
-     * @returns {Map<string, INode>}
-     */
-    public getNodes (): Map <string, ICustomNode> {
-        return this.nodes;
-    }
-}

+ 39 - 11
src/node-groups/SelfDefendingNodesGroup.ts

@@ -1,22 +1,50 @@
-import { IOptions } from "../interfaces/IOptions";
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 
-import { NodesGroup } from './NodesGroup';
-import { SelfDefendingUnicodeNode } from "../custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode";
+import { AppendState } from '../enums/AppendState';
 
-export class SelfDefendingNodesGroup extends NodesGroup {
+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 '../NodeAppender';
+import { Utils } from '../Utils';
+
+export class SelfDefendingNodesGroup extends AbstractNodesGroup {
     /**
-     * @param options
+     * @type {AppendState}
      */
-    constructor (options: IOptions) {
-        super(options);
+    protected appendState: AppendState = AppendState.AfterObfuscation;
 
+    /**
+     * @returns {Map<string, ICustomNode> | undefined}
+     */
+    public getNodes (): Map <string, ICustomNode> | undefined {
         if (!this.options.selfDefending) {
             return;
         }
 
-        this.nodes.set(
-            'selfDefendingUnicodeNode',
-            new SelfDefendingUnicodeNode(this.options)
-        );
+        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
+                )
+            ]
+        ]));
     }
 }

+ 36 - 41
src/node-groups/UnicodeArrayNodesGroup.ts

@@ -1,34 +1,40 @@
-import { IOptions } from "../interfaces/IOptions";
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
 
-import { NodesGroup } from './NodesGroup';
-import { UnicodeArrayCallsWrapper } from "../custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper";
-import { UnicodeArrayDecodeNode } from "../custom-nodes/unicode-array-nodes/UnicodeArrayDecodeNode";
+import { AppendState } from '../enums/AppendState';
+
+import { UnicodeArrayCallsWrapper } from '../custom-nodes/unicode-array-nodes/UnicodeArrayCallsWrapper';
 import { UnicodeArrayNode } from '../custom-nodes/unicode-array-nodes/UnicodeArrayNode';
 import { UnicodeArrayRotateFunctionNode } from '../custom-nodes/unicode-array-nodes/UnicodeArrayRotateFunctionNode';
+
+import { AbstractNodesGroup } from './AbstractNodesGroup';
+import { UnicodeArray } from '../UnicodeArray';
 import { Utils } from '../Utils';
 
-export class UnicodeArrayNodesGroup extends NodesGroup {
+export class UnicodeArrayNodesGroup extends AbstractNodesGroup {
     /**
-     * @type {string}
+     * @type {AppendState}
      */
-    private unicodeArrayName: string = Utils.getRandomVariableName(UnicodeArrayNode.UNICODE_ARRAY_RANDOM_LENGTH);
+    protected appendState: AppendState = AppendState.AfterObfuscation;
 
     /**
-     * @type {number}
+     * @type {string}
      */
-    private unicodeArrayRotateValue: number;
+    private unicodeArrayName: string = Utils.getRandomVariableName(UnicodeArrayNode.UNICODE_ARRAY_RANDOM_LENGTH);
 
     /**
      * @type {string}
      */
-    private unicodeArrayTranslatorName: string = Utils.getRandomVariableName(UnicodeArrayNode.UNICODE_ARRAY_RANDOM_LENGTH);
+    private unicodeArrayCallsWrapper: string = Utils.getRandomVariableName(UnicodeArrayNode.UNICODE_ARRAY_RANDOM_LENGTH);
 
     /**
-     * @param options
+     * @type {number}
      */
-    constructor (options: IOptions) {
-        super(options);
+    private unicodeArrayRotateValue: number;
 
+    /**
+     * @returns {Map<string, ICustomNode> | undefined}
+     */
+    public getNodes (): Map <string, ICustomNode> | undefined {
         if (!this.options.unicodeArray) {
             return;
         }
@@ -42,43 +48,30 @@ export class UnicodeArrayNodesGroup extends NodesGroup {
             this.unicodeArrayRotateValue = 0;
         }
 
-        let unicodeArrayNode: UnicodeArrayNode = new UnicodeArrayNode(
-                this.unicodeArrayName,
-                this.unicodeArrayRotateValue,
-                this.options
-            ),
-            unicodeArray: string [] = unicodeArrayNode.getNodeData();
-
-        this.nodes.set(
-            'unicodeArrayNode',
-            unicodeArrayNode
+        const unicodeArray: UnicodeArray = new UnicodeArray();
+        const unicodeArrayNode: ICustomNode = new UnicodeArrayNode(
+            unicodeArray,
+            this.unicodeArrayName,
+            this.unicodeArrayRotateValue,
+            this.options
         );
-
-        if (this.options.wrapUnicodeArrayCalls) {
-            this.nodes.set(
+        const customNodes: Map <string, ICustomNode> = new Map <string, ICustomNode> ([
+            [
+                'unicodeArrayNode', unicodeArrayNode,
+            ],
+            [
                 'unicodeArrayCallsWrapper',
                 new UnicodeArrayCallsWrapper(
-                    this.unicodeArrayTranslatorName,
-                    this.unicodeArrayName,
-                    unicodeArray,
-                    this.options
-                )
-            );
-        }
-
-        if (this.options.encodeUnicodeLiterals) {
-            this.nodes.set(
-                'unicodeArrayDecodeNode',
-                new UnicodeArrayDecodeNode (
+                    this.unicodeArrayCallsWrapper,
                     this.unicodeArrayName,
                     unicodeArray,
                     this.options
                 )
-            );
-        }
+            ]
+        ]);
 
         if (this.options.rotateUnicodeArray) {
-            this.nodes.set(
+            customNodes.set(
                 'unicodeArrayRotateFunctionNode',
                 new UnicodeArrayRotateFunctionNode(
                     this.unicodeArrayName,
@@ -88,5 +81,7 @@ export class UnicodeArrayNodesGroup extends NodesGroup {
                 )
             );
         }
+
+        return this.syncCustomNodesWithNodesGroup(customNodes);
     }
 }

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

@@ -0,0 +1,32 @@
+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;
+}

+ 31 - 13
src/node-obfuscators/CatchClauseObfuscator.ts

@@ -1,9 +1,15 @@
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { ICatchClauseNode } from "../interfaces/nodes/ICatchClauseNode";
-import { INode } from '../interfaces/nodes/INode';
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../interfaces/IOptions';
 
-import { NodeObfuscator } from './NodeObfuscator';
+import { NodeType } from '../enums/NodeType';
+
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Nodes } from '../Nodes';
+import { NodeUtils } from '../NodeUtils';
 
 /**
  * replaces:
@@ -13,16 +19,26 @@ import { NodeObfuscator } from './NodeObfuscator';
  *     try {} catch (_0x12d45f) { console.log(_0x12d45f); };
  *
  */
-export class CatchClauseObfuscator extends NodeObfuscator {
+export class CatchClauseObfuscator extends AbstractNodeObfuscator {
+    /**
+     * @type {IdentifierReplacer}
+     */
+    private identifierReplacer: IdentifierReplacer;
+
     /**
-     * @type {Map<string, string>}
+     * @param nodes
+     * @param options
      */
-    private catchClauseParam: Map <string, string> = new Map <string, string> ();
+    constructor(nodes: Map <string, ICustomNode>, options: IOptions) {
+        super(nodes, options);
+
+        this.identifierReplacer = new IdentifierReplacer(this.nodes, this.options);
+    }
 
     /**
      * @param catchClauseNode
      */
-    public obfuscateNode (catchClauseNode: ICatchClauseNode): void {
+    public obfuscateNode (catchClauseNode: ESTree.CatchClause): void {
         this.storeCatchClauseParam(catchClauseNode);
         this.replaceCatchClauseParam(catchClauseNode);
     }
@@ -30,19 +46,21 @@ export class CatchClauseObfuscator extends NodeObfuscator {
     /**
      * @param catchClauseNode
      */
-    private storeCatchClauseParam (catchClauseNode: ICatchClauseNode): void {
-        estraverse.traverse(catchClauseNode.param, {
-            enter: (node: INode): any => this.storeIdentifiersNames(node, this.catchClauseParam)
+    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: ICatchClauseNode): void {
+    private replaceCatchClauseParam (catchClauseNode: ESTree.CatchClause): void {
         estraverse.replace(catchClauseNode, {
-            enter: (node: INode, parentNode: INode): any => {
-                this.replaceIdentifiersWithRandomNames(node, parentNode, this.catchClauseParam);
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Nodes.isReplaceableIdentifierNode(node, parentNode)) {
+                    node.name = this.identifierReplacer.replace(node.name);
+                }
             }
         });
     }

+ 31 - 16
src/node-obfuscators/FunctionDeclarationObfuscator.ts

@@ -1,12 +1,15 @@
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { IFunctionDeclarationNode } from "../interfaces/nodes/IFunctionDeclarationNode";
-import { INode } from "../interfaces/nodes/INode";
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../interfaces/IOptions';
 
-import { NodeType } from "../enums/NodeType";
+import { NodeType } from '../enums/NodeType';
 
-import { NodeObfuscator } from './NodeObfuscator';
-import { NodeUtils } from "../NodeUtils";
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Nodes } from '../Nodes';
+import { NodeUtils } from '../NodeUtils';
 
 /**
  * replaces:
@@ -17,17 +20,27 @@ import { NodeUtils } from "../NodeUtils";
  *     function _0x12d45f () { //... };
  *     _0x12d45f();
  */
-export class FunctionDeclarationObfuscator extends NodeObfuscator {
+export class FunctionDeclarationObfuscator extends AbstractNodeObfuscator {
     /**
-     * @type {Map<string, string>}
+     * @type {IdentifierReplacer}
      */
-    private functionName: Map <string, string> = new Map <string, string> ();
+    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: IFunctionDeclarationNode, parentNode: INode): void {
+    public obfuscateNode (functionDeclarationNode: ESTree.FunctionDeclaration, parentNode: ESTree.Node): void {
         if (parentNode.type === NodeType.Program) {
             return;
         }
@@ -39,23 +52,25 @@ export class FunctionDeclarationObfuscator extends NodeObfuscator {
     /**
      * @param functionDeclarationNode
      */
-    private storeFunctionName (functionDeclarationNode: IFunctionDeclarationNode): void {
-        estraverse.traverse(functionDeclarationNode.id, {
-            enter: (node: INode): any => this.storeIdentifiersNames(node, this.functionName)
+    private storeFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration): void {
+        NodeUtils.typedReplace(functionDeclarationNode.id, NodeType.Identifier, {
+            enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
         });
     }
 
     /**
      * @param functionDeclarationNode
      */
-    private replaceFunctionName (functionDeclarationNode: IFunctionDeclarationNode): void {
-        let scopeNode: INode = NodeUtils.getBlockScopeOfNode(
+    private replaceFunctionName (functionDeclarationNode: ESTree.FunctionDeclaration): void {
+        let scopeNode: ESTree.Node = NodeUtils.getBlockScopeOfNode(
             functionDeclarationNode
         );
 
         estraverse.replace(scopeNode, {
-            enter: (node: INode, parentNode: INode): any => {
-                this.replaceIdentifiersWithRandomNames(node, parentNode, this.functionName);
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Nodes.isReplaceableIdentifierNode(node, parentNode)) {
+                    node.name = this.identifierReplacer.replace(node.name);
+                }
             }
         });
     }

+ 33 - 23
src/node-obfuscators/FunctionObfuscator.ts

@@ -1,10 +1,15 @@
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { IFunctionNode } from "../interfaces/nodes/IFunctionNode";
-import { INode } from "../interfaces/nodes/INode";
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../interfaces/IOptions';
 
-import { NodeObfuscator } from './NodeObfuscator';
-import { Nodes } from "../Nodes";
+import { NodeType } from '../enums/NodeType';
+
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Nodes } from '../Nodes';
+import { NodeUtils } from '../NodeUtils';
 
 /**
  * replaces:
@@ -14,16 +19,26 @@ import { Nodes } from "../Nodes";
  *     function foo (_0x12d45f) { return _0x12d45f; };
  *
  */
-export class FunctionObfuscator extends NodeObfuscator {
+export class FunctionObfuscator extends AbstractNodeObfuscator {
     /**
-     * @type {Map<string, string>}
+     * @type {IdentifierReplacer}
      */
-    private functionParams: Map <string, string> = new Map <string, string> ();
+    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: IFunctionNode): void {
+    public obfuscateNode (functionNode: ESTree.Function): void {
         this.storeFunctionParams(functionNode);
         this.replaceFunctionParams(functionNode);
     }
@@ -31,11 +46,11 @@ export class FunctionObfuscator extends NodeObfuscator {
     /**
      * @param functionNode
      */
-    private storeFunctionParams (functionNode: IFunctionNode): void {
+    private storeFunctionParams (functionNode: ESTree.Function): void {
         functionNode.params
-            .forEach((paramsNode: INode) => {
-                estraverse.traverse(paramsNode, {
-                    enter: (node: INode): any => this.storeIdentifiersNames(node, this.functionParams)
+            .forEach((paramsNode: ESTree.Node) => {
+                NodeUtils.typedReplace(paramsNode, NodeType.Identifier, {
+                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
                 });
             });
     }
@@ -43,19 +58,14 @@ export class FunctionObfuscator extends NodeObfuscator {
     /**
      * @param functionNode
      */
-    private replaceFunctionParams (functionNode: IFunctionNode): void {
+    private replaceFunctionParams (functionNode: ESTree.Function): void {
         let replaceVisitor: estraverse.Visitor = {
-            enter: (node: INode, parentNode: INode): any => {
-                let newNodeName: string = '';
-
-                if (Nodes.isIdentifierNode(node)) {
-                    newNodeName = node.name;
-                }
-
-                this.replaceIdentifiersWithRandomNames(node, parentNode, this.functionParams);
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Nodes.isReplaceableIdentifierNode(node, parentNode)) {
+                    const newNodeName: string = this.identifierReplacer.replace(node.name);
 
-                if (Nodes.isIdentifierNode(node)) {
                     if (node.name !== newNodeName) {
+                        node.name = newNodeName;
                         node.obfuscated = true;
                     }
                 }
@@ -63,7 +73,7 @@ export class FunctionObfuscator extends NodeObfuscator {
         };
 
         functionNode.params
-            .forEach((paramsNode: INode) => {
+            .forEach((paramsNode: ESTree.Node) => {
                 estraverse.replace(paramsNode, replaceVisitor);
             });
 

+ 14 - 15
src/node-obfuscators/LiteralObfuscator.ts

@@ -1,41 +1,40 @@
 import * as escodegen from 'escodegen';
+import * as ESTree from 'estree';
 
-import { ILiteralNode } from "../interfaces/nodes/ILiteralNode";
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { BooleanLiteralReplacer } from './replacers/BooleanLiteralReplacer';
+import { Nodes } from '../Nodes';
+import { NumberLiteralReplacer } from './replacers/NumberLiteralReplacer';
+import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
 
-import { INode } from "../interfaces/nodes/INode";
-import { NodeObfuscator } from './NodeObfuscator';
-import { Nodes } from "../Nodes";
-
-export class LiteralObfuscator extends NodeObfuscator {
+export class LiteralObfuscator extends AbstractNodeObfuscator {
     /**
      * @param literalNode
      * @param parentNode
      */
-    public obfuscateNode (literalNode: ILiteralNode, parentNode: INode): void {
+    public obfuscateNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void {
         if (Nodes.isPropertyNode(parentNode) && parentNode.key === literalNode) {
             return;
         }
 
-        if (literalNode['x-verbatim-property']) {
-            return;
-        }
-
         let content: string;
 
         switch (typeof literalNode.value) {
             case 'boolean':
-                content = this.replaceLiteralBooleanWithJSFuck(<boolean>literalNode.value);
+                content = new BooleanLiteralReplacer(this.nodes, this.options)
+                    .replace(<boolean>literalNode.value);
 
                 break;
 
             case 'number':
-                content = this.replaceLiteralNumberWithHexadecimalValue(<number>literalNode.value);
+                content = new NumberLiteralReplacer(this.nodes, this.options)
+                    .replace(<number>literalNode.value);
 
                 break;
 
-
             case 'string':
-                content = this.replaceLiteralValueWithUnicodeValue(<string>literalNode.value);
+                content = new StringLiteralReplacer(this.nodes, this.options)
+                        .replace(<string>literalNode.value);
 
                 break;
 

+ 13 - 16
src/node-obfuscators/MemberExpressionObfuscator.ts

@@ -1,23 +1,20 @@
 import * as escodegen from 'escodegen';
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { IIdentifierNode } from "../interfaces/nodes/IIdentifierNode";
-import { ILiteralNode } from "../interfaces/nodes/ILiteralNode";
-import { IMemberExpressionNode } from "../interfaces/nodes/IMemberExpressionNode";
-import { INode } from "../interfaces/nodes/INode";
+import { NodeType } from '../enums/NodeType';
 
-import { NodeType } from "../enums/NodeType";
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { Nodes } from '../Nodes';
+import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
 
-import { NodeObfuscator } from './NodeObfuscator';
-import { Nodes } from "../Nodes";
-
-export class MemberExpressionObfuscator extends NodeObfuscator {
+export class MemberExpressionObfuscator extends AbstractNodeObfuscator {
     /**
      * @param memberExpressionNode
      */
-    public obfuscateNode (memberExpressionNode: IMemberExpressionNode): void {
+    public obfuscateNode (memberExpressionNode: ESTree.MemberExpression): void {
         estraverse.replace(memberExpressionNode.property, {
-            enter: (node: INode, parentNode: INode): any => {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                 if (Nodes.isLiteralNode(node)) {
                     this.obfuscateLiteralProperty(node);
 
@@ -48,12 +45,12 @@ export class MemberExpressionObfuscator extends NodeObfuscator {
      *
      * @param node
      */
-    private obfuscateIdentifierProperty (node: IIdentifierNode): void {
+    private obfuscateIdentifierProperty (node: ESTree.Identifier): void {
         let nodeValue: string = node.name,
-            literalNode: ILiteralNode = {
+            literalNode: ESTree.Literal = {
                 raw: `'${nodeValue}'`,
                 'x-verbatim-property': {
-                    content : this.replaceLiteralValueWithUnicodeValue(nodeValue),
+                    content : new StringLiteralReplacer(this.nodes, this.options).replace(nodeValue),
                     precedence: escodegen.Precedence.Primary
                 },
                 type: NodeType.Literal,
@@ -74,10 +71,10 @@ export class MemberExpressionObfuscator extends NodeObfuscator {
      *
      * @param node
      */
-    private obfuscateLiteralProperty (node: ILiteralNode): void {
+    private obfuscateLiteralProperty (node: ESTree.Literal): void {
         if (typeof node.value === 'string' && !node['x-verbatim-property']) {
             node['x-verbatim-property'] = {
-                content : this.replaceLiteralValueWithUnicodeValue(node.value),
+                content : new StringLiteralReplacer(this.nodes, this.options).replace(node.value),
                 precedence: escodegen.Precedence.Primary
             };
         }

+ 11 - 11
src/node-obfuscators/MethodDefinitionObfuscator.ts

@@ -1,11 +1,10 @@
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { IMethodDefinitionNode } from "../interfaces/nodes/IMethodDefinitionNode";
-import { INode } from "../interfaces/nodes/INode";
-
-import { NodeObfuscator } from './NodeObfuscator';
-import { Nodes } from "../Nodes";
-import { Utils } from "../Utils";
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { Nodes } from '../Nodes';
+import { Utils } from '../Utils';
+import { StringLiteralReplacer } from './replacers/StringLiteralReplacer';
 
 /**
  * replaces:
@@ -14,7 +13,7 @@ import { Utils } from "../Utils";
  * on:
  *     [_0x9a4e('0x0')] { //... };
  */
-export class MethodDefinitionObfuscator extends NodeObfuscator {
+export class MethodDefinitionObfuscator extends AbstractNodeObfuscator {
     /**
      * @type {string[]}
      */
@@ -24,23 +23,24 @@ export class MethodDefinitionObfuscator extends NodeObfuscator {
      * @param methodDefinitionNode
      * @param parentNode
      */
-    public obfuscateNode (methodDefinitionNode: IMethodDefinitionNode, parentNode: INode): void {
+    public obfuscateNode (methodDefinitionNode: ESTree.MethodDefinition, parentNode: ESTree.Node): void {
         this.replaceMethodName(methodDefinitionNode);
     }
 
     /**
      * @param methodDefinitionNode
      */
-    private replaceMethodName (methodDefinitionNode: IMethodDefinitionNode): void {
+    private replaceMethodName (methodDefinitionNode: ESTree.MethodDefinition): void {
         estraverse.replace(methodDefinitionNode.key, {
-            leave: (node: INode): any => {
+            enter: (node: ESTree.Node): any => {
                 if (
                     Nodes.isIdentifierNode(node) &&
                     !Utils.arrayContains(this.ignoredNames, node.name) &&
                     methodDefinitionNode.computed === false
                 ) {
                     methodDefinitionNode.computed = true;
-                    node.name = this.replaceLiteralValueWithUnicodeValue(node.name);
+                    node.name = new StringLiteralReplacer(this.nodes, this.options)
+                        .replace(node.name);
 
                     return;
                 }

+ 0 - 175
src/node-obfuscators/NodeObfuscator.ts

@@ -1,175 +0,0 @@
-import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
-import { INodeObfuscator } from '../interfaces/INodeObfuscator';
-import { INode } from "../interfaces/nodes/INode";
-import { IOptions } from "../interfaces/IOptions";
-
-import { TUnicodeArrayCallsWrapper } from "../types/custom-nodes/TUnicodeArrayCallsWrapper";
-import { TUnicodeArrayNode } from "../types/custom-nodes/TUnicodeArrayNode";
-
-import { JSFuck } from "../enums/JSFuck";
-
-import { Nodes } from "../Nodes";
-import { Utils } from '../Utils';
-
-export abstract class NodeObfuscator implements INodeObfuscator {
-    /**
-     * @type Map <string, Node>
-     */
-    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: INode, parentNode?: INode): void;
-
-    /**
-     * @param name
-     * @returns {boolean}
-     */
-    protected isReservedName (name: string): boolean {
-        return this.options.reservedNames
-            .some((reservedName: string) => {
-                return new RegExp(reservedName, 'g').test(name);
-            });
-    }
-
-    /**
-     * Store all identifiers names as keys in given `namesMap` with random names as value.
-     * Reserved names will be ignored.
-     *
-     * @param node
-     * @param namesMap
-     */
-    protected storeIdentifiersNames (
-        node: INode,
-        namesMap: Map <string, string>
-    ): void {
-        if (Nodes.isIdentifierNode(node) && !this.isReservedName(node.name)) {
-            namesMap.set(node.name, Utils.getRandomVariableName());
-        }
-    }
-
-    /**
-     * @param node
-     * @param parentNode
-     * @param namesMap
-     */
-    protected replaceIdentifiersWithRandomNames (
-        node: INode,
-        parentNode: INode,
-        namesMap: Map <string, string>
-    ): void {
-        if (Nodes.isIdentifierNode(node) && namesMap.has(node.name)) {
-            const parentNodeIsPropertyNode: boolean = (
-                    Nodes.isPropertyNode(parentNode) &&
-                    parentNode.key === node
-                ),
-                parentNodeIsMemberExpressionNode: boolean = (
-                    Nodes.isMemberExpressionNode(parentNode) &&
-                    parentNode.computed === false &&
-                    parentNode.property === node
-                );
-
-            if (parentNodeIsPropertyNode || parentNodeIsMemberExpressionNode) {
-                return;
-            }
-
-            node.name = <string>namesMap.get(node.name);
-        }
-    }
-
-    /**
-     * @param nodeValue
-     * @returns {string}
-     */
-    protected replaceLiteralBooleanWithJSFuck (nodeValue: boolean): string {
-        return nodeValue ? JSFuck.True : JSFuck.False;
-    }
-
-    /**
-     * @param nodeValue
-     * @returns {string}
-     */
-    protected replaceLiteralNumberWithHexadecimalValue (nodeValue: number): string {
-        const prefix: string = '0x';
-
-        if (!Utils.isInteger(nodeValue)) {
-            return String(nodeValue);
-        }
-
-        return `${prefix}${Utils.decToHex(nodeValue)}`;
-    }
-
-    /**
-     * @param nodeValue
-     * @returns {string}
-     */
-    protected replaceLiteralValueWithUnicodeValue (nodeValue: string): string {
-        let replaceWithUnicodeArrayFlag: boolean = Math.random() <= this.options.unicodeArrayThreshold;
-
-        if (this.options.encodeUnicodeLiterals && replaceWithUnicodeArrayFlag) {
-            nodeValue = Utils.btoa(nodeValue);
-        }
-
-        nodeValue = Utils.stringToUnicode(nodeValue);
-
-        if (this.options.unicodeArray && replaceWithUnicodeArrayFlag) {
-            return this.replaceLiteralValueWithUnicodeArrayCall(nodeValue);
-        }
-
-        return nodeValue;
-    }
-
-    /**
-     * @param value
-     * @returns {string}
-     */
-    protected replaceLiteralValueWithUnicodeArrayCall (value: string): string {
-        let unicodeArrayNode: TUnicodeArrayNode = <TUnicodeArrayNode>this.nodes.get('unicodeArrayNode');
-
-        if (!unicodeArrayNode) {
-            throw new ReferenceError('`unicodeArrayNode` node is not found in Map with custom nodes.');
-        }
-
-        let unicodeArray: string[] = unicodeArrayNode.getNodeData(),
-            valueIndex: number = unicodeArray.indexOf(value),
-            literalValueCallIndex: number,
-            hexadecimalIndex: string;
-
-        if (valueIndex >= 0) {
-            literalValueCallIndex = valueIndex;
-        } else {
-            literalValueCallIndex = unicodeArray.length;
-            unicodeArrayNode.updateNodeData(value);
-        }
-
-        hexadecimalIndex = this.replaceLiteralNumberWithHexadecimalValue(literalValueCallIndex);
-
-        if (this.options.wrapUnicodeArrayCalls) {
-            let unicodeArrayCallsWrapper: TUnicodeArrayCallsWrapper = <TUnicodeArrayCallsWrapper>this.nodes.get('unicodeArrayCallsWrapper');
-
-            if (!unicodeArrayCallsWrapper) {
-                throw new ReferenceError('`unicodeArrayCallsWrapper` node is not found in Map with custom nodes.');
-            }
-
-            return `${unicodeArrayCallsWrapper.getNodeIdentifier()}('${hexadecimalIndex}')`;
-        }
-
-        return `${unicodeArrayNode.getNodeIdentifier()}[${hexadecimalIndex}]`;
-    }
-}

+ 11 - 16
src/node-obfuscators/ObjectExpressionObfuscator.ts

@@ -1,16 +1,11 @@
 import * as escodegen from 'escodegen';
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { IIdentifierNode } from "../interfaces/nodes/IIdentifierNode";
-import { ILiteralNode } from "../interfaces/nodes/ILiteralNode";
-import { IObjectExpressionNode } from "../interfaces/nodes/IObjectExpressionNode";
-import { IPropertyNode } from "../interfaces/nodes/IPropertyNode";
-import { INode } from "../interfaces/nodes/INode";
+import { NodeType } from '../enums/NodeType';
 
-import { NodeType } from "../enums/NodeType";
-
-import { NodeObfuscator } from './NodeObfuscator';
-import { Nodes } from "../Nodes";
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { Nodes } from '../Nodes';
 import { Utils } from '../Utils';
 
 /**
@@ -23,19 +18,19 @@ import { Utils } from '../Utils';
  * on:
  *     var object = { '\u0050\u0053\u0045\u0055\u0044\u004f': 1 };
  */
-export class ObjectExpressionObfuscator extends NodeObfuscator {
+export class ObjectExpressionObfuscator extends AbstractNodeObfuscator {
     /**
      * @param objectExpressionNode
      */
-    public obfuscateNode (objectExpressionNode: IObjectExpressionNode): void {
+    public obfuscateNode (objectExpressionNode: ESTree.ObjectExpression): void {
         objectExpressionNode.properties
-            .forEach((property: IPropertyNode) => {
+            .forEach((property: ESTree.Property) => {
                 if (property.shorthand) {
                     property.shorthand = false;
                 }
 
                 estraverse.replace(property.key, {
-                    leave: (node: INode, parentNode: INode): any => {
+                    enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
                         if (Nodes.isLiteralNode(node)) {
                             this.obfuscateLiteralPropertyKey(node);
 
@@ -53,7 +48,7 @@ export class ObjectExpressionObfuscator extends NodeObfuscator {
     /**
      * @param node
      */
-    private obfuscateLiteralPropertyKey (node: ILiteralNode): void {
+    private obfuscateLiteralPropertyKey (node: ESTree.Literal): void {
         if (typeof node.value === 'string' && !node['x-verbatim-property']) {
             node['x-verbatim-property'] = {
                 content : Utils.stringToUnicode(node.value),
@@ -65,9 +60,9 @@ export class ObjectExpressionObfuscator extends NodeObfuscator {
     /**
      * @param node
      */
-    private obfuscateIdentifierPropertyKey (node: IIdentifierNode): void {
+    private obfuscateIdentifierPropertyKey (node: ESTree.Identifier): void {
         let nodeValue: string = node.name,
-            literalNode: ILiteralNode = {
+            literalNode: ESTree.Literal = {
                 raw: `'${nodeValue}'`,
                 'x-verbatim-property': {
                     content : Utils.stringToUnicode(nodeValue),

+ 31 - 19
src/node-obfuscators/VariableDeclarationObfuscator.ts

@@ -1,13 +1,15 @@
 import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
 
-import { INode } from "../interfaces/nodes/INode";
-import { IVariableDeclarationNode } from "../interfaces/nodes/IVariableDeclarationNode";
-import { IVariableDeclaratorNode } from "../interfaces/nodes/IVariableDeclaratorNode";
+import { ICustomNode } from '../interfaces/custom-nodes/ICustomNode';
+import { IOptions } from '../interfaces/IOptions';
 
-import { NodeType } from "../enums/NodeType";
+import { NodeType } from '../enums/NodeType';
 
-import { NodeObfuscator } from './NodeObfuscator';
-import { NodeUtils } from "../NodeUtils";
+import { AbstractNodeObfuscator } from './AbstractNodeObfuscator';
+import { IdentifierReplacer } from './replacers/IdentifierReplacer';
+import { Nodes } from '../Nodes';
+import { NodeUtils } from '../NodeUtils';
 
 /**
  * replaces:
@@ -19,17 +21,27 @@ import { NodeUtils } from "../NodeUtils";
  *     _0x12d45f++;
  *
  */
-export class VariableDeclarationObfuscator extends NodeObfuscator {
+export class VariableDeclarationObfuscator extends AbstractNodeObfuscator {
     /**
-     * @type {Map<string, string>}
+     * @type {IdentifierReplacer}
      */
-    private variableNames: Map <string, string> = new Map <string, string> ();
+    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 variableDeclarationNode
      * @param parentNode
      */
-    public obfuscateNode (variableDeclarationNode: IVariableDeclarationNode, parentNode: INode): void {
+    public obfuscateNode (variableDeclarationNode: ESTree.VariableDeclaration, parentNode: ESTree.Node): void {
         if (parentNode.type === NodeType.Program) {
             return;
         }
@@ -41,11 +53,11 @@ export class VariableDeclarationObfuscator extends NodeObfuscator {
     /**
      * @param variableDeclarationNode
      */
-    private storeVariableNames (variableDeclarationNode: IVariableDeclarationNode): void {
+    private storeVariableNames (variableDeclarationNode: ESTree.VariableDeclaration): void {
         variableDeclarationNode.declarations
-            .forEach((declarationNode: IVariableDeclaratorNode) => {
-                estraverse.traverse(declarationNode.id, {
-                    enter: (node: INode): any => this.storeIdentifiersNames(node, this.variableNames)
+            .forEach((declarationNode: ESTree.VariableDeclarator) => {
+                NodeUtils.typedReplace(declarationNode.id, NodeType.Identifier, {
+                    enter: (node: ESTree.Identifier) => this.identifierReplacer.storeNames(node.name)
                 });
             });
     }
@@ -54,15 +66,15 @@ export class VariableDeclarationObfuscator extends NodeObfuscator {
      * @param variableDeclarationNode
      * @param variableParentNode
      */
-    private replaceVariableNames (variableDeclarationNode: IVariableDeclarationNode, variableParentNode: INode): void {
-        let scopeNode: INode = variableDeclarationNode.kind === 'var' ? NodeUtils.getBlockScopeOfNode(
+    private replaceVariableNames (variableDeclarationNode: ESTree.VariableDeclaration, variableParentNode: ESTree.Node): void {
+        let scopeNode: ESTree.Node = variableDeclarationNode.kind === 'var' ? NodeUtils.getBlockScopeOfNode(
                 variableDeclarationNode
             ) : variableParentNode;
 
         estraverse.replace(scopeNode, {
-            enter: (node: INode, parentNode: INode): any => {
-                if (!node.obfuscated) {
-                    this.replaceIdentifiersWithRandomNames(node, parentNode, this.variableNames);
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (!node.obfuscated && Nodes.isReplaceableIdentifierNode(node, parentNode)) {
+                    node.name = this.identifierReplacer.replace(node.name);
                 }
             }
         });

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

@@ -0,0 +1,31 @@
+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;
+}

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