sanex преди 3 години
родител
ревизия
2144b34c88

+ 10 - 1
CHANGELOG.md

@@ -1,9 +1,18 @@
 Change Log
 
-v2.15.0
+v2.16.0
 ---
 * Added support of `es2022` features: private identifiers and class properties
 
+v2.15.1
+---
+* **Hotfix**: `domainDest` => `domainLockRedirectUrl` option rename
+
+v2.15.0
+---
+* Added `domainDest` option that option allows the browser to be redirected to a passed domain if the source code isn't run on the domains or URL specified by `domainLock`. Thank you https://github.com/erikdubbelboer!
+* `ObfuscationResult` object now contains `getOptions` method to get options that were used during obfuscation
+
 v2.14.0
 ---
 * Added `identifierNamesCache` option for reading and writing identifier names cache. See `README.md`.

+ 25 - 10
README.md

@@ -361,6 +361,7 @@ Following options are available for the JS Obfuscator:
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     forceTransformStrings: [],
     identifierNamesCache: null,
     identifierNamesGenerator: 'hexadecimal',
@@ -421,6 +422,7 @@ Following options are available for the JS Obfuscator:
     --debug-protection-interval <boolean>
     --disable-console-output <boolean>
     --domain-lock '<list>' (comma separated)
+    --domain-lock-redirect-url <string>
     --exclude '<list>' (comma separated)
     --force-transform-strings '<list>' (comma separated)
     --identifier-names-cache-path <string>
@@ -688,9 +690,18 @@ Type: `string[]` Default: `[]`
 
 Allows to run the obfuscated source code only on specific domains and/or sub-domains. This makes really hard for someone to just copy and paste your source code and run it elsewhere.
 
+If the source code isn't run on the domains specified by this option, the browser will be redirected to a passed to the [`domainLockRedirectUrl`](#domainlockredirecturl) option URL.
+
 ##### 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 the root domain including any sub-domains (`example.com`, `sub.example.com`), use `.example.com`.
 
+### `domainLockRedirectUrl`
+Type: `string` Default: `about:blank`
+
+##### :warning: This option does not work with `target: 'node'`
+
+Allows the browser to be redirected to a passed URL if the source code isn't run on the domains specified by [`domainLock`](#domainlock)
+
 ### `exclude`
 Type: `string[]` Default: `[]`
 
@@ -736,7 +747,7 @@ If an empty object (`{}`) is passed, enables the writing identifier names to the
 The resulting cache-object can be next used as `identifierNamesGenerator` option value for using these names during obfuscation of all matched identifier names of next sources.
 
 Example:
-```
+```ts
 const source1ObfuscationResult = JavaScriptObfuscator.obfuscate(
     `
         function foo(arg) {
@@ -755,12 +766,14 @@ const source1ObfuscationResult = JavaScriptObfuscator.obfuscate(
 )
 
 console.log(source1ObfuscationResult.getIdentifierNamesCache());
-/*{ 
-    globalIdentifiers: {
-        foo: '_0x5de86d',
-        bar: '_0x2a943b'
+/*
+    { 
+        globalIdentifiers: {
+            foo: '_0x5de86d',
+            bar: '_0x2a943b'
+        }
     }
-}*/
+*/
 
 
 
@@ -781,9 +794,11 @@ const source2ObfuscationResult = JavaScriptObfuscator.obfuscate(
 )
 
 console.log(source2ObfuscationResult.getObfuscatedCode());
-// _0x5de86d(0x1);
-// _0x2a943b();
-// baz();
+/*
+    _0x5de86d(0x1);
+    _0x2a943b();
+    baz();
+ */
 ```
 
 #### CLI
@@ -1600,7 +1615,7 @@ Become a sponsor and get your logo on our README on Github with a link to your s
 ## License
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjavascript-obfuscator%2Fjavascript-obfuscator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjavascript-obfuscator%2Fjavascript-obfuscator?ref=badge_large)
 
-Copyright (C) 2016-2020 [Timofey Kachalov](http://github.com/sanex3339).
+Copyright (C) 2016-2021 [Timofey Kachalov](http://github.com/sanex3339).
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/index.browser.js


+ 126 - 42
dist/index.cli.js

@@ -209,7 +209,7 @@ let JavaScriptObfuscator = JavaScriptObfuscator_1 = class JavaScriptObfuscator {
             sourceCode = '';
         }
         const timeStart = Date.now();
-        this.logger.info(LoggingMessage_1.LoggingMessage.Version, Utils_1.Utils.buildVersionMessage("2.15.0", 1623003625223));
+        this.logger.info(LoggingMessage_1.LoggingMessage.Version, Utils_1.Utils.buildVersionMessage("2.16.0", 1623404313493));
         this.logger.info(LoggingMessage_1.LoggingMessage.ObfuscationStarted);
         this.logger.info(LoggingMessage_1.LoggingMessage.RandomGeneratorSeed, this.randomGenerator.getInputSeed());
         sourceCode = this.runCodeTransformationStage(sourceCode, CodeTransformationStage_1.CodeTransformationStage.PreparingTransformers);
@@ -423,7 +423,7 @@ class JavaScriptObfuscatorFacade {
     }
 }
 exports.JavaScriptObfuscator = JavaScriptObfuscatorFacade;
-JavaScriptObfuscatorFacade.version = (_a = "2.15.0") !== null && _a !== void 0 ? _a : 'unknown';
+JavaScriptObfuscatorFacade.version = (_a = "2.16.0") !== null && _a !== void 0 ? _a : 'unknown';
 
 
 /***/ }),
