UNPKG

@bernhste/obfuscator-io-metro-plugin

Version:

A metro plugin to use javascript-obfuscator library with react-native

175 lines (152 loc) 4.83 kB
const { emptyDir, mkdirp, readFile, writeFile,remove } = require('fs-extra'); const obfuscate = require('./javascriptObfuscatorAPI'); const fs = require('fs'); const path = require('path'); const { TEMP_FOLDER, DIST_TEMP_FOLDER, SRC_TEMP_FOLDER, BEG_ANNOTATION, END_ANNOTATION, EXTS } = require('./constants'); const { buildNormalizePath, wrapCodeWithTags, getBundlePath, skipObfuscation, stripTags } = require('./utils'); const debug = !!process.env.DEBUG; function filterFileNames(files, options) { var exclude = options.exclude; var include = options.include; if (!exclude){ // match nothing exclude = [/$^/]; } if (!include){ // match everything from ./* include = [/\.\/*/]; } const matchesAnyRegex = function (file, regexList) { return regexList.some(regex => regex.test(file)); }; return files.filter(file => { const isIncluded = include.length === 0 || matchesAnyRegex(file, include); const isExcluded = matchesAnyRegex(file, exclude); return isIncluded && !isExcluded; }); } async function obfuscateBundle( {bundlePath, bundleSourceMapPath}, fileNames, config, runConfig ) { await emptyDir(TEMP_FOLDER); const metroBundle = await readFile(bundlePath, 'utf8'); const metroBundleChunks = metroBundle.split(BEG_ANNOTATION); const metroUserFilesOnly = metroBundleChunks .filter((c, i) => i > 0) .map((c, i) => { return c.split(END_ANNOTATION)[0]; }); // build tmp src folders structure await Promise.all( fileNames.map(n => mkdirp(`${SRC_TEMP_FOLDER}/${path.dirname(n)}`) ) ); // write user files to tmp folder await Promise.all( metroUserFilesOnly.map((c, i) => writeFile(`${SRC_TEMP_FOLDER}/${fileNames[i]}`, c) ) ) const filesSrc = `**/*.js?(.map)`; const filesDest = DIST_TEMP_FOLDER; const cwd = SRC_TEMP_FOLDER; if (bundleSourceMapPath) { console.warn(`Metro is generating source maps that won't be useful after obfuscation.`); } const filteredFileNames = filterFileNames(fileNames, runConfig); // Loop through filesSrc and obfuscate files and save to filesDest. await obfuscate({ config, filesSrc, filesDest, cwd, filteredFileNames, runConfig}); // read obfuscated user files const obfusctedUserFiles = await Promise.all(metroUserFilesOnly.map((c, i) => readFile(`${DIST_TEMP_FOLDER}/${fileNames[i]}`, 'utf8') )); // build final bundle (with JSO TAGS still) const finalBundle = metroBundleChunks.reduce((acc, c, i) => { if (i === 0) { return c; } const obfuscatedCode = obfusctedUserFiles[i - 1]; const tillCodeEnd = c.substr( c.indexOf(END_ANNOTATION), c.length ); return acc + BEG_ANNOTATION + obfuscatedCode + tillCodeEnd; }, ''); await writeFile(bundlePath, stripTags(finalBundle)); } /** * Add serialize.processModuleFilter option to metro and attach listener to beforeExit event. * *config.fileSrc* and *config.filesDest* will be ignored. * @param {object} _config * @param {object} runConfig * @param {string} [projectRoot=process.cwd()] * @returns {{serializer: {processModuleFilter(*): boolean}}} */ module.exports = function (_config = {}, runConfig = {}, projectRoot = process.cwd()) { const skipReason = skipObfuscation(runConfig); if (skipReason) { console.log(`warning: Obfuscation SKIPPED [${skipReason}]`); return {}; } const config = _config; const bundlePath = getBundlePath(); const fileNames = new Set(); process.on('beforeExit', async function (exitCode) { try{ console.log('info: Obfuscating Code'); // start obfuscation await obfuscateBundle(bundlePath, Array.from(fileNames), config, runConfig); if(!runConfig || !runConfig.logObfuscatedFiles){ await remove(TEMP_FOLDER); // clear temp folder } } catch(err) { console.error(err); process.exit(1); } finally { process.exit(exitCode) } }); return { serializer: { /** * Select user files ONLY (no vendor) to be obfuscated. That code should be tagged with * {@BEG_ANNOTATION} and {@END_ANNOTATION}. * @param {{output: Array<*>, path: string, getSource: function():Buffer}} _module * @returns {boolean} */ processModuleFilter(_module) { if ( _module.path.indexOf('node_modules') !== -1 || typeof _module.path !== 'string' || !fs.existsSync(_module.path) || !path.extname(_module.path).match(EXTS) ) { return true; } const normalizePath = buildNormalizePath(_module.path, projectRoot); fileNames.add(normalizePath); _module.output.forEach(({data}) => { wrapCodeWithTags(data); }); return true; } } }; };