Browse Source

`sourceMapBaseUrl` and `sourceMapFileName` wip implementation

sanex3339 8 years ago
parent
commit
1eeaf7df2b

+ 17 - 4
README.md

@@ -225,20 +225,33 @@ Enables source map generation for obfuscated code.
 ### `sourceMapBaseUrl`
 Type: `string` Default: ``
 
-Adds base url to the source map import url when `sourceMapMode: 'separate'`.
+Sets base url to the source map import url when `sourceMapMode: 'separate'`.
  
-##### :warning: this option designed for CLI usage
-
 CLI example:
 ```
 javascript-obfuscator input.js --output out.js --sourceMap true --sourceMapBaseUrl 'http://localhost:9000'
 ```
 
-Result import: 
+Result: 
 ```
 //# sourceMappingURL=http://localhost:9000/out.js.map
 ```
 
+### `sourceMapFileName`
+Type: `string` Default: ``
+
+Sets file name for output source map when `sourceMapMode: 'separate'`.
+
+CLI example:
+```
+javascript-obfuscator input.js --output out.js --sourceMap true --sourceMapBaseUrl 'http://localhost:9000' --sourceMapFileName example
+```
+
+Result: 
+```
+//# sourceMappingURL=http://localhost:9000/example.js.map
+```
+
 ### `sourceMapMode`
 Type: `string` Default: `separate`
 

+ 43 - 47
dist/index.js