@@ -1419,7 +1419,7 @@ class JavaScriptObfuscatorCLI {
     configureCommands() {
         this.commands
             .usage('<inputPath> [options]')
-            .version(Utils_1.Utils.buildVersionMessage("2.15.0", 1623003625223), '-v, --version')
+            .version(Utils_1.Utils.buildVersionMessage("2.16.0", 1623404313493), '-v, --version')
             .option('-o, --output <path>', 'Output path for obfuscated code')
             .option('--compact <boolean>', 'Disable one line output code compacting', BooleanSanitizer_1.BooleanSanitizer)
             .option('--config <boolean>', 'Name of js / json config file')
@@ -1431,6 +1431,7 @@ class JavaScriptObfuscatorCLI {
             .option('--debug-protection-interval <boolean>', 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)', BooleanSanitizer_1.BooleanSanitizer)
             .option('--disable-console-output <boolean>', 'Allow console.log, console.info, console.error and console.warn messages output into browser console', BooleanSanitizer_1.BooleanSanitizer)
             .option('--domain-lock <list> (comma separated, without whitespaces)', 'Allows to run the obfuscated source code only on specific domains and/or sub-domains (comma separated)', ArraySanitizer_1.ArraySanitizer)
+            .option('--domain-lock-redirect-url <string>', 'Allows the browser to be redirected to a passed URL if the source code isn\'t run on the domains specified by --domain-lock')
             .option('--exclude <list> (comma separated, without whitespaces)', 'A filename or glob which indicates files to exclude from obfuscation', ArraySanitizer_1.ArraySanitizer)
             .option('--force-transform-strings <list> (comma separated, without whitespaces)', 'Enables force transformation of string literals, which being matched by passed RegExp patterns (comma separated)', ArraySanitizer_1.ArraySanitizer)
             .option('--identifier-names-cache-path <string>', 'Sets path for identifier names cache')
@@ -4725,15 +4726,19 @@ let DomainLockCodeHelper = class DomainLockCodeHelper extends AbstractCustomCode
     }
     getCodeHelperTemplate() {
         const domainsString = this.options.domainLock.join(';');
-        const [hiddenDomainsString, diff] = this.cryptUtils.hideString(domainsString, domainsString.length * 3);
+        const domainsLockRedirectUrl = this.options.domainLockRedirectUrl;
+        const [hiddenDomainsString, domainsStringDiff] = this.cryptUtils.hideString(domainsString, domainsString.length * 3);
+        const [hiddenDomainLockRedirectUrl, domainLockRedirectUrlDiff] = this.cryptUtils.hideString(domainsLockRedirectUrl, domainsLockRedirectUrl.length * 3);
         const globalVariableTemplate = this.options.target !== ObfuscationTarget_1.ObfuscationTarget.BrowserNoEval
             ? this.getGlobalVariableTemplate()
             : GlobalVariableNoEvalTemplate_1.GlobalVariableNoEvalTemplate();
         return this.customCodeHelperFormatter.formatTemplate(DomainLockTemplate_1.DomainLockTemplate(), {
             callControllerFunctionName: this.callsControllerFunctionName,
             domainLockFunctionName: this.domainLockFunctionName,
-            diff,
+            domainsStringDiff,
             domains: hiddenDomainsString,
+            domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+            hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
             globalVariableTemplate
         });
     }
@@ -4874,34 +4879,43 @@ function DomainLockTemplate() {
             
             {globalVariableTemplate}
             
-            const func = function () {
-                return {
-                    key: 'item',
-                    value: 'attribute',
-                    getAttribute: function () {
-                        for (let i = 0; i < 1000; i--) {
-                            const isPositive = i > 0;
-                            
-                            switch (isPositive) {
-                                case true:
-                                    return this.item + '_' + this.value + '_' + i;
-                                default:
-                                    this.item + '_' + this.value;
-                            }
-                        }
-                    }()
-                };
-            };
-                        
-            const regExp = new RegExp("[{diff}]", "g");
+            const regExp = new RegExp("[{domainsStringDiff}]", "g");
             const domains = "{domains}".replace(regExp, "").split(";");
             let document;
             let domain;
             let location;
             let hostname;
 
+            const isName = function(name, length, cs) {
+                if (name.length != length) {
+                    return false;
+                }
+
+                for (let i = 0; i < length; i++) {
+                    for (let j = 0; j < cs.length; j += 2) {
+                        if (i == cs[j] && name.charCodeAt(i) != cs[j+1]) {
+                            return false;
+                        }
+                    }
+                }
+
+                return true;
+            };
+
+            const isNameVariant1 = function(cs, name, length) {
+              return isName(name, length, cs);
+            };
+
+            const isNameVariant2 = function(name, cs, length) {
+              return isNameVariant1(cs, name, length);
+            };
+
+            const isNameVariant3 = function(length, name, cs) {
+              return isNameVariant2(name, cs, length);
+            };
+
             for (let d in that) {
-                if (d.length == 8 && d.charCodeAt(7) == 116 && d.charCodeAt(5) == 101 && d.charCodeAt(3) == 117 && d.charCodeAt(0) == 100) {
+                if (isName(d, 8, [7, 116, 5, 101, 3, 117, 0, 100])) {
                     document = d;
                 
                     break;
@@ -4909,24 +4923,24 @@ function DomainLockTemplate() {
             }
 
             for (let d1 in that[document]) {
-                if (d1.length == 6 && d1.charCodeAt(5) == 110 && d1.charCodeAt(0) == 100) {
+                if (isNameVariant3(6, d1, [5, 110, 0, 100])) {
                     domain = d1;
-                    
+
                     break;
                 }
             }
 
-            if (!("~" > domain)) {
-                for (let d2 in that[document]) {
-                    if (d2.length == 8 && d2.charCodeAt(7) == 110 && d2.charCodeAt(0) == 108) {
-                        location = d2;
-                        
-                        break;
-                    }
+            for (let d2 in that[document]) {
+                if (isNameVariant2(d2, [7, 110, 0, 108], 8)) {
+                    location = d2;
+
+                    break;
                 }
+            }
 
+            if (!("~" > domain)) {
                 for (let d3 in that[document][location]) {
-                    if (d3.length == 8 && d3.charCodeAt(7) == 101 && d3.charCodeAt(0) == 104) {
+                    if (isNameVariant1([7, 101, 0, 104], d3, 8)) {
                         hostname = d3;
                         
                         break;
@@ -4963,14 +4977,13 @@ function DomainLockTemplate() {
                     }
                 }
             }
-               
+
             if (!ok) {
-                data;
-            } else {
-                return;
+                const regExp2 = new RegExp("[{domainLockRedirectUrlDiff}]", "g");
+                const domainLockRedirectUrl = "{hiddenDomainLockRedirectUrl}".replace(regExp2, "");
+
+                that[document][location] = domainLockRedirectUrl;
             }
-            
-            func();
         });
 
         {domainLockFunctionName}();
@@ -16223,6 +16236,7 @@ const MediumObfuscation_1 = __webpack_require__(/*! ./presets/MediumObfuscation
 const HighObfuscation_1 = __webpack_require__(/*! ./presets/HighObfuscation */ "./src/options/presets/HighObfuscation.ts");
 const ValidationErrorsFormatter_1 = __webpack_require__(/*! ./ValidationErrorsFormatter */ "./src/options/ValidationErrorsFormatter.ts");
 const IsAllowedForObfuscationTargets_1 = __webpack_require__(/*! ./validators/IsAllowedForObfuscationTargets */ "./src/options/validators/IsAllowedForObfuscationTargets.ts");
+const IsDomainLockRedirectUrl_1 = __webpack_require__(/*! ./validators/IsDomainLockRedirectUrl */ "./src/options/validators/IsDomainLockRedirectUrl.ts");
 const IsIdentifierNamesCache_1 = __webpack_require__(/*! ./validators/IsIdentifierNamesCache */ "./src/options/validators/IsIdentifierNamesCache.ts");
 let Options = Options_1 = class Options {
     constructor(inputOptions, optionsNormalizer) {
@@ -16302,6 +16316,10 @@ __decorate([
     ]),
     __metadata("design:type", Array)
 ], Options.prototype, "domainLock", void 0);
+__decorate([
+    IsDomainLockRedirectUrl_1.IsDomainLockRedirectUrl(),
+    __metadata("design:type", String)
+], Options.prototype, "domainLockRedirectUrl", void 0);
 __decorate([
     class_validator_1.IsArray(),
     class_validator_1.ArrayUnique(),
@@ -16527,6 +16545,7 @@ const inversify_1 = __webpack_require__(/*! inversify */ "inversify");
 const ControlFlowFlatteningThresholdRule_1 = __webpack_require__(/*! ./normalizer-rules/ControlFlowFlatteningThresholdRule */ "./src/options/normalizer-rules/ControlFlowFlatteningThresholdRule.ts");
 const DeadCodeInjectionRule_1 = __webpack_require__(/*! ./normalizer-rules/DeadCodeInjectionRule */ "./src/options/normalizer-rules/DeadCodeInjectionRule.ts");
 const DeadCodeInjectionThresholdRule_1 = __webpack_require__(/*! ./normalizer-rules/DeadCodeInjectionThresholdRule */ "./src/options/normalizer-rules/DeadCodeInjectionThresholdRule.ts");
+const DomainLockRedirectUrlRule_1 = __webpack_require__(/*! ./normalizer-rules/DomainLockRedirectUrlRule */ "./src/options/normalizer-rules/DomainLockRedirectUrlRule.ts");
 const DomainLockRule_1 = __webpack_require__(/*! ./normalizer-rules/DomainLockRule */ "./src/options/normalizer-rules/DomainLockRule.ts");
 const IdentifierNamesCacheRule_1 = __webpack_require__(/*! ./normalizer-rules/IdentifierNamesCacheRule */ "./src/options/normalizer-rules/IdentifierNamesCacheRule.ts");
 const InputFileNameRule_1 = __webpack_require__(/*! ./normalizer-rules/InputFileNameRule */ "./src/options/normalizer-rules/InputFileNameRule.ts");
@@ -16553,6 +16572,7 @@ OptionsNormalizer.normalizerRules = [
     ControlFlowFlatteningThresholdRule_1.ControlFlowFlatteningThresholdRule,
     DeadCodeInjectionRule_1.DeadCodeInjectionRule,
     DeadCodeInjectionThresholdRule_1.DeadCodeInjectionThresholdRule,
+    DomainLockRedirectUrlRule_1.DomainLockRedirectUrlRule,
     DomainLockRule_1.DomainLockRule,
     IdentifierNamesCacheRule_1.IdentifierNamesCacheRule,
     InputFileNameRule_1.InputFileNameRule,
@@ -16691,6 +16711,31 @@ const DeadCodeInjectionThresholdRule = (options) => {
 exports.DeadCodeInjectionThresholdRule = DeadCodeInjectionThresholdRule;
 
 
+/***/ }),
+
+/***/ "./src/options/normalizer-rules/DomainLockRedirectUrlRule.ts":
+/*!*******************************************************************!*\
+  !*** ./src/options/normalizer-rules/DomainLockRedirectUrlRule.ts ***!
+  \*******************************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.DomainLockRedirectUrlRule = void 0;
+const Default_1 = __webpack_require__(/*! ../presets/Default */ "./src/options/presets/Default.ts");
+const DomainLockRedirectUrlRule = (options) => {
+    if (!options.domainLock.length) {
+        options = {
+            ...options,
+            domainLockRedirectUrl: Default_1.DEFAULT_PRESET.domainLockRedirectUrl
+        };
+    }
+    return options;
+};
+exports.DomainLockRedirectUrlRule = DomainLockRedirectUrlRule;
+
+
 /***/ }),
 
 /***/ "./src/options/normalizer-rules/DomainLockRule.ts":
@@ -17057,6 +17102,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     exclude: [],
     forceTransformStrings: [],
     identifierNamesCache: null,
@@ -17223,6 +17269,7 @@ exports.NO_ADDITIONAL_NODES_PRESET = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     exclude: [],
     forceTransformStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator_1.IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
@@ -17312,6 +17359,40 @@ function IsAllowedForObfuscationTargets(obfuscationTargets, validationOptions) {
 exports.IsAllowedForObfuscationTargets = IsAllowedForObfuscationTargets;
 
 
+/***/ }),
+
+/***/ "./src/options/validators/IsDomainLockRedirectUrl.ts":
+/*!***********************************************************!*\
+  !*** ./src/options/validators/IsDomainLockRedirectUrl.ts ***!
+  \***********************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.IsDomainLockRedirectUrl = void 0;
+const class_validator_1 = __webpack_require__(/*! class-validator */ "class-validator");
+const ObfuscationTarget_1 = __webpack_require__(/*! ../../enums/ObfuscationTarget */ "./src/enums/ObfuscationTarget.ts");
+const Default_1 = __webpack_require__(/*! ../presets/Default */ "./src/options/presets/Default.ts");
+const IsAllowedForObfuscationTargets_1 = __webpack_require__(/*! ./IsAllowedForObfuscationTargets */ "./src/options/validators/IsAllowedForObfuscationTargets.ts");
+const IsDomainLockRedirectUrl = () => {
+    return (target, key) => {
+        class_validator_1.ValidateIf(({ domainLockRedirectUrl }) => {
+            return domainLockRedirectUrl !== Default_1.DEFAULT_PRESET.domainLockRedirectUrl;
+        })(target, key);
+        class_validator_1.IsUrl({
+            require_protocol: false,
+            require_host: false
+        })(target, key);
+        IsAllowedForObfuscationTargets_1.IsAllowedForObfuscationTargets([
+            ObfuscationTarget_1.ObfuscationTarget.Browser,
+            ObfuscationTarget_1.ObfuscationTarget.BrowserNoEval,
+        ])(target, key);
+    };
+};
+exports.IsDomainLockRedirectUrl = IsDomainLockRedirectUrl;
+
+
 /***/ }),
 
 /***/ "./src/options/validators/IsIdentifierNamesCache.ts":
@@ -17438,6 +17519,9 @@ let ObfuscationResult = class ObfuscationResult {
     getObfuscatedCode() {
         return this.correctObfuscatedCode();
     }
+    getOptions() {
+        return this.options;
+    }
     getSourceMap() {
         return this.sourceMap;
     }

+ 122 - 41
dist/index.js

@@ -207,7 +207,7 @@ let JavaScriptObfuscator = JavaScriptObfuscator_1 = class JavaScriptObfuscator {
             sourceCode = '';
         }
         const timeStart = Date.now();
-        this.logger.info(LoggingMessage_1.LoggingMessage.Version, Utils_1.Utils.buildVersionMessage("2.15.0", 1623003625223));
+        this.logger.info(LoggingMessage_1.LoggingMessage.Version, Utils_1.Utils.buildVersionMessage("2.16.0", 1623404313493));
         this.logger.info(LoggingMessage_1.LoggingMessage.ObfuscationStarted);
         this.logger.info(LoggingMessage_1.LoggingMessage.RandomGeneratorSeed, this.randomGenerator.getInputSeed());
         sourceCode = this.runCodeTransformationStage(sourceCode, CodeTransformationStage_1.CodeTransformationStage.PreparingTransformers);
@@ -396,7 +396,7 @@ class JavaScriptObfuscatorFacade {
     }
 }
 exports.JavaScriptObfuscator = JavaScriptObfuscatorFacade;
-JavaScriptObfuscatorFacade.version = (_a = "2.15.0") !== null && _a !== void 0 ? _a : 'unknown';
+JavaScriptObfuscatorFacade.version = (_a = "2.16.0") !== null && _a !== void 0 ? _a : 'unknown';
 
 
 /***/ }),
@@ -3939,15 +3939,19 @@ let DomainLockCodeHelper = class DomainLockCodeHelper extends AbstractCustomCode
     }
     getCodeHelperTemplate() {
         const domainsString = this.options.domainLock.join(';');
-        const [hiddenDomainsString, diff] = this.cryptUtils.hideString(domainsString, domainsString.length * 3);
+        const domainsLockRedirectUrl = this.options.domainLockRedirectUrl;
+        const [hiddenDomainsString, domainsStringDiff] = this.cryptUtils.hideString(domainsString, domainsString.length * 3);
+        const [hiddenDomainLockRedirectUrl, domainLockRedirectUrlDiff] = this.cryptUtils.hideString(domainsLockRedirectUrl, domainsLockRedirectUrl.length * 3);
         const globalVariableTemplate = this.options.target !== ObfuscationTarget_1.ObfuscationTarget.BrowserNoEval
             ? this.getGlobalVariableTemplate()
             : GlobalVariableNoEvalTemplate_1.GlobalVariableNoEvalTemplate();
         return this.customCodeHelperFormatter.formatTemplate(DomainLockTemplate_1.DomainLockTemplate(), {
             callControllerFunctionName: this.callsControllerFunctionName,
             domainLockFunctionName: this.domainLockFunctionName,
-            diff,
+            domainsStringDiff,
             domains: hiddenDomainsString,
+            domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+            hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
             globalVariableTemplate
         });
     }
@@ -4086,34 +4090,43 @@ function DomainLockTemplate() {
             
             {globalVariableTemplate}
             
-            const func = function () {
-                return {
-                    key: 'item',
-                    value: 'attribute',
-                    getAttribute: function () {
-                        for (let i = 0; i < 1000; i--) {
-                            const isPositive = i > 0;
-                            
-                            switch (isPositive) {
-                                case true:
-                                    return this.item + '_' + this.value + '_' + i;
-                                default:
-                                    this.item + '_' + this.value;
-                            }
-                        }
-                    }()
-                };
-            };
-                        
-            const regExp = new RegExp("[{diff}]", "g");
+            const regExp = new RegExp("[{domainsStringDiff}]", "g");
             const domains = "{domains}".replace(regExp, "").split(";");
             let document;
             let domain;
             let location;
             let hostname;
 
+            const isName = function(name, length, cs) {
+                if (name.length != length) {
+                    return false;
+                }
+
+                for (let i = 0; i < length; i++) {
+                    for (let j = 0; j < cs.length; j += 2) {
+                        if (i == cs[j] && name.charCodeAt(i) != cs[j+1]) {
+                            return false;
+                        }
+                    }
+                }
+
+                return true;
+            };
+
+            const isNameVariant1 = function(cs, name, length) {
+              return isName(name, length, cs);
+            };
+
+            const isNameVariant2 = function(name, cs, length) {
+              return isNameVariant1(cs, name, length);
+            };
+
+            const isNameVariant3 = function(length, name, cs) {
+              return isNameVariant2(name, cs, length);
+            };
+
             for (let d in that) {
-                if (d.length == 8 && d.charCodeAt(7) == 116 && d.charCodeAt(5) == 101 && d.charCodeAt(3) == 117 && d.charCodeAt(0) == 100) {
+                if (isName(d, 8, [7, 116, 5, 101, 3, 117, 0, 100])) {
                     document = d;
                 
                     break;
@@ -4121,24 +4134,24 @@ function DomainLockTemplate() {
             }
 
             for (let d1 in that[document]) {
-                if (d1.length == 6 && d1.charCodeAt(5) == 110 && d1.charCodeAt(0) == 100) {
+                if (isNameVariant3(6, d1, [5, 110, 0, 100])) {
                     domain = d1;
-                    
+
                     break;
                 }
             }
 
-            if (!("~" > domain)) {
-                for (let d2 in that[document]) {
-                    if (d2.length == 8 && d2.charCodeAt(7) == 110 && d2.charCodeAt(0) == 108) {
-                        location = d2;
-                        
-                        break;
-                    }
+            for (let d2 in that[document]) {
+                if (isNameVariant2(d2, [7, 110, 0, 108], 8)) {
+                    location = d2;
+
+                    break;
                 }
+            }
 
+            if (!("~" > domain)) {
                 for (let d3 in that[document][location]) {
-                    if (d3.length == 8 && d3.charCodeAt(7) == 101 && d3.charCodeAt(0) == 104) {
+                    if (isNameVariant1([7, 101, 0, 104], d3, 8)) {
                         hostname = d3;
                         
                         break;
@@ -4175,14 +4188,13 @@ function DomainLockTemplate() {
                     }
                 }
             }
-               
+
             if (!ok) {
-                data;
-            } else {
-                return;
+                const regExp2 = new RegExp("[{domainLockRedirectUrlDiff}]", "g");
+                const domainLockRedirectUrl = "{hiddenDomainLockRedirectUrl}".replace(regExp2, "");
+
+                that[document][location] = domainLockRedirectUrl;
             }
-            
-            func();
         });
 
         {domainLockFunctionName}();
@@ -15265,6 +15277,7 @@ const MediumObfuscation_1 = __webpack_require__(/*! ./presets/MediumObfuscation
 const HighObfuscation_1 = __webpack_require__(/*! ./presets/HighObfuscation */ "./src/options/presets/HighObfuscation.ts");
 const ValidationErrorsFormatter_1 = __webpack_require__(/*! ./ValidationErrorsFormatter */ "./src/options/ValidationErrorsFormatter.ts");
 const IsAllowedForObfuscationTargets_1 = __webpack_require__(/*! ./validators/IsAllowedForObfuscationTargets */ "./src/options/validators/IsAllowedForObfuscationTargets.ts");
+const IsDomainLockRedirectUrl_1 = __webpack_require__(/*! ./validators/IsDomainLockRedirectUrl */ "./src/options/validators/IsDomainLockRedirectUrl.ts");
 const IsIdentifierNamesCache_1 = __webpack_require__(/*! ./validators/IsIdentifierNamesCache */ "./src/options/validators/IsIdentifierNamesCache.ts");
 let Options = Options_1 = class Options {
     constructor(inputOptions, optionsNormalizer) {
@@ -15344,6 +15357,10 @@ __decorate([
     ]),
     __metadata("design:type", Array)
 ], Options.prototype, "domainLock", void 0);
+__decorate([
+    IsDomainLockRedirectUrl_1.IsDomainLockRedirectUrl(),
+    __metadata("design:type", String)
+], Options.prototype, "domainLockRedirectUrl", void 0);
 __decorate([
     class_validator_1.IsArray(),
     class_validator_1.ArrayUnique(),
@@ -15568,6 +15585,7 @@ const inversify_1 = __webpack_require__(/*! inversify */ "inversify");
 const ControlFlowFlatteningThresholdRule_1 = __webpack_require__(/*! ./normalizer-rules/ControlFlowFlatteningThresholdRule */ "./src/options/normalizer-rules/ControlFlowFlatteningThresholdRule.ts");
 const DeadCodeInjectionRule_1 = __webpack_require__(/*! ./normalizer-rules/DeadCodeInjectionRule */ "./src/options/normalizer-rules/DeadCodeInjectionRule.ts");
 const DeadCodeInjectionThresholdRule_1 = __webpack_require__(/*! ./normalizer-rules/DeadCodeInjectionThresholdRule */ "./src/options/normalizer-rules/DeadCodeInjectionThresholdRule.ts");
+const DomainLockRedirectUrlRule_1 = __webpack_require__(/*! ./normalizer-rules/DomainLockRedirectUrlRule */ "./src/options/normalizer-rules/DomainLockRedirectUrlRule.ts");
 const DomainLockRule_1 = __webpack_require__(/*! ./normalizer-rules/DomainLockRule */ "./src/options/normalizer-rules/DomainLockRule.ts");
 const IdentifierNamesCacheRule_1 = __webpack_require__(/*! ./normalizer-rules/IdentifierNamesCacheRule */ "./src/options/normalizer-rules/IdentifierNamesCacheRule.ts");
 const InputFileNameRule_1 = __webpack_require__(/*! ./normalizer-rules/InputFileNameRule */ "./src/options/normalizer-rules/InputFileNameRule.ts");
@@ -15594,6 +15612,7 @@ OptionsNormalizer.normalizerRules = [
     ControlFlowFlatteningThresholdRule_1.ControlFlowFlatteningThresholdRule,
     DeadCodeInjectionRule_1.DeadCodeInjectionRule,
     DeadCodeInjectionThresholdRule_1.DeadCodeInjectionThresholdRule,
+    DomainLockRedirectUrlRule_1.DomainLockRedirectUrlRule,
     DomainLockRule_1.DomainLockRule,
     IdentifierNamesCacheRule_1.IdentifierNamesCacheRule,
     InputFileNameRule_1.InputFileNameRule,
@@ -15728,6 +15747,30 @@ const DeadCodeInjectionThresholdRule = (options) => {
 exports.DeadCodeInjectionThresholdRule = DeadCodeInjectionThresholdRule;
 
 
+/***/ }),
+
+/***/ "./src/options/normalizer-rules/DomainLockRedirectUrlRule.ts":
+/*!*******************************************************************!*\
+  !*** ./src/options/normalizer-rules/DomainLockRedirectUrlRule.ts ***!
+  \*******************************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
+
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.DomainLockRedirectUrlRule = void 0;
+const Default_1 = __webpack_require__(/*! ../presets/Default */ "./src/options/presets/Default.ts");
+const DomainLockRedirectUrlRule = (options) => {
+    if (!options.domainLock.length) {
+        options = {
+            ...options,
+            domainLockRedirectUrl: Default_1.DEFAULT_PRESET.domainLockRedirectUrl
+        };
+    }
+    return options;
+};
+exports.DomainLockRedirectUrlRule = DomainLockRedirectUrlRule;
+
+
 /***/ }),
 
 /***/ "./src/options/normalizer-rules/DomainLockRule.ts":
@@ -16082,6 +16125,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     exclude: [],
     forceTransformStrings: [],
     identifierNamesCache: null,
@@ -16244,6 +16288,7 @@ exports.NO_ADDITIONAL_NODES_PRESET = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     exclude: [],
     forceTransformStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator_1.IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,
@@ -16332,6 +16377,39 @@ function IsAllowedForObfuscationTargets(obfuscationTargets, validationOptions) {
 exports.IsAllowedForObfuscationTargets = IsAllowedForObfuscationTargets;
 
 
+/***/ }),
+
+/***/ "./src/options/validators/IsDomainLockRedirectUrl.ts":
+/*!***********************************************************!*\
+  !*** ./src/options/validators/IsDomainLockRedirectUrl.ts ***!
+  \***********************************************************/
+/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
+
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.IsDomainLockRedirectUrl = void 0;
+const class_validator_1 = __webpack_require__(/*! class-validator */ "class-validator");
+const ObfuscationTarget_1 = __webpack_require__(/*! ../../enums/ObfuscationTarget */ "./src/enums/ObfuscationTarget.ts");
+const Default_1 = __webpack_require__(/*! ../presets/Default */ "./src/options/presets/Default.ts");
+const IsAllowedForObfuscationTargets_1 = __webpack_require__(/*! ./IsAllowedForObfuscationTargets */ "./src/options/validators/IsAllowedForObfuscationTargets.ts");
+const IsDomainLockRedirectUrl = () => {
+    return (target, key) => {
+        class_validator_1.ValidateIf(({ domainLockRedirectUrl }) => {
+            return domainLockRedirectUrl !== Default_1.DEFAULT_PRESET.domainLockRedirectUrl;
+        })(target, key);
+        class_validator_1.IsUrl({
+            require_protocol: false,
+            require_host: false
+        })(target, key);
+        IsAllowedForObfuscationTargets_1.IsAllowedForObfuscationTargets([
+            ObfuscationTarget_1.ObfuscationTarget.Browser,
+            ObfuscationTarget_1.ObfuscationTarget.BrowserNoEval,
+        ])(target, key);
+    };
+};
+exports.IsDomainLockRedirectUrl = IsDomainLockRedirectUrl;
+
+
 /***/ }),
 
 /***/ "./src/options/validators/IsIdentifierNamesCache.ts":
@@ -16456,6 +16534,9 @@ let ObfuscationResult = class ObfuscationResult {
     getObfuscatedCode() {
         return this.correctObfuscatedCode();
     }
+    getOptions() {
+        return this.options;
+    }
     getSourceMap() {
         return this.sourceMap;
     }

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.15.0",
+  "version": "2.16.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -57,7 +57,7 @@
     "@types/mkdirp": "1.0.1",
     "@types/mocha": "8.2.2",
     "@types/multimatch": "4.0.0",
-    "@types/node": "15.12.1",
+    "@types/node": "15.12.2",
     "@types/rimraf": "3.0.0",
     "@types/sinon": "10.0.2",
     "@types/string-template": "1.0.2",

+ 4 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -236,6 +236,10 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 'Allows to run the obfuscated source code only on specific domains and/or sub-domains (comma separated)',
                 ArraySanitizer
             )
+            .option(
+                '--domain-lock-redirect-url <string>',
+                'Allows the browser to be redirected to a passed URL if the source code isn\'t run on the domains specified by --domain-lock',
+            )
             .option(
                 '--exclude <list> (comma separated, without whitespaces)',
                 'A filename or glob which indicates files to exclude from obfuscation',

+ 9 - 2
src/custom-code-helpers/domain-lock/DomainLockCodeHelper.ts

@@ -89,10 +89,15 @@ export class DomainLockCodeHelper extends AbstractCustomCodeHelper {
      */
     protected override getCodeHelperTemplate (): string {
         const domainsString: string = this.options.domainLock.join(';');
-        const [hiddenDomainsString, diff]: string[] = this.cryptUtils.hideString(
+        const domainsLockRedirectUrl: string = this.options.domainLockRedirectUrl;
+        const [hiddenDomainsString, domainsStringDiff]: string[] = this.cryptUtils.hideString(
             domainsString,
             domainsString.length * 3
         );
+        const [hiddenDomainLockRedirectUrl, domainLockRedirectUrlDiff]: string[] = this.cryptUtils.hideString(
+            domainsLockRedirectUrl,
+            domainsLockRedirectUrl.length * 3
+        );
         const globalVariableTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval
             ? this.getGlobalVariableTemplate()
             : GlobalVariableNoEvalTemplate();
@@ -100,8 +105,10 @@ export class DomainLockCodeHelper extends AbstractCustomCodeHelper {
         return this.customCodeHelperFormatter.formatTemplate(DomainLockTemplate(), {
             callControllerFunctionName: this.callsControllerFunctionName,
             domainLockFunctionName: this.domainLockFunctionName,
-            diff,
+            domainsStringDiff,
             domains: hiddenDomainsString,
+            domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+            hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
             globalVariableTemplate
         });
     }

+ 45 - 37
src/custom-code-helpers/domain-lock/templates/DomainLockTemplate.ts

@@ -7,34 +7,43 @@ export function DomainLockTemplate (): string {
             
             {globalVariableTemplate}
             
-            const func = function () {
-                return {
-                    key: 'item',
-                    value: 'attribute',
-                    getAttribute: function () {
-                        for (let i = 0; i < 1000; i--) {
-                            const isPositive = i > 0;
-                            
-                            switch (isPositive) {
-                                case true:
-                                    return this.item + '_' + this.value + '_' + i;
-                                default:
-                                    this.item + '_' + this.value;
-                            }
-                        }
-                    }()
-                };
-            };
-                        
-            const regExp = new RegExp("[{diff}]", "g");
+            const regExp = new RegExp("[{domainsStringDiff}]", "g");
             const domains = "{domains}".replace(regExp, "").split(";");
             let document;
             let domain;
             let location;
             let hostname;
 
+            const isName = function(name, length, cs) {
+                if (name.length != length) {
+                    return false;
+                }
+
+                for (let i = 0; i < length; i++) {
+                    for (let j = 0; j < cs.length; j += 2) {
+                        if (i == cs[j] && name.charCodeAt(i) != cs[j+1]) {
+                            return false;
+                        }
+                    }
+                }
+
+                return true;
+            };
+
+            const isNameVariant1 = function(cs, name, length) {
+              return isName(name, length, cs);
+            };
+
+            const isNameVariant2 = function(name, cs, length) {
+              return isNameVariant1(cs, name, length);
+            };
+
+            const isNameVariant3 = function(length, name, cs) {
+              return isNameVariant2(name, cs, length);
+            };
+
             for (let d in that) {
-                if (d.length == 8 && d.charCodeAt(7) == 116 && d.charCodeAt(5) == 101 && d.charCodeAt(3) == 117 && d.charCodeAt(0) == 100) {
+                if (isName(d, 8, [7, 116, 5, 101, 3, 117, 0, 100])) {
                     document = d;
                 
                     break;
@@ -42,24 +51,24 @@ export function DomainLockTemplate (): string {
             }
 
             for (let d1 in that[document]) {
-                if (d1.length == 6 && d1.charCodeAt(5) == 110 && d1.charCodeAt(0) == 100) {
+                if (isNameVariant3(6, d1, [5, 110, 0, 100])) {
                     domain = d1;
-                    
+
                     break;
                 }
             }
 
-            if (!("~" > domain)) {
-                for (let d2 in that[document]) {
-                    if (d2.length == 8 && d2.charCodeAt(7) == 110 && d2.charCodeAt(0) == 108) {
-                        location = d2;
-                        
-                        break;
-                    }
+            for (let d2 in that[document]) {
+                if (isNameVariant2(d2, [7, 110, 0, 108], 8)) {
+                    location = d2;
+
+                    break;
                 }
+            }
 
+            if (!("~" > domain)) {
                 for (let d3 in that[document][location]) {
-                    if (d3.length == 8 && d3.charCodeAt(7) == 101 && d3.charCodeAt(0) == 104) {
+                    if (isNameVariant1([7, 101, 0, 104], d3, 8)) {
                         hostname = d3;
                         
                         break;
@@ -96,14 +105,13 @@ export function DomainLockTemplate (): string {
                     }
                 }
             }
-               
+
             if (!ok) {
-                data;
-            } else {
-                return;
+                const regExp2 = new RegExp("[{domainLockRedirectUrlDiff}]", "g");
+                const domainLockRedirectUrl = "{hiddenDomainLockRedirectUrl}".replace(regExp2, "");
+
+                that[document][location] = domainLockRedirectUrl;
             }
-            
-            func();
         });
 
         {domainLockFunctionName}();

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

@@ -20,6 +20,7 @@ export interface IOptions {
     readonly debugProtectionInterval: boolean;
     readonly disableConsoleOutput: boolean;
     readonly domainLock: string[];
+    readonly domainLockRedirectUrl: string;
     readonly forceTransformStrings: string[];
     readonly identifierNamesCache: TIdentifierNamesCache;
     readonly identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>;

+ 7 - 0
src/interfaces/source-code/IObfuscationResult.ts

@@ -1,5 +1,7 @@
 import { TIdentifierNamesCache } from '../../types/TIdentifierNamesCache';
+
 import { IInitializable } from '../IInitializable';
+import { IOptions } from '../options/IOptions';
 
 export interface IObfuscationResult extends IInitializable <[string, string]> {
     /**
@@ -12,6 +14,11 @@ export interface IObfuscationResult extends IInitializable <[string, string]> {
      */
     getObfuscatedCode (): string;
 
+    /**
+     * @return {IOptions}
+     */
+    getOptions (): IOptions;
+
     /**
      * @return {string}
      */

+ 7 - 0
src/options/Options.ts

@@ -46,6 +46,7 @@ import { HIGH_OBFUSCATION_PRESET } from './presets/HighObfuscation';
 
 import { ValidationErrorsFormatter } from './ValidationErrorsFormatter';
 import { IsAllowedForObfuscationTargets } from './validators/IsAllowedForObfuscationTargets';
+import { IsDomainLockRedirectUrl } from './validators/IsDomainLockRedirectUrl';
 import { IsIdentifierNamesCache } from './validators/IsIdentifierNamesCache';
 
 @injectable()
@@ -134,6 +135,12 @@ export class Options implements IOptions {
     ])
     public readonly domainLock!: string[];
 
+    /**
+     * @type {string}
+     */
+    @IsDomainLockRedirectUrl()
+    public readonly domainLockRedirectUrl!: string;
+
     /**
      * @type {string[]}
      */

+ 2 - 0
src/options/OptionsNormalizer.ts

@@ -8,6 +8,7 @@ import { IOptionsNormalizer } from '../interfaces/options/IOptionsNormalizer';
 import { ControlFlowFlatteningThresholdRule } from './normalizer-rules/ControlFlowFlatteningThresholdRule';
 import { DeadCodeInjectionRule } from './normalizer-rules/DeadCodeInjectionRule';
 import { DeadCodeInjectionThresholdRule } from './normalizer-rules/DeadCodeInjectionThresholdRule';
+import { DomainLockRedirectUrlRule } from './normalizer-rules/DomainLockRedirectUrlRule';
 import { DomainLockRule } from './normalizer-rules/DomainLockRule';
 import { IdentifierNamesCacheRule } from './normalizer-rules/IdentifierNamesCacheRule';
 import { InputFileNameRule } from './normalizer-rules/InputFileNameRule';
@@ -29,6 +30,7 @@ export class OptionsNormalizer implements IOptionsNormalizer {
         ControlFlowFlatteningThresholdRule,
         DeadCodeInjectionRule,
         DeadCodeInjectionThresholdRule,
+        DomainLockRedirectUrlRule,
         DomainLockRule,
         IdentifierNamesCacheRule,
         InputFileNameRule,

+ 20 - 0
src/options/normalizer-rules/DomainLockRedirectUrlRule.ts

@@ -0,0 +1,20 @@
+import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { DEFAULT_PRESET } from '../presets/Default';
+
+/**
+ * @param {IOptions} options
+ * @returns {IOptions}
+ */
+export const DomainLockRedirectUrlRule: TOptionsNormalizerRule = (options: IOptions): IOptions => {
+    if (!options.domainLock.length) {
+        options = {
+            ...options,
+            domainLockRedirectUrl: <string>DEFAULT_PRESET.domainLockRedirectUrl
+        };
+    }
+
+    return options;
+};

+ 1 - 0
src/options/presets/Default.ts

@@ -20,6 +20,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     exclude: [],
     forceTransformStrings: [],
     identifierNamesCache: null,

+ 1 - 0
src/options/presets/NoCustomNodes.ts

@@ -18,6 +18,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     debugProtectionInterval: false,
     disableConsoleOutput: false,
     domainLock: [],
+    domainLockRedirectUrl: 'about:blank',
     exclude: [],
     forceTransformStrings: [],
     identifierNamesGenerator: IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator,

+ 30 - 0
src/options/validators/IsDomainLockRedirectUrl.ts

@@ -0,0 +1,30 @@
+import { IsUrl, ValidateIf } from 'class-validator';
+
+import { TInputOptions } from '../../types/options/TInputOptions';
+
+import { IOptions } from '../../interfaces/options/IOptions';
+
+import { ObfuscationTarget } from '../../enums/ObfuscationTarget';
+
+import { DEFAULT_PRESET } from '../presets/Default';
+
+import { IsAllowedForObfuscationTargets } from './IsAllowedForObfuscationTargets';
+
+/**
+ * @returns {PropertyDecorator}
+ */
+export const IsDomainLockRedirectUrl = (): PropertyDecorator => {
+    return (target: any, key: string | symbol): void => {
+        ValidateIf(({domainLockRedirectUrl}: TInputOptions) => {
+            return domainLockRedirectUrl !== DEFAULT_PRESET.domainLockRedirectUrl;
+        })(target, key);
+        IsUrl({
+            require_protocol: false,
+            require_host: false
+        })(target, key);
+        IsAllowedForObfuscationTargets([
+            ObfuscationTarget.Browser,
+            ObfuscationTarget.BrowserNoEval,
+        ])(target, <keyof IOptions>key);
+    };
+};

+ 7 - 0
src/source-code/ObfuscationResult.ts

@@ -96,6 +96,13 @@ export class ObfuscationResult implements IObfuscationResult {
         return this.correctObfuscatedCode();
     }
 
+    /**
+     * @returns {IOptions}
+     */
+    public getOptions (): IOptions {
+        return this.options;
+    }
+
     /**
      * @returns {string}
      */

+ 424 - 170
test/functional-tests/custom-code-helpers/domain-lock/templates/DomainLockNodeTemplate.spec.ts

@@ -13,53 +13,11 @@ import { IObfuscationResult } from '../../../../../src/interfaces/source-code/IO
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
 import { DomainLockTemplate } from '../../../../../src/custom-code-helpers/domain-lock/templates/DomainLockTemplate';
-import { GlobalVariableTemplate1 } from '../../../../../src/custom-code-helpers/common/templates/GlobalVariableTemplate1';
 
 import { InversifyContainerFacade } from '../../../../../src/container/InversifyContainerFacade';
 import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
 import { readFileAsString } from '../../../../helpers/readFileAsString';
 
-/**
- * @param {string} currentDomain
- * @returns {string}
- */
-function getDocumentDomainTemplate (currentDomain: string): string {
-    return `
-        document = {
-            domain: '${currentDomain}'
-        };
-    `
-}
-
-/**
- * @param {string} currentDomain
- * @returns {string}
- */
-function getDocumentLocationTemplate (currentDomain: string): string {
-    return `
-        document = {
-            location: {
-                hostname: '${currentDomain}'
-            }
-        };
-    `
-}
-
-/**
- * @param {string} currentDomain
- * @returns {string}
- */
-function getDocumentDomainAndLocationTemplate (currentDomain: string): string {
-    return `
-        document = {
-            domain: '${currentDomain}',
-            location: {
-                hostname: '${currentDomain}'
-            }
-        };
-    `
-}
-
 /**
  * @param templateData
  * @param {string} callsControllerFunctionName
@@ -85,11 +43,12 @@ function getFunctionFromTemplate (
         })();
 
         ${domainLockTemplate}
-    `)();
+    `);
 }
 
 describe('DomainLockTemplate', () => {
     const singleCallControllerFunctionName: string = 'callsController';
+    const thatThisTemplate = 'const that = this;';
 
     let cryptUtils: ICryptUtils;
 
@@ -102,393 +61,614 @@ describe('DomainLockTemplate', () => {
 
     describe('Variant #1: current domain matches with `domainsString`', () => {
         const domainsString: string = ['www.example.com'].join(';');
+        const domainLockRedirectUrl: string = 'about:blank';
         const currentDomain: string = 'www.example.com';
 
-        let testFunc: () => void;
+        let testFunc: Function;
+        let root: any;
 
         before(() => {
             const [
                 hiddenDomainsString,
-                diff
+                domainsStringDiff
             ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-            testFunc = () => getFunctionFromTemplate(
+            const [
+                hiddenDomainLockRedirectUrl,
+                domainLockRedirectUrlDiff
+            ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+            root = {
+                document: {
+                    domain: currentDomain,
+                    location: undefined,
+                },
+            };
+
+            testFunc = getFunctionFromTemplate(
                 {
                     domainLockFunctionName: 'domainLockFunction',
-                    diff: diff,
+                    domainsStringDiff,
                     domains: hiddenDomainsString,
-                    globalVariableTemplate: GlobalVariableTemplate1(),
+                    domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                    hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                    globalVariableTemplate: '',
                     singleCallControllerFunctionName
                 },
                 singleCallControllerFunctionName,
-                getDocumentDomainTemplate(currentDomain)
+                thatThisTemplate
             );
         });
 
         it('should correctly run code inside template', () => {
-            assert.doesNotThrow(testFunc);
+            assert.doesNotThrow(() => testFunc.apply(root));
+            assert.isUndefined(root.document.location);
         });
     });
 
     describe('Variant #2: current domain matches with base domain of `domainsString`', () => {
         const domainsString: string = ['.example.com'].join(';');
+        const domainLockRedirectUrl: string = 'about:blank';
         const currentDomain: string = 'www.example.com';
 
-        let testFunc: () => void;
+        let testFunc: Function;
+        let root: any;
 
         before(() => {
             const [
                 hiddenDomainsString,
-                diff
+                domainsStringDiff
             ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-            testFunc = () => getFunctionFromTemplate(
+            const [
+                hiddenDomainLockRedirectUrl,
+                domainLockRedirectUrlDiff
+            ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+            root = {
+                document: {
+                    domain: currentDomain,
+                    location: undefined,
+                },
+            };
+
+            testFunc = getFunctionFromTemplate(
                 {
                     domainLockFunctionName: 'domainLockFunction',
-                    diff: diff,
+                    domainsStringDiff,
                     domains: hiddenDomainsString,
-                    globalVariableTemplate: GlobalVariableTemplate1(),
+                    domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                    hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                    globalVariableTemplate: '',
                     singleCallControllerFunctionName
                 },
                 singleCallControllerFunctionName,
-                getDocumentDomainTemplate(currentDomain)
+                thatThisTemplate
             );
         });
 
         it('should correctly run code inside template', () => {
-            assert.doesNotThrow(testFunc);
+            assert.doesNotThrow(() => testFunc.apply(root));
+            assert.isUndefined(root.document.location);
         });
     });
 
     describe('Variant #3: current domain matches with root domain of `domainsString`', () => {
         const domainsString: string = ['.example.com'].join(';');
+        const domainLockRedirectUrl: string = 'about:blank';
         const currentDomain: string = 'example.com';
 
-        let testFunc: () => void;
+        let testFunc: Function;
+        let root: any;
 
         before(() => {
             const [
                 hiddenDomainsString,
-                diff
+                domainsStringDiff
             ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-            testFunc = () => getFunctionFromTemplate(
+            const [
+                hiddenDomainLockRedirectUrl,
+                domainLockRedirectUrlDiff
+            ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+            root = {
+                document: {
+                    domain: currentDomain,
+                    location: undefined,
+                },
+            };
+
+            testFunc = getFunctionFromTemplate(
                 {
                     domainLockFunctionName: 'domainLockFunction',
-                    diff: diff,
+                    domainsStringDiff,
                     domains: hiddenDomainsString,
-                    globalVariableTemplate: GlobalVariableTemplate1(),
+                    domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                    hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                    globalVariableTemplate: '',
                     singleCallControllerFunctionName
                 },
                 singleCallControllerFunctionName,
-                getDocumentDomainTemplate(currentDomain)
+                thatThisTemplate
             );
         });
 
         it('should correctly run code inside template', () => {
-            assert.doesNotThrow(testFunc);
+            assert.doesNotThrow(() => testFunc.apply(root));
+            assert.isUndefined(root.document.location);
         });
     });
 
     describe('Variant #4: current root domain matches with `domainsString`', () => {
         describe('Variant #1', () => {
             const domainsString: string = ['example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
             it('should correctly run code inside template', () => {
-                assert.doesNotThrow(testFunc);
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.isUndefined(root.document.location);
             });
         });
 
         describe('Variant #2', () => {
             const domainsString: string = ['example.com', '.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'subdomain.example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
             it('should correctly run code inside template', () => {
-                assert.doesNotThrow(testFunc);
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.isUndefined(root.document.location);
             });
         });
 
         describe('Variant #3', () => {
             const domainsString: string = ['.example.com', 'example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'subdomain.example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
             it('should correctly run code inside template', () => {
-                assert.doesNotThrow(testFunc);
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.isUndefined(root.document.location);
             });
         });
 
         describe('Variant #4', () => {
             const domainsString: string = ['sub1.example.com', 'sub2.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'sub1.example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
             it('should correctly run code inside template', () => {
-                assert.doesNotThrow(testFunc);
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.isUndefined(root.document.location);
             });
         });
     });
 
     describe('Variant #5: current domain matches with base domain of `domainsString` item', () => {
         const domainsString: string = ['www.test.com', '.example.com'].join(';');
+        const domainLockRedirectUrl: string = 'about:blank';
         const currentDomain: string = 'subdomain.example.com';
 
-        let testFunc: () => void;
+        let testFunc: Function;
+        let root: any;
 
         before(() => {
             const [
                 hiddenDomainsString,
-                diff
+                domainsStringDiff
             ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-            testFunc = () => getFunctionFromTemplate(
+            const [
+                hiddenDomainLockRedirectUrl,
+                domainLockRedirectUrlDiff
+            ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+            root = {
+                document: {
+                    domain: currentDomain,
+                    location: undefined,
+                },
+            };
+
+            testFunc = getFunctionFromTemplate(
                 {
                     domainLockFunctionName: 'domainLockFunction',
-                    diff: diff,
+                    domainsStringDiff,
                     domains: hiddenDomainsString,
-                    globalVariableTemplate: GlobalVariableTemplate1(),
+                    domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                    hiddenDomainLockRedirectUrl,
+                    globalVariableTemplate: '',
                     singleCallControllerFunctionName
                 },
                 singleCallControllerFunctionName,
-                getDocumentDomainTemplate(currentDomain)
+                thatThisTemplate
             );
         });
 
         it('should correctly run code inside template', () => {
-            assert.doesNotThrow(testFunc);
+            assert.doesNotThrow(() => testFunc.apply(root));
+            assert.isUndefined(root.document.location);
         });
     });
 
     describe('Variant #6: current domain doesn\'t match with `domainsString`', () => {
         describe('Variant #1', () => {
             const domainsString: string = ['www.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'www.test.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
 
         describe('Variant #2', () => {
             const domainsString: string = ['sub1.test.com', 'sub2.test.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'sub3.test.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate({
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate({
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
 
         describe('Variant #3', () => {
             const domainsString: string = ['www.example.com', '.example.com', 'sub.test.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'www.test.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
 
         describe('Variant #4', () => {
             const domainsString: string = ['.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'example1.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
 
-        describe('Variant #4', () => {
+        describe('Variant #5', () => {
             const domainsString: string = ['example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentDomain: string = 'sub.example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentDomain,
+                        location: undefined,
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainTemplate(currentDomain)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
     });
@@ -496,61 +676,97 @@ describe('DomainLockTemplate', () => {
     describe('Variant #7: location.hostname', () => {
         describe('Variant #1: current location.hostname matches with `domainsString`', () => {
             const domainsString: string = ['www.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentHostName: string = 'www.example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        location: {
+                            hostname: currentHostName,
+                        },
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentLocationTemplate(currentHostName)
+                    thatThisTemplate
                 );
             });
 
             it('should correctly run code inside template', () => {
-                assert.doesNotThrow(testFunc);
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.isObject(root.document.location);
             });
         });
 
         describe('Variant #2: current location.hostname doesn\'t match with `domainsString`', () => {
             const domainsString: string = ['www.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentHostName: string = 'www.test.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        location: {
+                            hostname: currentHostName,
+                        },
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentLocationTemplate(currentHostName)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
     });
@@ -558,61 +774,99 @@ describe('DomainLockTemplate', () => {
     describe('Variant #8: domain and location.hostname presented', () => {
         describe('Variant #1: current domain matches with `domainsString`', () => {
             const domainsString: string = ['www.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentHostName: string = 'www.example.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentHostName,
+                        location: {
+                            hostname: currentHostName,
+                        },
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainAndLocationTemplate(currentHostName)
+                    thatThisTemplate
                 );
             });
 
             it('should correctly run code inside template', () => {
-                assert.doesNotThrow(testFunc);
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.isObject(root.document.location);
             });
         });
 
         describe('Variant #2: current domain doesn\'t match with `domainsString`', () => {
             const domainsString: string = ['www.example.com'].join(';');
+            const domainLockRedirectUrl: string = 'about:blank';
             const currentHostName: string = 'www.test.com';
 
-            let testFunc: () => void;
+            let testFunc: Function;
+            let root: any;
 
             before(() => {
                 const [
                     hiddenDomainsString,
-                    diff
+                    domainsStringDiff
                 ] = cryptUtils.hideString(domainsString, domainsString.length * 3);
 
-                testFunc = () => getFunctionFromTemplate(
+                const [
+                    hiddenDomainLockRedirectUrl,
+                    domainLockRedirectUrlDiff
+                ] = cryptUtils.hideString(domainLockRedirectUrl, domainLockRedirectUrl.length * 3);
+
+                root = {
+                    document: {
+                        domain: currentHostName,
+                        location: {
+                            hostname: currentHostName,
+                        },
+                    },
+                };
+
+                testFunc = getFunctionFromTemplate(
                     {
                         domainLockFunctionName: 'domainLockFunction',
-                        diff: diff,
+                        domainsStringDiff,
                         domains: hiddenDomainsString,
-                        globalVariableTemplate: GlobalVariableTemplate1(),
+                        domainLockRedirectUrlDiff: domainLockRedirectUrlDiff,
+                        hiddenDomainLockRedirectUrl: hiddenDomainLockRedirectUrl,
+                        globalVariableTemplate: '',
                         singleCallControllerFunctionName
                     },
                     singleCallControllerFunctionName,
-                    getDocumentDomainAndLocationTemplate(currentHostName)
+                    thatThisTemplate
                 );
             });
 
-            it('should throw an error', () => {
-                assert.throws(testFunc);
+            it('should change document.location', () => {
+                assert.doesNotThrow(() => testFunc.apply(root));
+                assert.equal(root.document.location, domainLockRedirectUrl);
             });
         });
     });

+ 12 - 10
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -1140,14 +1140,14 @@ describe('JavaScriptObfuscator', () => {
 
             const samplesCount: number = 30;
 
-            let areCollisionsExists: boolean = false;
-            let obfuscateFunc: (identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>) => string;
+            let collisionError: string | null = null;
+            let obfuscateFunc: (identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>) => IObfuscationResult;
 
             before(() => {
                 const code: string = readFileAsString(__dirname + '/fixtures/custom-nodes-identifier-names-collision.js');
 
                 obfuscateFunc = (identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>) => {
-                    const obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    return JavaScriptObfuscator.obfuscate(
                         code,
                         {
                             identifierNamesGenerator,
@@ -1156,9 +1156,7 @@ describe('JavaScriptObfuscator', () => {
                             identifiersDictionary: ['foo', 'bar', 'baz', 'bark', 'hawk', 'foozmos', 'cow', 'chikago'],
                             stringArray: true
                         }
-                    ).getObfuscatedCode();
-
-                    return obfuscatedCode;
+                    );
                 };
 
 
@@ -1167,10 +1165,14 @@ describe('JavaScriptObfuscator', () => {
                     IdentifierNamesGenerator.MangledIdentifierNamesGenerator
                 ].forEach((identifierNamesGenerator: TTypeFromEnum<typeof IdentifierNamesGenerator>) => {
                     for (let i = 0; i < samplesCount; i++) {
+                        const obfuscationResult = obfuscateFunc(identifierNamesGenerator);
+                        const obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                        const seed = obfuscationResult.getOptions().seed;
+
                         try {
-                            eval(obfuscateFunc(identifierNamesGenerator));
-                        } catch {
-                            areCollisionsExists = true;
+                            eval(obfuscatedCode);
+                        } catch (error) {
+                            collisionError = `Seed: ${seed}. Error: ${error.message}`;
                             break;
                         }
                     }
@@ -1178,7 +1180,7 @@ describe('JavaScriptObfuscator', () => {
             });
 
             it('It does not create identifier names collision', () => {
-                assert.equal(areCollisionsExists, false);
+                assert.isNull(collisionError);
             });
         });
 

+ 46 - 0
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -159,6 +159,52 @@ describe('OptionsNormalizer', () => {
             });
         });
 
+        describe('domainLockRedirectUrlRule', () => {
+            describe('Variant #1: `domainLock` option is set', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        domainLock: [
+                            'localhost'
+                        ],
+                        domainLockRedirectUrl: 'https://example.com'
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        domainLock: [
+                            'localhost'
+                        ],
+                        domainLockRedirectUrl: 'https://example.com'
+                    };
+                });
+
+                it('should not normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+
+            describe('Variant #2 `domainLock` option is not set', () => {
+                before(() => {
+                    optionsPreset = getNormalizedOptions({
+                        ...getDefaultOptions(),
+                        domainLock: [],
+                        domainLockRedirectUrl: 'https://example.com'
+                    });
+
+                    expectedOptionsPreset = {
+                        ...getDefaultOptions(),
+                        domainLock: [],
+                        domainLockRedirectUrl: 'about:blank'
+                    };
+                });
+
+                it('should normalize options preset', () => {
+                    assert.deepEqual(optionsPreset, expectedOptionsPreset);
+                });
+            });
+        });
+
         describe('domainLockRule', () => {
             before(() => {
                 optionsPreset = getNormalizedOptions({

+ 104 - 0
test/functional-tests/options/domain-lock-destination/Validation.spec.ts

@@ -0,0 +1,104 @@
+import { assert } from 'chai';
+
+import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
+
+import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
+
+describe('`domainLockRedirectUrl` validation', () => {
+    describe('IsDomainLockRedirectUrl', () => {
+        describe('Variant #1: positive validation', () => {
+            describe('Variant #1: string with url containing protocol, host and some path', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            domainLockRedirectUrl: 'https://example.com/path'
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #2: string with url containing host and some path', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            domainLockRedirectUrl: 'example.com/path'
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #3: string with url containing host and some path', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            domainLockRedirectUrl: '/path'
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+
+            describe('Variant #4: `about:blank` string', () => {
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            domainLockRedirectUrl: 'about:blank'
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should pass validation', () => {
+                    assert.doesNotThrow(testFunc);
+                });
+            });
+        });
+
+        describe('Variant #2: negative validation', () => {
+            describe('Variant #1: some non-url string', () => {
+                const expectedError: string = 'must be an URL address';
+                let testFunc: () => string;
+
+                beforeEach(() => {
+                    testFunc = () => JavaScriptObfuscator.obfuscate(
+                        '',
+                        {
+                            ...NO_ADDITIONAL_NODES_PRESET,
+                            domainLockRedirectUrl: 'foo'
+                        }
+                    ).getObfuscatedCode();
+                });
+
+                it('should not pass validation', () => {
+                    assert.throws(testFunc, expectedError);
+                });
+            });
+        });
+    });
+});

+ 1 - 1
test/functional-tests/options/identifier-names-cache/Validation.spec.ts

@@ -233,7 +233,7 @@ describe('`identifierNamesCache` validation', () => {
                 });
             });
 
-            describe('Variant #4: cache with nullable dictionary fields', () => {
+            describe('Variant #5: cache with nullable dictionary fields', () => {
                 let testFunc: () => string;
 
                 beforeEach(() => {

+ 1 - 0
test/index.spec.ts

@@ -133,6 +133,7 @@ import './functional-tests/node-transformers/string-array-transformers/string-ar
 import './functional-tests/node-transformers/string-array-transformers/string-array-scope-calls-wrapper-transformer/StringArrayScopeCallsWrapperTransformer.spec';
 import './functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec';
 import './functional-tests/options/OptionsNormalizer.spec';
+import './functional-tests/options/domain-lock-destination/Validation.spec';
 import './functional-tests/options/domain-lock/Validation.spec';
 import './functional-tests/options/identifier-names-cache/Validation.spec';
 import './functional-tests/storages/string-array-transformers/string-array-storage/StringArrayStorage.spec';

+ 64 - 9
test/unit-tests/source-code/ObfuscationResult.spec.ts

@@ -4,15 +4,47 @@ import { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
 
 import { assert } from 'chai';
 
+import { TInputOptions } from '../../../src/types/options/TInputOptions';
 import { TTypeFromEnum } from '../../../src/types/utils/TTypeFromEnum';
 
 import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
 import { IObfuscationResult } from '../../../src/interfaces/source-code/IObfuscationResult';
+import { IOptions } from '../../../src/interfaces/options/IOptions';
 
 import { SourceMapMode } from '../../../src/enums/source-map/SourceMapMode';
 
+import { DEFAULT_PRESET } from '../../../src/options/presets/Default';
+
 import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
 
+/**
+ * @param {string} rawObfuscatedCode
+ * @param {TInputOptions} options
+ * @returns {IObfuscationResult}
+ */
+function getObfuscationResult (
+    rawObfuscatedCode: string,
+    options: TInputOptions
+): IObfuscationResult {
+    const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade();
+
+    inversifyContainerFacade.load(
+        '',
+        '',
+        {
+            ...DEFAULT_PRESET,
+            ...options
+        }
+    );
+
+    const obfuscationResult: IObfuscationResult = inversifyContainerFacade
+        .get<IObfuscationResult>(ServiceIdentifiers.IObfuscationResult);
+
+    obfuscationResult.initialize(rawObfuscatedCode, '');
+
+    return obfuscationResult;
+}
+
 /**
  * @param rawObfuscatedCode
  * @param sourceMap
@@ -20,7 +52,7 @@ import { InversifyContainerFacade } from '../../../src/container/InversifyContai
  * @param sourceMapFileName
  * @param sourceMapMode
  */
-function getObfuscationResult (
+function getSourceMapObfuscationResult (
     rawObfuscatedCode: string,
     sourceMap: string,
     sourceMapBaseUrl: string,
@@ -33,10 +65,11 @@ function getObfuscationResult (
         '',
         '',
         {
+            ...DEFAULT_PRESET,
             sourceMap: true,
-            sourceMapBaseUrl: sourceMapBaseUrl,
-            sourceMapFileName: sourceMapFileName,
-            sourceMapMode: sourceMapMode
+            sourceMapBaseUrl,
+            sourceMapFileName,
+            sourceMapMode
         }
     );
 
@@ -56,7 +89,7 @@ describe('ObfuscatedCode', () => {
         let obfuscationResult: IObfuscationResult;
 
         before(() => {
-            obfuscationResult = getObfuscationResult(
+            obfuscationResult = getSourceMapObfuscationResult(
                 expectedObfuscatedCode,
                 sourceMap,
                 '',
@@ -75,7 +108,7 @@ describe('ObfuscatedCode', () => {
 
         describe('source map doest\'t exist', () => {
             before(() => {
-                obfuscatedCode = getObfuscationResult(
+                obfuscatedCode = getSourceMapObfuscationResult(
                     expectedObfuscatedCode,
                     '',
                     '',
@@ -93,7 +126,7 @@ describe('ObfuscatedCode', () => {
             const regExp: RegExp = /data:application\/json;base64,dGVzdA==/;
 
             before(() => {
-                obfuscatedCode = getObfuscationResult(
+                obfuscatedCode = getSourceMapObfuscationResult(
                     expectedObfuscatedCode,
                     sourceMap,
                     '',
@@ -111,7 +144,7 @@ describe('ObfuscatedCode', () => {
             const regExp: RegExp = /sourceMappingURL=http:\/\/example\.com\/output\.js\.map/;
 
             before(() => {
-                obfuscatedCode = getObfuscationResult(
+                obfuscatedCode = getSourceMapObfuscationResult(
                     expectedObfuscatedCode,
                     sourceMap,
                     'http://example.com',
@@ -127,7 +160,7 @@ describe('ObfuscatedCode', () => {
 
         describe('source map mode is `separate`, `sourceMapUrl` is not set', () => {
             before(() => {
-                obfuscatedCode = getObfuscationResult(
+                obfuscatedCode = getSourceMapObfuscationResult(
                     expectedObfuscatedCode,
                     sourceMap,
                     '',
@@ -141,4 +174,26 @@ describe('ObfuscatedCode', () => {
             });
         });
     });
+
+    describe('getOptions', () => {
+        const seed: number = 1234;
+        const expectedOptions: IOptions = {
+            ...DEFAULT_PRESET,
+            seed
+        } as IOptions;
+        let options: IOptions;
+
+        before(() => {
+            options = getObfuscationResult(
+                expectedObfuscatedCode,
+                {
+                    seed
+                }
+            ).getOptions();
+        });
+
+        it('should return options object', () => {
+            assert.deepEqual(options, expectedOptions);
+        });
+    });
 });

+ 15 - 27
yarn.lock

@@ -693,10 +693,10 @@
   resolved "https://registry.npmjs.org/@types/node/-/node-13.9.3.tgz"
   integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA==
 
-"@types/[email protected].1":
-  version "15.12.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2"
-  integrity sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw==
+"@types/[email protected].2":
+  version "15.12.2"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d"
+  integrity sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
@@ -1629,7 +1629,7 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
 
 decamelize@^1.2.0:
   version "1.2.0"
-  resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
 decamelize@^4.0.0:
@@ -2355,14 +2355,7 @@ get-stream@^6.0.0:
   resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz"
   integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==
 
-glob-parent@^5.1.0, glob-parent@~5.1.0:
-  version "5.1.1"
-  resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz"
-  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
-  dependencies:
-    is-glob "^4.0.1"
-
-glob-parent@^5.1.2:
+glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2643,7 +2636,7 @@ is-date-object@^1.0.1:
 
 is-extglob@^2.1.1:
   version "2.1.1"
-  resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
+  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
   integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
 
 is-fullwidth-code-point@^2.0.0:
@@ -2663,7 +2656,7 @@ is-generator-function@^1.0.7:
 
 is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
   version "4.0.1"
-  resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
   integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
@@ -3067,12 +3060,7 @@ lodash.truncate@^4.4.2:
   resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
   integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
 
-lodash@^4.17.13, lodash@^4.17.19:
-  version "4.17.19"
-  resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
-
-lodash@^4.17.21:
+lodash@^4.17.13, lodash@^4.17.19, lodash@^4.17.21:
   version "4.17.21"
   resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -4737,9 +4725,9 @@ write-file-atomic@^3.0.0:
     typedarray-to-buffer "^3.1.5"
 
 y18n@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz"
-  integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
+  integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
 
 y18n@^5.0.5:
   version "5.0.5"
@@ -4767,9 +4755,9 @@ [email protected]:
   integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
 
 yargs-parser@^18.1.1:
-  version "18.1.1"
-  resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.1.tgz"
-  integrity sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA==
+  version "18.1.3"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
+  integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
   dependencies:
     camelcase "^5.0.0"
     decamelize "^1.2.0"

Някои файлове не бяха показани, защото твърде много файлове са промени