@bernhste/obfuscator-io-metro-plugin
Version:
A metro plugin to use javascript-obfuscator library with react-native
132 lines (124 loc) • 3.65 kB
JavaScript
const readline = require('readline');
const {Command} = require('commander');
const {Readable} = require('stream');
const {
EXTS,
END_ANNOTATION,
BEG_ANNOTATION,
BUNDLE_OUTPUT_CLI_ARG,
BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG,
BUNDLE_DEV_CLI_ARG,
BUNDLE_CMD
} = require('./constants');
/**
* Only 'bundle' command triggers obfuscation.
* Development bundles will be ignored (--dev true). Use JSO_METRO_DEV to override this behaviour.
* @returns {string} skip reason. If falsy value dont skip obfuscation
*/
function skipObfuscation({ runInDev }) {
let isBundleCmd = false;
const command = new Command();
command
.command(BUNDLE_CMD)
.allowUnknownOption()
.action(() => (isBundleCmd = true));
command.option(`${BUNDLE_DEV_CLI_ARG} <boolean>`).parse(process.argv);
if (!isBundleCmd) {
return 'Not a *bundle* command';
}
if (command.dev === 'true') {
return (
!runInDev &&
'Development mode. Override with JSO_METRO_DEV=true environment variable'
);
}
return null;
}
/**
* Get bundle path based CLI arguments
* @returns {{bundlePath: string, bundleSourceMapPath: string}}
* @throws {Error} when bundle output was not found
*/
function getBundlePath() {
const command = new Command();
command
.option(`${BUNDLE_OUTPUT_CLI_ARG} <string>`)
.option(`${BUNDLE_SOURCEMAP_OUTPUT_CLI_ARG} <string>`)
.parse(process.argv);
if (command.bundleOutput) {
return {
bundlePath: command.bundleOutput,
bundleSourceMapPath: command.sourcemapOutput
};
}
console.error('Bundle output path not found.');
return process.exit(-1);
}
/**
* Strip all tags from code
* @param {string} code
* @returns {string}
*/
function stripTags(code) {
return code.replace(new RegExp(BEG_ANNOTATION, 'g'), '')
.replace(new RegExp(END_ANNOTATION, 'g'), '')
}
/**
* When next character is a new line (\n or \r\n),
* we should increment startIndex to avoid user code starting with a new line.
* @param {string} startIndex
* @param {string} code
* @returns {number}
* @example
* __d(function(g,r,i,a,m,e,d){(detect new line here and start below)
* // user code
* ...
* }
*/
function shiftStartIndexOnNewLine(startIndex, code) {
switch (code[startIndex + 1]) {
case '\r':
startIndex++;
return shiftStartIndexOnNewLine(startIndex, code);
case '\n':
startIndex++;
break;
}
return startIndex;
}
/**
* Wrap user code with TAGS {BEG_ANNOTATION and END_ANNOTATION}
* @param {{code: string}} data
*/
function wrapCodeWithTags(data) {
let startIndex = data.code.indexOf('{');
const endIndex = data.code.lastIndexOf('}');
startIndex = shiftStartIndexOnNewLine(startIndex, data.code);
const init = data.code.substring(0, startIndex + 1);
const clientCode = data.code.substring(startIndex + 1, endIndex);
const end = data.code.substr(endIndex, data.code.length);
data.code = init + BEG_ANNOTATION + clientCode + END_ANNOTATION + end;
}
/**
* @param {string} path
* @param {string} projectRoot
* @returns {string} undefined if path is empty or invalid
*
* @example
* <project_root>/react-native0.59-grocery-list/App/index.js -> App/index.js
* <project_root>/react-native0.59-grocery-list/App/index.ts -> App/index.js
*/
function buildNormalizePath(path, projectRoot) {
if (typeof path !== 'string' || path.trim().length === 0) {
return;
}
const relativePath = path.replace(projectRoot, '');
return relativePath.replace(EXTS, '.js').substring(1 /* remove '/' */);
}
module.exports = {
skipObfuscation,
getBundlePath,
stripTags,
wrapCodeWithTags,
buildNormalizePath
}