@@ -207,14 +207,6 @@ var Utils = function () {
         value: function isInteger(number) {
             return number % 1 === 0;
         }
-    }, {
-        key: 'normalizeUrl',
-        value: function normalizeUrl(url) {
-            if (!url.endsWith('/')) {
-                url += '/';
-            }
-            return url;
-        }
     }, {
         key: 'strEnumify',
         value: function strEnumify(obj) {
@@ -904,6 +896,7 @@ exports.NO_CUSTOM_NODES_PRESET = Object.freeze({
     selfDefending: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
+    sourceMapFileName: '',
     sourceMapMode: SourceMapMode_1.SourceMapMode.Separate,
     unicodeArray: false,
     unicodeArrayThreshold: 0,
@@ -932,7 +925,6 @@ var JavaScriptObfuscatorInternal = function () {
     function JavaScriptObfuscatorInternal(sourceCode, obfuscatorOptions) {
         _classCallCheck(this, JavaScriptObfuscatorInternal);
 
-        this.sourceMapUrl = '';
         this.sourceCode = sourceCode;
         if (obfuscatorOptions) {
             this.options = new Options_1.Options(obfuscatorOptions);
@@ -942,10 +934,7 @@ var JavaScriptObfuscatorInternal = function () {
     _createClass(JavaScriptObfuscatorInternal, [{
         key: 'getObfuscationResult',
         value: function getObfuscationResult() {
-            if (this.sourceMapUrl) {
-                this.sourceMapUrl = this.options.sourceMapBaseUrl + this.sourceMapUrl;
-            }
-            return new SourceMapCorrector_1.SourceMapCorrector(new ObfuscationResult_1.ObfuscationResult(this.generatorOutput.code, this.generatorOutput.map), this.sourceMapUrl, this.options.sourceMapMode).correct();
+            return new SourceMapCorrector_1.SourceMapCorrector(new ObfuscationResult_1.ObfuscationResult(this.generatorOutput.code, this.generatorOutput.map), this.options.sourceMapBaseUrl + this.options.sourceMapFileName, this.options.sourceMapMode).correct();
         }
     }, {
         key: 'obfuscate',
@@ -956,11 +945,6 @@ var JavaScriptObfuscatorInternal = function () {
             astTree = new Obfuscator_1.Obfuscator(this.options).obfuscateNode(astTree);
             this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
         }
-    }, {
-        key: 'setSourceMapUrl',
-        value: function setSourceMapUrl(url) {
-            this.sourceMapUrl = url;
-        }
     }], [{
         key: 'generateCode',
         value: function generateCode(sourceCode, astTree, options) {
@@ -1157,6 +1141,7 @@ exports.DEFAULT_PRESET = Object.freeze({
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
+    sourceMapFileName: '',
     sourceMapMode: SourceMapMode_1.SourceMapMode.Separate,
     unicodeArray: true,
     unicodeArrayThreshold: 0.8,
@@ -1311,7 +1296,7 @@ var SourceMapCorrector = function () {
     _createClass(SourceMapCorrector, [{
         key: "correct",
         value: function correct() {
-            return new ObfuscationResult_1.ObfuscationResult(this.correctObfuscatedCode(), this.correctSourceMap());
+            return new ObfuscationResult_1.ObfuscationResult(this.correctObfuscatedCode(), this.sourceMap);
         }
     }, {
         key: "correctObfuscatedCode",
@@ -1322,26 +1307,18 @@ var SourceMapCorrector = function () {
             var sourceMappingUrl = '//# sourceMappingURL=';
             switch (this.sourceMapMode) {
                 case SourceMapMode_1.SourceMapMode.Inline:
-                    sourceMappingUrl += "data:application/json;base64," + Utils_1.Utils.btoa(this.sourceMapUrl || this.sourceMap, false);
+                    sourceMappingUrl += "data:application/json;base64," + Utils_1.Utils.btoa(this.sourceMap, false);
                     break;
                 case SourceMapMode_1.SourceMapMode.Separate:
                 default:
-                    if (this.sourceMapUrl) {
-                        sourceMappingUrl += this.sourceMapUrl;
-                        break;
+                    if (!this.sourceMapUrl) {
+                        return this.obfuscatedCode;
                     }
-                    return this.obfuscatedCode;
+                    sourceMappingUrl += this.sourceMapUrl;
+                    break;
             }
             return this.obfuscatedCode + "\n" + sourceMappingUrl;
         }
-    }, {
-        key: "correctSourceMap",
-        value: function correctSourceMap() {
-            if (this.sourceMapMode === SourceMapMode_1.SourceMapMode.Inline) {
-                return '';
-            }
-            return this.sourceMap;
-        }
     }]);
 
     return SourceMapCorrector;
@@ -1440,9 +1417,17 @@ var CLIUtils = function () {
     }, {
         key: 'getOutputSourceMapPath',
         value: function getOutputSourceMapPath(outputCodePath) {
-            return outputCodePath.split('.').map(function (value, index, array) {
-                return index === array.length - 1 ? value + '.map' : value;
-            }).join('.');
+            var sourceMapFileName = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];
+
+            if (sourceMapFileName) {
+                outputCodePath = outputCodePath.substr(0, outputCodePath.lastIndexOf('/')) + '/' + sourceMapFileName;
+            }
+            if (!/\.js\.map$/.test(outputCodePath)) {
+                outputCodePath = outputCodePath.split('.')[0] + '.js.map';
+            } else if (/\.js$/.test(outputCodePath)) {
+                outputCodePath += '.map';
+            }
+            return outputCodePath;
         }
     }, {
         key: 'getPackageConfig',
@@ -1553,7 +1538,7 @@ var JavaScriptObfuscatorCLI = function () {
         value: function configureCommands() {
             this.commands = new commander.Command().version(JavaScriptObfuscatorCLI.getBuildVersion(), '-v, --version').usage('<inputPath> [options]').option('-o, --output <path>', 'Output path for obfuscated code').option('--compact <boolean>', 'Disable one line output code compacting', JavaScriptObfuscatorCLI.parseBoolean).option('--debugProtection <boolean>', 'Disable browser Debug panel (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean).option('--debugProtectionInterval <boolean>', 'Disable browser Debug panel even after page was loaded (can cause DevTools enabled browser freeze)', JavaScriptObfuscatorCLI.parseBoolean).option('--disableConsoleOutput <boolean>', 'Allow console.log, console.info, console.error and console.warn messages output into browser console', JavaScriptObfuscatorCLI.parseBoolean).option('--encodeUnicodeLiterals <boolean>', 'All literals in Unicode array become encoded in Base64 (this option can slightly slow down your code speed)', JavaScriptObfuscatorCLI.parseBoolean).option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', function (val) {
                 return val.split(',');
-            }).option('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapBaseUrl <boolean>', 'Adds base url to the source map import url when `--sourceMapMode=separate`').option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--unicodeArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--unicodeArrayThreshold <number>', 'The probability that the literal string will be inserted into unicodeArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--wrapUnicodeArrayCalls <boolean>', 'Disables usage of special access function instead of direct array call', JavaScriptObfuscatorCLI.parseBoolean).option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', function (val) {
+            }).option('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean).option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean).option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`').option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`').option('--sourceMapMode <string> [inline, separate]', 'Specify source map output mode', JavaScriptObfuscatorCLI.parseSourceMapMode).option('--unicodeArray <boolean>', 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', JavaScriptObfuscatorCLI.parseBoolean).option('--unicodeArrayThreshold <number>', 'The probability that the literal string will be inserted into unicodeArray (Default: 0.8, Min: 0, Max: 1)', parseFloat).option('--wrapUnicodeArrayCalls <boolean>', 'Disables usage of special access function instead of direct array call', JavaScriptObfuscatorCLI.parseBoolean).option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', function (val) {
                 return val.split(',');
             }).parse(this.rawArguments);
             this.commands.on('--help', function () {
@@ -1588,16 +1573,13 @@ var JavaScriptObfuscatorCLI = function () {
     }, {
         key: 'processDataWithSourceMap',
         value: function processDataWithSourceMap(outputCodePath, options) {
-            var javaScriptObfuscator = new JavaScriptObfuscatorInternal_1.JavaScriptObfuscatorInternal(this.data, options),
-                obfuscationResult = void 0,
-                outputSourceMapPath = CLIUtils_1.CLIUtils.getOutputSourceMapPath(outputCodePath);
+            var outputSourceMapPath = CLIUtils_1.CLIUtils.getOutputSourceMapPath(outputCodePath, options.sourceMapFileName ? options.sourceMapFileName : '');
+            options.sourceMapFileName = path.basename(outputSourceMapPath);
+            var javaScriptObfuscator = new JavaScriptObfuscatorInternal_1.JavaScriptObfuscatorInternal(this.data, options);
             javaScriptObfuscator.obfuscate();
-            if (options.sourceMapMode === SourceMapMode_1.SourceMapMode.Separate) {
-                javaScriptObfuscator.setSourceMapUrl(path.basename(outputSourceMapPath));
-            }
-            obfuscationResult = javaScriptObfuscator.getObfuscationResult();
+            var obfuscationResult = javaScriptObfuscator.getObfuscationResult();
             CLIUtils_1.CLIUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
-            if (obfuscationResult.getSourceMap()) {
+            if (options.sourceMapMode === 'separate' && obfuscationResult.getSourceMap()) {
                 CLIUtils_1.CLIUtils.writeFile(outputSourceMapPath, obfuscationResult.getSourceMap());
             }
         }
@@ -3244,6 +3226,7 @@ __decorate([class_validator_1.IsString(), class_validator_1.ValidateIf(function
     require_protocol: false,
     require_valid_protocol: true
 }), __metadata('design:type', String)], Options.prototype, "sourceMapBaseUrl", void 0);
+__decorate([class_validator_1.IsString(), __metadata('design:type', String)], Options.prototype, "sourceMapFileName", void 0);
 __decorate([class_validator_1.IsIn(['inline', 'separate']), __metadata('design:type', String)], Options.prototype, "sourceMapMode", void 0);
 __decorate([class_validator_1.IsBoolean(), __metadata('design:type', Boolean)], Options.prototype, "unicodeArray", void 0);
 __decorate([class_validator_1.IsNumber(), class_validator_1.Min(0), class_validator_1.Max(1), __metadata('design:type', Number)], Options.prototype, "unicodeArrayThreshold", void 0);
@@ -3354,9 +3337,22 @@ var OptionsNormalizer = function () {
     }, {
         key: "sourceMapBaseUrl",
         value: function sourceMapBaseUrl(options) {
-            if (options.sourceMapBaseUrl) {
+            var sourceMapBaseUrl = options.sourceMapBaseUrl;
+            if (sourceMapBaseUrl && !sourceMapBaseUrl.endsWith('/')) {
+                Object.assign(options, {
+                    sourceMapBaseUrl: sourceMapBaseUrl + "/"
+                });
+            }
+            return options;
+        }
+    }, {
+        key: "sourceMapFileName",
+        value: function sourceMapFileName(options) {
+            var sourceMapFileName = options.sourceMapFileName;
+            if (sourceMapFileName) {
+                sourceMapFileName = sourceMapFileName.split('.')[0];
                 Object.assign(options, {
-                    sourceMapBaseUrl: Utils_1.Utils.normalizeUrl(options.sourceMapBaseUrl)
+                    sourceMapFileName: sourceMapFileName + ".js.map"
                 });
             }
             return options;
@@ -3397,7 +3393,7 @@ OptionsNormalizer.SELF_DEFENDING_OPTIONS = {
     compact: true,
     selfDefending: true
 };
-OptionsNormalizer.normalizerRules = [OptionsNormalizer.domainLockRule, OptionsNormalizer.unicodeArrayRule, OptionsNormalizer.unicodeArrayThresholdRule, OptionsNormalizer.encodeUnicodeLiteralsRule, OptionsNormalizer.sourceMapBaseUrl, OptionsNormalizer.selfDefendingRule];
+OptionsNormalizer.normalizerRules = [OptionsNormalizer.domainLockRule, OptionsNormalizer.unicodeArrayRule, OptionsNormalizer.unicodeArrayThresholdRule, OptionsNormalizer.encodeUnicodeLiteralsRule, OptionsNormalizer.sourceMapBaseUrl, OptionsNormalizer.sourceMapFileName, OptionsNormalizer.selfDefendingRule];
 exports.OptionsNormalizer = OptionsNormalizer;
 
 /***/ },

+ 1 - 17
src/JavaScriptObfuscatorInternal.ts

@@ -36,11 +36,6 @@ export class JavaScriptObfuscatorInternal {
      */
     private sourceCode: string;
 
-    /**
-     * @type {string}
-     */
-    private sourceMapUrl: string = '';
-
     /**
      * @param sourceCode
      * @param obfuscatorOptions
@@ -84,16 +79,12 @@ export class JavaScriptObfuscatorInternal {
      * @returns {IObfuscationResult}
      */
     public getObfuscationResult (): IObfuscationResult {
-        if (this.sourceMapUrl) {
-            this.sourceMapUrl = this.options.sourceMapBaseUrl + this.sourceMapUrl;
-        }
-
         return new SourceMapCorrector(
             new ObfuscationResult(
                 this.generatorOutput.code,
                 this.generatorOutput.map
             ),
-            this.sourceMapUrl,
+            this.options.sourceMapBaseUrl + this.options.sourceMapFileName,
             this.options.sourceMapMode
         ).correct();
     }
@@ -107,11 +98,4 @@ export class JavaScriptObfuscatorInternal {
 
         this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
     }
-
-    /**
-     * @param url
-     */
-    public setSourceMapUrl (url: string): void {
-        this.sourceMapUrl = url;
-    }
 }

+ 7 - 18
src/SourceMapCorrector.ts

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

+ 0 - 12
src/Utils.ts

@@ -152,18 +152,6 @@ export class Utils {
         return number % 1 === 0;
     }
 
-    /**
-     * @param url
-     * @returns {string}
-     */
-    public static normalizeUrl (url: string): string {
-        if (!url.endsWith('/')) {
-            url += '/';
-        }
-
-        return url;
-    }
-
     /**
      * @param obj
      * @returns {T}

+ 16 - 7
src/cli/CLIUtils.ts

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

+ 15 - 12
src/cli/JavaScriptObfuscatorCLI.ts

@@ -132,7 +132,8 @@ export class JavaScriptObfuscatorCLI {
             .option('--rotateUnicodeArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
-            .option('--sourceMapBaseUrl <boolean>', 'Adds base url to the source map import url when `--sourceMapMode=separate`')
+            .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')
+            .option('--sourceMapFileName <string>', 'Sets file name for output source map when `--sourceMapMode=separate`')
             .option(
                 '--sourceMapMode <string> [inline, separate]',
                 'Specify source map output mode',
@@ -182,23 +183,25 @@ export class JavaScriptObfuscatorCLI {
      * @param options
      */
     private processDataWithSourceMap (outputCodePath: string, options: IObfuscatorOptions): void {
-        let javaScriptObfuscator: JavaScriptObfuscatorInternal = new JavaScriptObfuscatorInternal(this.data, options),
-            obfuscationResult: IObfuscationResult,
-            outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(outputCodePath);
+        let outputSourceMapPath: string = CLIUtils.getOutputSourceMapPath(
+            outputCodePath,
+            options.sourceMapFileName ? options.sourceMapFileName : ''
+        );
 
-        javaScriptObfuscator.obfuscate();
+        options.sourceMapFileName = path.basename(outputSourceMapPath);
 
-        if (options.sourceMapMode === SourceMapMode.Separate) {
-            javaScriptObfuscator.setSourceMapUrl(
-                path.basename(outputSourceMapPath)
-            );
-        }
+        const javaScriptObfuscator: JavaScriptObfuscatorInternal = new JavaScriptObfuscatorInternal(
+            this.data,
+            options
+        );
+
+        javaScriptObfuscator.obfuscate();
 
-        obfuscationResult = javaScriptObfuscator.getObfuscationResult();
+        const obfuscationResult: IObfuscationResult = javaScriptObfuscator.getObfuscationResult();
 
         CLIUtils.writeFile(outputCodePath, obfuscationResult.getObfuscatedCode());
 
-        if (obfuscationResult.getSourceMap()) {
+        if (options.sourceMapMode === 'separate' && obfuscationResult.getSourceMap()) {
             CLIUtils.writeFile(outputSourceMapPath, obfuscationResult.getSourceMap());
         }
     }

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

@@ -11,6 +11,7 @@ export interface IObfuscatorOptions {
     selfDefending?: boolean;
     sourceMap?: boolean;
     sourceMapBaseUrl?: string;
+    sourceMapFileName?: string;
     sourceMapMode?: TSourceMapMode;
     unicodeArray?: boolean;
     unicodeArrayThreshold?: number;

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

@@ -11,6 +11,7 @@ export interface IOptions {
     readonly selfDefending: boolean;
     readonly sourceMap: boolean;
     readonly sourceMapBaseUrl: string;
+    readonly sourceMapFileName: string;
     readonly sourceMapMode: TSourceMapMode;
     readonly unicodeArray: boolean;
     readonly unicodeArrayThreshold: number;

+ 7 - 1
src/options/Options.ts

@@ -97,7 +97,7 @@ export class Options implements IOptions {
     public readonly sourceMap: boolean;
 
     /**
-     * @type {boolean}
+     * @type {string}
      */
     @IsString()
     @ValidateIf((options: IOptions) => Boolean(options.sourceMapBaseUrl))
@@ -107,6 +107,12 @@ export class Options implements IOptions {
     })
     public readonly sourceMapBaseUrl: string;
 
+    /**
+     * @type {string}
+     */
+    @IsString()
+    public readonly sourceMapFileName: string;
+
     /**
      * @type {TSourceMapMode}
      */

+ 23 - 2
src/options/OptionsNormalizer.ts

@@ -42,6 +42,7 @@ export class OptionsNormalizer {
         OptionsNormalizer.unicodeArrayThresholdRule,
         OptionsNormalizer.encodeUnicodeLiteralsRule,
         OptionsNormalizer.sourceMapBaseUrl,
+        OptionsNormalizer.sourceMapFileName,
         OptionsNormalizer.selfDefendingRule
     ];
 
@@ -108,9 +109,29 @@ export class OptionsNormalizer {
      * @returns {IOptions}
      */
     private static sourceMapBaseUrl (options: IOptions): IOptions {
-        if (options.sourceMapBaseUrl) {
+        let sourceMapBaseUrl: string = options.sourceMapBaseUrl;
+
+        if (sourceMapBaseUrl && !sourceMapBaseUrl.endsWith('/')) {
+            Object.assign(options, {
+                sourceMapBaseUrl: `${sourceMapBaseUrl}/`
+            });
+        }
+
+        return options;
+    }
+
+    /**
+     * @param options
+     * @returns {IOptions}
+     */
+    private static sourceMapFileName (options: IOptions): IOptions {
+        let sourceMapFileName: string = options.sourceMapFileName;
+
+        if (sourceMapFileName) {
+            sourceMapFileName = sourceMapFileName.split('.')[0];
+
             Object.assign(options, {
-                sourceMapBaseUrl: Utils.normalizeUrl(options.sourceMapBaseUrl)
+                sourceMapFileName: `${sourceMapFileName}.js.map`
             });
         }
 

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

@@ -14,6 +14,7 @@ export const DEFAULT_PRESET: IObfuscatorOptions = Object.freeze({
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
+    sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
     unicodeArray: true,
     unicodeArrayThreshold: 0.8,

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

@@ -14,6 +14,7 @@ export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     selfDefending: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
+    sourceMapFileName: '',
     sourceMapMode: SourceMapMode.Separate,
     unicodeArray: false,
     unicodeArrayThreshold: 0,

+ 2 - 2
test/functional-tests/JavaScriptObfuscator.spec.ts

@@ -33,7 +33,7 @@ describe('JavaScriptObfuscator', () => {
                 assert.isOk(JSON.parse(obfuscationResult.getSourceMap()).mappings);
             });
 
-            it('should returns object with obfuscated code with inline source map and empty `sourceMap` if `sourceMapMode` is `inline', () => {
+            it('should returns object with obfuscated code with inline source map as Base64 string', () => {
                 let obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
                     `var test = 1;`,
                     Object.assign({}, NO_CUSTOM_NODES_PRESET, {
@@ -47,7 +47,7 @@ describe('JavaScriptObfuscator', () => {
                     obfuscationResult.getObfuscatedCode(),
                     /sourceMappingURL=data:application\/json;base64/
                 );
-                assert.isNotOk(obfuscationResult.getSourceMap());
+                assert.isOk(obfuscationResult.getSourceMap());
             });
 
             it('should returns object with empty obfuscated code and source map with empty data if source code is empty', () => {

+ 112 - 48
test/functional-tests/JavaScriptObfuscatorCLI.spec.ts

@@ -87,61 +87,125 @@ describe('JavaScriptObfuscatorCLI', function (): void {
         describe('--sourceMap option is set', () => {
             let outputSourceMapPath: string = `${outputFilePath}.map`;
 
-            it('should creates file with source map in the same directory as output file', () => {
-                JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator',
-                    fixtureFilePath,
-                    '--output',
-                    outputFilePath,
-                    '--compact',
-                    'true',
-                    '--selfDefending',
-                    '0',
-                    '--sourceMap',
-                    'true'
-                ]);
-
-                assert.equal(fs.existsSync(outputSourceMapPath), true);
-
-                const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
-                    sourceMap: any = JSON.parse(content);
-
-                assert.property(sourceMap, 'version');
-                assert.property(sourceMap, 'sources');
-                assert.property(sourceMap, 'names');
+            describe('--sourceMapMode option is `separate`', () => {
+                it('should creates file with source map in the same directory as output file', () => {
+                    JavaScriptObfuscator.runCLI([
+                        'node',
+                        'javascript-obfuscator',
+                        fixtureFilePath,
+                        '--output',
+                        outputFilePath,
+                        '--compact',
+                        'true',
+                        '--selfDefending',
+                        '0',
+                        '--sourceMap',
+                        'true'
+                    ]);
+
+                    assert.equal(fs.existsSync(outputSourceMapPath), true);
+
+                    const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
+                        sourceMap: any = JSON.parse(content);
+
+                    assert.property(sourceMap, 'version');
+                    assert.property(sourceMap, 'sources');
+                    assert.property(sourceMap, 'names');
+                });
+
+                it('should creates file with source map in the same directory as output file if `sourceMapBaseUrl` is set', () => {
+                    JavaScriptObfuscator.runCLI([
+                        'node',
+                        'javascript-obfuscator',
+                        fixtureFilePath,
+                        '--output',
+                        outputFilePath,
+                        '--compact',
+                        'true',
+                        '--selfDefending',
+                        '0',
+                        '--sourceMap',
+                        'true',
+                        '--sourceMapBaseUrl',
+                        'http://localhost:9000/'
+                    ]);
+
+                    assert.equal(fs.existsSync(outputSourceMapPath), true);
+
+                    const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
+                        sourceMap: any = JSON.parse(content);
+
+                    assert.property(sourceMap, 'version');
+                    assert.property(sourceMap, 'sources');
+                    assert.property(sourceMap, 'names');
+                });
+
+                afterEach(() => {
+                    fs.unlinkSync(outputSourceMapPath);
+                });
             });
 
-            it('should creates file with source map in the same directory as output file if `sourceMapBaseUrl` is set', () => {
-                JavaScriptObfuscator.runCLI([
-                    'node',
-                    'javascript-obfuscator',
-                    fixtureFilePath,
-                    '--output',
-                    outputFilePath,
-                    '--compact',
-                    'true',
-                    '--selfDefending',
-                    '0',
-                    '--sourceMap',
-                    'true',
-                    '--sourceMapBaseUrl',
-                    'http://localhost:9000/'
-                ]);
-
-                assert.equal(fs.existsSync(outputSourceMapPath), true);
-
-                const content: string = fs.readFileSync(outputSourceMapPath, { encoding: 'utf8' }),
-                    sourceMap: any = JSON.parse(content);
+            describe('--sourceMapFileName option is set', () => {
+                let sourceMapFileName: string = 'test',
+                    sourceMapFilePath: string = `${sourceMapFileName}.js.map`,
+                    outputSourceMapFilePath: string = `${outputDirName}/${sourceMapFilePath}`;
+
+                it('should creates source map file with given name in the same directory as output file', () => {
+                    JavaScriptObfuscator.runCLI([
+                        'node',
+                        'javascript-obfuscator',
+                        fixtureFilePath,
+                        '--output',
+                        outputFilePath,
+                        '--compact',
+                        'true',
+                        '--selfDefending',
+                        '0',
+                        '--sourceMap',
+                        'true',
+                        '--sourceMapFileName',
+                        sourceMapFileName
+                    ]);
+
+                    assert.equal(fs.existsSync(outputSourceMapFilePath), true);
+
+                    const content: string = fs.readFileSync(outputSourceMapFilePath, { encoding: 'utf8' }),
+                        sourceMap: any = JSON.parse(content);
+
+                    assert.property(sourceMap, 'version');
+                    assert.property(sourceMap, 'sources');
+                    assert.property(sourceMap, 'names');
+                });
+
+                afterEach(() => {
+                    fs.unlinkSync(outputSourceMapFilePath);
+                });
+            });
 
-                assert.property(sourceMap, 'version');
-                assert.property(sourceMap, 'sources');
-                assert.property(sourceMap, 'names');
+            describe('--sourceMapMode option is `inline`', () => {
+                it('shouldn\'t create file with source map if `sourceMapMode` is `inline`', () => {
+                    JavaScriptObfuscator.runCLI([
+                        'node',
+                        'javascript-obfuscator',
+                        fixtureFilePath,
+                        '--output',
+                        outputFilePath,
+                        '--compact',
+                        'true',
+                        '--selfDefending',
+                        '0',
+                        '--sourceMap',
+                        'true',
+                        '--sourceMapMode',
+                        'inline'
+                    ]);
+
+                    assert.equal(fs.existsSync(outputSourceMapPath), false);
+                });
             });
 
             afterEach(() => {
                 fs.unlinkSync(outputFilePath);
-                fs.unlinkSync(outputSourceMapPath);
             });
         });
 

+ 5 - 5
test/functional-tests/JavaScriptObfuscatorInternal.spec.ts

@@ -10,18 +10,18 @@ describe('JavaScriptObfuscatorInternal', () => {
     describe(`setSourceMapUrl (url: string)`, () => {
         let javaScriptObfuscator: JavaScriptObfuscatorInternal,
             obfuscationResult: IObfuscationResult,
-            sourceMapUrl: string = 'test.map.js';
+            sourceMapUrl: string = 'test.js.map';
 
         it('should link obfuscated code with source map', () => {
             javaScriptObfuscator = new JavaScriptObfuscatorInternal(
                 `var test = 1;`,
                 Object.assign({}, NO_CUSTOM_NODES_PRESET, {
-                    sourceMap: true
+                    sourceMap: true,
+                    sourceMapFileName: sourceMapUrl
                 })
             );
 
             javaScriptObfuscator.obfuscate();
-            javaScriptObfuscator.setSourceMapUrl(sourceMapUrl);
 
             obfuscationResult = javaScriptObfuscator.getObfuscationResult();
 
@@ -39,12 +39,12 @@ describe('JavaScriptObfuscatorInternal', () => {
                 `var test = 1;`,
                 Object.assign({}, NO_CUSTOM_NODES_PRESET, {
                     sourceMap: true,
-                    sourceMapBaseUrl: sourceMapBaseUrl
+                    sourceMapBaseUrl: sourceMapBaseUrl,
+                    sourceMapFileName: sourceMapUrl
                 })
             );
 
             javaScriptObfuscator.obfuscate();
-            javaScriptObfuscator.setSourceMapUrl(sourceMapUrl);
 
             obfuscationResult = javaScriptObfuscator.getObfuscationResult();
 

+ 4 - 0
test/unit-tests/OptionsNormalizer.spec.ts

@@ -31,12 +31,14 @@ describe('OptionsNormalizer', () => {
             optionsPreset2 = Object.assign({}, DEFAULT_PRESET, {
                 rotateUnicodeArray: true,
                 sourceMapBaseUrl: 'http://localhost:9000',
+                sourceMapFileName: 'outputSourceMapName',
                 unicodeArray: true,
                 unicodeArrayThreshold: 0,
                 wrapUnicodeArrayCalls: true
             });
             optionsPreset3 = Object.assign({}, DEFAULT_PRESET, {
                 domainLock: ['//localhost:9000', 'https://google.ru/abc?cde=fgh'],
+                sourceMapFileName: 'outputSourceMapName.map',
                 unicodeArray: true,
                 encodeUnicodeLiterals: true,
                 wrapUnicodeArrayCalls: false
@@ -52,12 +54,14 @@ describe('OptionsNormalizer', () => {
             expectedOptionsPreset2 = Object.assign({}, DEFAULT_PRESET, {
                 rotateUnicodeArray: false,
                 sourceMapBaseUrl: 'http://localhost:9000/',
+                sourceMapFileName: 'outputSourceMapName.js.map',
                 unicodeArray: false,
                 unicodeArrayThreshold: 0,
                 wrapUnicodeArrayCalls: false
             });
             expectedOptionsPreset3 = Object.assign({}, DEFAULT_PRESET, {
                 domainLock: ['localhost', 'google.ru'],
+                sourceMapFileName: 'outputSourceMapName.js.map',
                 unicodeArray: true,
                 encodeUnicodeLiterals: true,
                 wrapUnicodeArrayCalls: true

+ 0 - 4
test/unit-tests/SourceMapCorrector.spec.ts

@@ -57,10 +57,6 @@ describe('SourceMapCorrector', () => {
             it('should add source map to obfuscated code as base64 encoded string', () => {
                 assert.match(expectedObfuscationResult.getObfuscatedCode(), /data:application\/json;base64/);
             });
-
-            it('should clear original source map', () => {
-                assert.equal(expectedObfuscationResult.getSourceMap(), '');
-            });
         });
 
         it('should add source map import to obfuscated code if source map mode is `separate`', () => {

+ 0 - 6
test/unit-tests/Utils.spec.ts

@@ -75,12 +75,6 @@ describe('Utils', () => {
         });
     });
 
-    describe('normalizeUrl (url: string): string', () => {
-        it('should normalize given url', () => {
-            assert.equal(Utils.normalizeUrl('http://localhost:9000'), 'http://localhost:9000/');
-        });
-    });
-
     describe('stringToJSFuck (string: string): string', () => {
         let expected: string = `${JSFuck.s} + ${JSFuck.t} + ${JSFuck.r} + ${JSFuck.i} + ${JSFuck.n} + ${JSFuck.g}`;