From cdfe43459e89b769507d8a3c494ea501a8e10b6f Mon Sep 17 00:00:00 2001 From: Cristian Date: Wed, 19 Jun 2024 14:46:50 +0300 Subject: [PATCH] investigating --- .gitignore | 2 +- .tool-versions | 1 + dist/config.d.ts | 7 + dist/config.js | 39 ++ dist/handlers/css.d.ts | 20 + dist/handlers/css.js | 277 ++++++++ dist/handlers/css.test.d.ts | 1 + dist/handlers/css.test.js | 305 +++++++++ dist/handlers/html.d.ts | 13 + dist/handlers/html.js | 183 +++++ dist/handlers/html.test.d.ts | 1 + dist/handlers/html.test.js | 116 ++++ dist/handlers/js-ast.d.ts | 9 + dist/handlers/js-ast.js | 252 +++++++ dist/handlers/js-ast.test.d.ts | 1 + dist/handlers/js-ast.test.js | 1156 ++++++++++++++++++++++++++++++++ dist/handlers/js.d.ts | 9 + dist/handlers/js.js | 106 +++ dist/handlers/js.test.d.ts | 1 + dist/handlers/js.test.js | 60 ++ dist/index.d.ts | 3 + dist/index.js | 85 +++ dist/types.d.ts | 57 ++ dist/types.js | 2 + dist/utils.d.ts | 44 ++ dist/utils.js | 381 +++++++++++ package-lock.json | 4 +- package.json | 2 +- src/handlers/js-ast.ts | 30 +- 29 files changed, 3148 insertions(+), 19 deletions(-) create mode 100644 .tool-versions create mode 100644 dist/config.d.ts create mode 100644 dist/config.js create mode 100644 dist/handlers/css.d.ts create mode 100644 dist/handlers/css.js create mode 100644 dist/handlers/css.test.d.ts create mode 100644 dist/handlers/css.test.js create mode 100644 dist/handlers/html.d.ts create mode 100644 dist/handlers/html.js create mode 100644 dist/handlers/html.test.d.ts create mode 100644 dist/handlers/html.test.js create mode 100644 dist/handlers/js-ast.d.ts create mode 100644 dist/handlers/js-ast.js create mode 100644 dist/handlers/js-ast.test.d.ts create mode 100644 dist/handlers/js-ast.test.js create mode 100644 dist/handlers/js.d.ts create mode 100644 dist/handlers/js.js create mode 100644 dist/handlers/js.test.d.ts create mode 100644 dist/handlers/js.test.js create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/types.d.ts create mode 100644 dist/types.js create mode 100644 dist/utils.d.ts create mode 100644 dist/utils.js diff --git a/.gitignore b/.gitignore index 7864794..cd445dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules coverage .vscode +.idea .env .dccache -dist \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..9c08e2b --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 20.12.0 diff --git a/dist/config.d.ts b/dist/config.d.ts new file mode 100644 index 0000000..6302811 --- /dev/null +++ b/dist/config.d.ts @@ -0,0 +1,7 @@ +import { type Options, type OptionalOptions } from "./types"; +declare class Config { + private options; + constructor(options?: OptionalOptions); + get(): Options; +} +export default Config; diff --git a/dist/config.js b/dist/config.js new file mode 100644 index 0000000..4da935e --- /dev/null +++ b/dist/config.js @@ -0,0 +1,39 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const defaultOptions = { + enable: true, + mode: "random", + buildFolderPath: ".next", + classConversionJsonFolderPath: "./css-obfuscator", + refreshClassConversionJson: false, + classLength: 5, + classPrefix: "", + classSuffix: "", + classIgnore: [], + allowExtensions: [".jsx", ".tsx", ".js", ".ts", ".html", ".rsc"], + contentIgnoreRegexes: [ + /\.jsxs\)\("\w+"/g, + ], + whiteListedFolderPaths: [], + blackListedFolderPaths: ["./.next/cache"], + enableMarkers: false, + markers: ["next-css-obfuscation"], + removeMarkersAfterObfuscated: true, + removeOriginalCss: false, + generatorSeed: "-1", + enableJsAst: false, + logLevel: "info", +}; +class Config { + constructor(options) { + if (!options) { + this.options = defaultOptions; + return; + } + this.options = Object.assign(Object.assign({}, defaultOptions), options); + } + get() { + return this.options; + } +} +exports.default = Config; diff --git a/dist/handlers/css.d.ts b/dist/handlers/css.d.ts new file mode 100644 index 0000000..5aa3027 --- /dev/null +++ b/dist/handlers/css.d.ts @@ -0,0 +1,20 @@ +import { obfuscateMode, SelectorConversion } from "../types"; +declare function extractClassFromSelector(selector: string, replacementClassNames?: (string | undefined)[]): { + selector: string; + extractedClasses: string[]; +}; +declare function createSelectorConversionJson({ selectorConversionJsonFolderPath, buildFolderPath, mode, classNameLength, classPrefix, classSuffix, classIgnore, enableObfuscateMarkerClasses, generatorSeed, }: { + selectorConversionJsonFolderPath: string; + buildFolderPath: string; + mode?: obfuscateMode; + classNameLength?: number; + classPrefix?: string; + classSuffix?: string; + classIgnore?: (string | RegExp)[]; + enableObfuscateMarkerClasses?: boolean; + generatorSeed?: string; +}): void; +declare function copyCssData(targetSelector: string, newSelectorName: string, cssObj: any): any; +declare function renameCssSelector(oldSelector: string, newSelector: string, cssObj: any): any; +declare function obfuscateCss(selectorConversion: SelectorConversion, cssPath: string, replaceOriginalSelector?: boolean, isFullObfuscation?: boolean, outCssPath?: string): void; +export { copyCssData, renameCssSelector, createSelectorConversionJson, obfuscateCss, extractClassFromSelector, }; diff --git a/dist/handlers/css.js b/dist/handlers/css.js new file mode 100644 index 0000000..dd4259d --- /dev/null +++ b/dist/handlers/css.js @@ -0,0 +1,277 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.extractClassFromSelector = exports.obfuscateCss = exports.createSelectorConversionJson = exports.renameCssSelector = exports.copyCssData = void 0; +const tslib_1 = require("tslib"); +const path_1 = tslib_1.__importDefault(require("path")); +const fs_1 = tslib_1.__importDefault(require("fs")); +const css_1 = tslib_1.__importDefault(require("css")); +const recoverable_random_1 = tslib_1.__importDefault(require("recoverable-random")); +const utils_1 = require("../utils"); +let randomStringGeneraterStateCode = undefined; +let currentAlphabetPoistion = 1; +function createNewClassName(mode, className, classPrefix = "", classSuffix = "", classNameLength = 5, seed = Math.random().toString()) { + let newClassName = className; + let { rngStateCode, randomString } = { rngStateCode: "", randomString: "" }; + switch (mode) { + case "random": + ({ rngStateCode, randomString } = (0, utils_1.getRandomString)(classNameLength, seed, undefined, className)); + break; + case "simplify-seedable": + ({ rngStateCode, randomString } = (0, utils_1.seedableSimplifyString)(className, seed, seed + recoverable_random_1.default.stringToSeed(className).toString())); + break; + case "simplify": + randomString = (0, utils_1.simplifyString)(currentAlphabetPoistion); + currentAlphabetPoistion++; + break; + default: + break; + } + newClassName = randomString; + randomStringGeneraterStateCode = rngStateCode; + if (classPrefix) { + newClassName = `${classPrefix}${newClassName}`; + } + if (classSuffix) { + newClassName = `${newClassName}${classSuffix}`; + } + return newClassName; +} +const findActionSelectorsRegex = /(?)/g; +function extractClassFromSelector(selector, replacementClassNames) { + const extractClassRegex = /(?<=[.:!]|(?\/]|(?:\\\[(?:[^\[\]\s])*\\\]))+)(?![\w\-]*\()/g; + const vendorPseudoClassRegexes = [ + /::?-moz-[\w-]+/g, + /::?-ms-[\w-]+/g, + /::?-webkit-[\w-]+/g, + /::?-o-[\w-]+/g, + ]; + selector = selector.replace(findActionSelectorsRegex, (match) => { + return (0, utils_1.createKey)(match); + }); + vendorPseudoClassRegexes.forEach((regex, i) => { + selector = selector.replace(regex, (match) => { + return (0, utils_1.createKey)(match); + }); + }); + let classes = selector.match(extractClassRegex); + if (replacementClassNames !== undefined) { + selector = selector.replace(extractClassRegex, (originalClassName) => { + return replacementClassNames.shift() || originalClassName; + }); + } + selector = (0, utils_1.decodeKey)(selector); + return { + selector: selector, + extractedClasses: classes || [] + }; +} +exports.extractClassFromSelector = extractClassFromSelector; +function getAllSelector(cssObj) { + const selectors = []; + function recursive(rules) { + for (const item of rules) { + if (item.rules) { + recursive(item.rules); + } + else if (item.selectors) { + item.selectors = item.selectors.filter((selector) => selector !== ""); + selectors.push(...item.selectors); + } + } + return null; + } + recursive(cssObj.stylesheet.rules); + return selectors; +} +function createSelectorConversionJson({ selectorConversionJsonFolderPath, buildFolderPath, mode = "random", classNameLength = 5, classPrefix = "", classSuffix = "", classIgnore = [], enableObfuscateMarkerClasses = false, generatorSeed = Math.random().toString().slice(2, 10), }) { + if (!fs_1.default.existsSync(selectorConversionJsonFolderPath)) { + fs_1.default.mkdirSync(selectorConversionJsonFolderPath); + } + const selectorConversion = (0, utils_1.loadAndMergeJsonFiles)(selectorConversionJsonFolderPath); + if (enableObfuscateMarkerClasses) { + selectorConversion[".dark"] = ".dark"; + } + const cssPaths = (0, utils_1.findAllFilesWithExt)(".css", buildFolderPath); + const selectors = []; + cssPaths.forEach((cssPath) => { + const cssContent = fs_1.default.readFileSync(cssPath, "utf-8"); + const cssObj = css_1.default.parse(cssContent); + selectors.push(...getAllSelector(cssObj)); + }); + const uniqueSelectors = [...new Set(selectors)]; + const allowClassStartWith = [".", "#", ":is(", ":where(", ":not(", + ":matches(", ":nth-child(", ":nth-last-child(", + ":nth-of-type(", ":nth-last-of-type(", ":first-child(", + ":last-child(", ":first-of-type(", ":last-of-type(", + ":only-child(", ":only-of-type(", ":empty(", ":link(", + ":visited(", ":active(", ":hover(", ":focus(", ":target(", + ":lang(", ":enabled(", ":disabled(", ":checked(", ":default(", + ":indeterminate(", ":root(", ":before(", + ":after(", ":first-letter(", ":first-line(", ":selection(", + ":read-only(", ":read-write(", ":fullscreen(", ":optional(", + ":required(", ":valid(", ":invalid(", ":in-range(", ":out-of-range(", + ":placeholder-shown(" + ]; + const selectorClassPair = {}; + for (let i = 0; i < uniqueSelectors.length; i++) { + const originalSelector = uniqueSelectors[i]; + const { extractedClasses } = extractClassFromSelector(originalSelector) || []; + selectorClassPair[originalSelector] = extractedClasses; + } + const sortedSelectorClassPair = Object.entries(selectorClassPair) + .sort((a, b) => a[1].length - b[1].length) + .filter((pair) => pair[1].length > 0); + for (let i = 0; i < sortedSelectorClassPair.length; i++) { + const [originalSelector, selectorClasses] = sortedSelectorClassPair[i]; + if (selectorClasses.length == 0) { + continue; + } + let selector = originalSelector; + let classes = selectorClasses; + if (classes && allowClassStartWith.some((start) => selector.startsWith(start))) { + classes = classes.map((className) => { + if (classIgnore.some(regex => { + if (typeof regex === "string") { + return className === regex; + } + return new RegExp(regex).test(className); + })) { + return className; + } + let obfuscatedSelector = selectorConversion[`.${className}`]; + if (!obfuscatedSelector) { + const obfuscatedClass = createNewClassName(mode, className, classPrefix, classSuffix, classNameLength, generatorSeed); + obfuscatedSelector = `.${obfuscatedClass}`; + selectorConversion[`.${className}`] = obfuscatedSelector; + } + return obfuscatedSelector.slice(1); + }); + const { selector: obfuscatedSelector } = extractClassFromSelector(originalSelector, classes); + selectorConversion[originalSelector] = obfuscatedSelector; + } + } + const jsonPath = path_1.default.join(process.cwd(), selectorConversionJsonFolderPath, "conversion.json"); + fs_1.default.writeFileSync(jsonPath, JSON.stringify(selectorConversion, null, 2)); + if ((0, utils_1.duplicationCheck)(Object.keys(selectorConversion))) { + if (mode == "random") { + (0, utils_1.log)("error", "Obfuscation", "Duplicated class names found in the conversion JSON, try to increase the class name length / open an issue on GitHub https://github.com/soranoo/next-css-obfuscator/issues"); + } + else { + (0, utils_1.log)("error", "Obfuscation", "Duplicated class names found in the conversion JSON, please open an issue on GitHub https://github.com/soranoo/next-css-obfuscator/issues"); + } + } +} +exports.createSelectorConversionJson = createSelectorConversionJson; +function copyCssData(targetSelector, newSelectorName, cssObj) { + function recursive(rules) { + return rules.map((item) => { + if (item.rules) { + let newRules = recursive(item.rules); + if (Array.isArray(newRules)) { + newRules = newRules.flat(); + } + return Object.assign(Object.assign({}, item), { rules: newRules }); + } + else if (item.selectors) { + item.selectors = item.selectors.filter((selector) => selector !== ""); + if (item.selectors.includes(targetSelector)) { + const newRule = JSON.parse(JSON.stringify(item)); + newRule.selectors = [newSelectorName]; + return [item, newRule]; + } + else { + return item; + } + } + else { + return item; + } + }); + } + cssObj.stylesheet.rules = recursive(cssObj.stylesheet.rules).flat(); + return cssObj; +} +exports.copyCssData = copyCssData; +function renameCssSelector(oldSelector, newSelector, cssObj) { + function recursive(rules) { + return rules.map((item) => { + if (item.rules) { + return Object.assign(Object.assign({}, item), { rules: recursive(item.rules) }); + } + else if (item.selectors) { + item.selectors = item.selectors.filter((selector) => selector !== ""); + let updatedSelectors = item.selectors.map((selector) => selector === oldSelector ? newSelector : selector); + return Object.assign(Object.assign({}, item), { selectors: updatedSelectors }); + } + else { + return item; + } + }); + } + cssObj.stylesheet.rules = recursive(cssObj.stylesheet.rules); + return cssObj; +} +exports.renameCssSelector = renameCssSelector; +function obfuscateCss(selectorConversion, cssPath, replaceOriginalSelector = false, isFullObfuscation = false, outCssPath) { + if (!outCssPath) { + outCssPath = cssPath; + } + else if (!fs_1.default.existsSync(path_1.default.dirname(outCssPath))) { + fs_1.default.mkdirSync(path_1.default.dirname(outCssPath)); + } + let cssContent = fs_1.default.readFileSync(cssPath, "utf-8"); + let cssObj = css_1.default.parse(cssContent); + const cssRulesCount = cssObj.stylesheet.rules.length; + if (isFullObfuscation) { + Object.keys(selectorConversion).forEach((key) => { + utils_1.usedKeyRegistery.add(key); + }); + } + else { + Object.keys(selectorConversion).forEach((key) => { + if (key.startsWith(":")) { + utils_1.usedKeyRegistery.add(key); + } + }); + const actionSelectors = getAllSelector(cssObj).filter((selector) => selector.match(findActionSelectorsRegex)); + actionSelectors.forEach((actionSelector) => { + utils_1.usedKeyRegistery.add(actionSelector); + }); + const tailwindCssChildSelectors = getAllSelector(cssObj).filter((selector) => selector.startsWith(".\\[")); + tailwindCssChildSelectors.forEach((tailwindCssChildSelector) => { + utils_1.usedKeyRegistery.add(tailwindCssChildSelector); + }); + const universalSelectors = getAllSelector(cssObj).filter((selector) => selector.includes(">")); + universalSelectors.forEach((universalSelector) => { + utils_1.usedKeyRegistery.add(universalSelector); + }); + } + utils_1.usedKeyRegistery.forEach((key) => { + const originalSelectorName = key; + const obfuscatedSelectorName = selectorConversion[key]; + if (obfuscatedSelectorName) { + if (replaceOriginalSelector) { + cssObj = renameCssSelector(originalSelectorName, selectorConversion[key], cssObj); + } + else { + cssObj = copyCssData(originalSelectorName, selectorConversion[key], cssObj); + } + } + }); + if (replaceOriginalSelector) { + (0, utils_1.log)("info", "CSS rules:", `Modified ${utils_1.usedKeyRegistery.size} CSS rules to ${(0, utils_1.getFilenameFromPath)(cssPath)}`); + } + else { + (0, utils_1.log)("info", "CSS rules:", `Added ${cssObj.stylesheet.rules.length - cssRulesCount} new CSS rules to ${(0, utils_1.getFilenameFromPath)(cssPath)}`); + } + const cssOptions = { + compress: true, + }; + const cssObfuscatedContent = css_1.default.stringify(cssObj, cssOptions); + const sizeBefore = Buffer.byteLength(cssContent, "utf8"); + fs_1.default.writeFileSync(outCssPath, cssObfuscatedContent); + const sizeAfter = Buffer.byteLength(cssObfuscatedContent, "utf8"); + const percentChange = Math.round(((sizeAfter) / sizeBefore) * 100); + (0, utils_1.log)("success", "CSS obfuscated:", `Size from ${sizeBefore} to ${sizeAfter} bytes (${percentChange}%) in ${(0, utils_1.getFilenameFromPath)(cssPath)}`); +} +exports.obfuscateCss = obfuscateCss; diff --git a/dist/handlers/css.test.d.ts b/dist/handlers/css.test.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/handlers/css.test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/handlers/css.test.js b/dist/handlers/css.test.js new file mode 100644 index 0000000..6b6d972 --- /dev/null +++ b/dist/handlers/css.test.js @@ -0,0 +1,305 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = require("tslib"); +const vitest_1 = require("vitest"); +const css_1 = tslib_1.__importDefault(require("css")); +const css_2 = require("./css"); +const testCss = ` +.s0-1 { + background: #181810; + color: #181811; +} + +@media (min-width: 640px) +{ + .s1-1 + { + background: #181812; + color: #181813; + } + + @media (min-width: 768px) + { + .s2-1, .s2-1-1 { + background: #181814; + color: #181815; + }, + .s2-1, .s2-1-1 { + background: #181814; + color: #181815; + }, + .s2-2, .s2-2-2 { + background: #181816; + color: #181817; + }, + .s2-3 { + background: #181818; + color: #181819; + } + } + + .s1-2 + { + background: #181820; + color: #181821; + } +} + +.s0-2 { + background: #181822; + color: #181823; +} +`; +(0, vitest_1.describe)("renameCssSelector", () => { + (0, vitest_1.it)("should rename the CSS selector (single selector, no nested rule)", () => { + const cssObj = css_1.default.parse(testCss); + const oldSelector = ".s1-1"; + const newSelector = ".s1-1-new"; + const expectedOutput = [{ "type": "rule", "selectors": [".s0-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181810", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181811", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 19 } } }], "position": { "start": { "line": 2, "column": 1 }, "end": { "line": 5, "column": 2 } } }, { "type": "media", "media": "(min-width: 640px)", "rules": [{ "type": "rule", "selectors": [".s1-1-new"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181812", "position": { "start": { "line": 11, "column": 9 }, "end": { "line": 11, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181813", "position": { "start": { "line": 12, "column": 9 }, "end": { "line": 12, "column": 23 } } }], "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 13, "column": 6 } } }, { "type": "media", "media": "(min-width: 768px)", "rules": [{ "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2", ".s2-2-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }], "position": { "start": { "line": 15, "column": 5 }, "end": { "line": 33, "column": 6 } } }, { "type": "rule", "selectors": [".s1-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181820", "position": { "start": { "line": 37, "column": 9 }, "end": { "line": 37, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181821", "position": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 23 } } }], "position": { "start": { "line": 35, "column": 5 }, "end": { "line": 39, "column": 6 } } }], "position": { "start": { "line": 7, "column": 1 }, "end": { "line": 40, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }]; + const result = (0, css_2.renameCssSelector)(oldSelector, newSelector, cssObj); + (0, vitest_1.expect)(result.stylesheet.rules).toEqual(expectedOutput); + }); + (0, vitest_1.it)("should rename the CSS selector (multiple nested media queries)", () => { + const cssObj = css_1.default.parse(testCss); + const oldSelector = ".s2-2"; + const newSelector = ".s2-2-new"; + const expectedOutput = [{ "type": "rule", "selectors": [".s0-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181810", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181811", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 19 } } }], "position": { "start": { "line": 2, "column": 1 }, "end": { "line": 5, "column": 2 } } }, { "type": "media", "media": "(min-width: 640px)", "rules": [{ "type": "rule", "selectors": [".s1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181812", "position": { "start": { "line": 11, "column": 9 }, "end": { "line": 11, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181813", "position": { "start": { "line": 12, "column": 9 }, "end": { "line": 12, "column": 23 } } }], "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 13, "column": 6 } } }, { "type": "media", "media": "(min-width: 768px)", "rules": [{ "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2-new", ".s2-2-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }], "position": { "start": { "line": 15, "column": 5 }, "end": { "line": 33, "column": 6 } } }, { "type": "rule", "selectors": [".s1-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181820", "position": { "start": { "line": 37, "column": 9 }, "end": { "line": 37, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181821", "position": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 23 } } }], "position": { "start": { "line": 35, "column": 5 }, "end": { "line": 39, "column": 6 } } }], "position": { "start": { "line": 7, "column": 1 }, "end": { "line": 40, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }]; + const result = (0, css_2.renameCssSelector)(oldSelector, newSelector, cssObj); + (0, vitest_1.expect)(result.stylesheet.rules).toEqual(expectedOutput); + }); +}); +(0, vitest_1.describe)("copyCssData", () => { + (0, vitest_1.it)("should copy the CSS data (single selector, no nested rule)", () => { + const cssObj = css_1.default.parse(testCss); + const targetSelector = ".s0-2"; + const newSelectorName = ".s0-2-new"; + const expectedOutput = [{ "type": "rule", "selectors": [".s0-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181810", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181811", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 19 } } }], "position": { "start": { "line": 2, "column": 1 }, "end": { "line": 5, "column": 2 } } }, { "type": "media", "media": "(min-width: 640px)", "rules": [{ "type": "rule", "selectors": [".s1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181812", "position": { "start": { "line": 11, "column": 9 }, "end": { "line": 11, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181813", "position": { "start": { "line": 12, "column": 9 }, "end": { "line": 12, "column": 23 } } }], "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 13, "column": 6 } } }, { "type": "media", "media": "(min-width: 768px)", "rules": [{ "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2", ".s2-2-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }], "position": { "start": { "line": 15, "column": 5 }, "end": { "line": 33, "column": 6 } } }, { "type": "rule", "selectors": [".s1-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181820", "position": { "start": { "line": 37, "column": 9 }, "end": { "line": 37, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181821", "position": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 23 } } }], "position": { "start": { "line": 35, "column": 5 }, "end": { "line": 39, "column": 6 } } }], "position": { "start": { "line": 7, "column": 1 }, "end": { "line": 40, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2-new"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }]; + const result = (0, css_2.copyCssData)(targetSelector, newSelectorName, cssObj); + (0, vitest_1.expect)(result.stylesheet.rules).toEqual(expectedOutput); + }); + (0, vitest_1.it)("should copy the CSS data (multiple nested rules)", () => { + const cssObj = css_1.default.parse(testCss); + const targetSelector = ".s2-3"; + const newSelectorName = ".s2-3-new"; + const expectedOutput = [{ "type": "rule", "selectors": [".s0-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181810", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181811", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 19 } } }], "position": { "start": { "line": 2, "column": 1 }, "end": { "line": 5, "column": 2 } } }, { "type": "media", "media": "(min-width: 640px)", "rules": [{ "type": "rule", "selectors": [".s1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181812", "position": { "start": { "line": 11, "column": 9 }, "end": { "line": 11, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181813", "position": { "start": { "line": 12, "column": 9 }, "end": { "line": 12, "column": 23 } } }], "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 13, "column": 6 } } }, { "type": "media", "media": "(min-width: 768px)", "rules": [{ "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2", ".s2-2-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3-new"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }], "position": { "start": { "line": 15, "column": 5 }, "end": { "line": 33, "column": 6 } } }, { "type": "rule", "selectors": [".s1-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181820", "position": { "start": { "line": 37, "column": 9 }, "end": { "line": 37, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181821", "position": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 23 } } }], "position": { "start": { "line": 35, "column": 5 }, "end": { "line": 39, "column": 6 } } }], "position": { "start": { "line": 7, "column": 1 }, "end": { "line": 40, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }]; + const result = (0, css_2.copyCssData)(targetSelector, newSelectorName, cssObj); + (0, vitest_1.expect)(result.stylesheet.rules).toEqual(expectedOutput); + }); + (0, vitest_1.it)("should copy the CSS data (multiple selector in same rule)", () => { + const cssObj = css_1.default.parse(testCss); + const targetSelector = ".s2-2-2"; + const newSelectorName = ".s2-2-2-new"; + const expectedOutput = [{ "type": "rule", "selectors": [".s0-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181810", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181811", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 19 } } }], "position": { "start": { "line": 2, "column": 1 }, "end": { "line": 5, "column": 2 } } }, { "type": "media", "media": "(min-width: 640px)", "rules": [{ "type": "rule", "selectors": [".s1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181812", "position": { "start": { "line": 11, "column": 9 }, "end": { "line": 11, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181813", "position": { "start": { "line": 12, "column": 9 }, "end": { "line": 12, "column": 23 } } }], "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 13, "column": 6 } } }, { "type": "media", "media": "(min-width: 768px)", "rules": [{ "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2", ".s2-2-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2-2-new"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }], "position": { "start": { "line": 15, "column": 5 }, "end": { "line": 33, "column": 6 } } }, { "type": "rule", "selectors": [".s1-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181820", "position": { "start": { "line": 37, "column": 9 }, "end": { "line": 37, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181821", "position": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 23 } } }], "position": { "start": { "line": 35, "column": 5 }, "end": { "line": 39, "column": 6 } } }], "position": { "start": { "line": 7, "column": 1 }, "end": { "line": 40, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }]; + const result = (0, css_2.copyCssData)(targetSelector, newSelectorName, cssObj); + (0, vitest_1.expect)(result.stylesheet.rules).toEqual(expectedOutput); + }); + (0, vitest_1.it)("should copy the CSS data (same selector with different declarations)", () => { + const cssObj = css_1.default.parse(testCss); + const targetSelector = ".s2-1"; + const newSelectorName = ".s2-1-new"; + const expectedOutput = [{ "type": "rule", "selectors": [".s0-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181810", "position": { "start": { "line": 3, "column": 5 }, "end": { "line": 3, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181811", "position": { "start": { "line": 4, "column": 5 }, "end": { "line": 4, "column": 19 } } }], "position": { "start": { "line": 2, "column": 1 }, "end": { "line": 5, "column": 2 } } }, { "type": "media", "media": "(min-width: 640px)", "rules": [{ "type": "rule", "selectors": [".s1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181812", "position": { "start": { "line": 11, "column": 9 }, "end": { "line": 11, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181813", "position": { "start": { "line": 12, "column": 9 }, "end": { "line": 12, "column": 23 } } }], "position": { "start": { "line": 9, "column": 5 }, "end": { "line": 13, "column": 6 } } }, { "type": "media", "media": "(min-width: 768px)", "rules": [{ "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1-new"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 18, "column": 13 }, "end": { "line": 18, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 19, "column": 13 }, "end": { "line": 19, "column": 27 } } }], "position": { "start": { "line": 17, "column": 9 }, "end": { "line": 20, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1", ".s2-1-1"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-1-new"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181814", "position": { "start": { "line": 22, "column": 13 }, "end": { "line": 22, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181815", "position": { "start": { "line": 23, "column": 13 }, "end": { "line": 23, "column": 27 } } }], "position": { "start": { "line": 20, "column": 10 }, "end": { "line": 24, "column": 10 } } }, { "type": "rule", "selectors": [".s2-2", ".s2-2-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181816", "position": { "start": { "line": 26, "column": 13 }, "end": { "line": 26, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181817", "position": { "start": { "line": 27, "column": 13 }, "end": { "line": 27, "column": 27 } } }], "position": { "start": { "line": 24, "column": 10 }, "end": { "line": 28, "column": 10 } } }, { "type": "rule", "selectors": [".s2-3"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181818", "position": { "start": { "line": 30, "column": 13 }, "end": { "line": 30, "column": 32 } } }, { "type": "declaration", "property": "color", "value": "#181819", "position": { "start": { "line": 31, "column": 13 }, "end": { "line": 31, "column": 27 } } }], "position": { "start": { "line": 28, "column": 10 }, "end": { "line": 32, "column": 10 } } }], "position": { "start": { "line": 15, "column": 5 }, "end": { "line": 33, "column": 6 } } }, { "type": "rule", "selectors": [".s1-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181820", "position": { "start": { "line": 37, "column": 9 }, "end": { "line": 37, "column": 28 } } }, { "type": "declaration", "property": "color", "value": "#181821", "position": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 23 } } }], "position": { "start": { "line": 35, "column": 5 }, "end": { "line": 39, "column": 6 } } }], "position": { "start": { "line": 7, "column": 1 }, "end": { "line": 40, "column": 2 } } }, { "type": "rule", "selectors": [".s0-2"], "declarations": [{ "type": "declaration", "property": "background", "value": "#181822", "position": { "start": { "line": 43, "column": 5 }, "end": { "line": 43, "column": 24 } } }, { "type": "declaration", "property": "color", "value": "#181823", "position": { "start": { "line": 44, "column": 5 }, "end": { "line": 44, "column": 19 } } }], "position": { "start": { "line": 42, "column": 1 }, "end": { "line": 45, "column": 2 } } }]; + const result = (0, css_2.copyCssData)(targetSelector, newSelectorName, cssObj); + (0, vitest_1.expect)(result.stylesheet.rules).toEqual(expectedOutput); + }); +}); +(0, vitest_1.describe)("extractClassFromSelector", () => { + (0, vitest_1.it)("should extract single class from simple selector", () => { + const sample = ".example htmlTag"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["example"] + }); + }); + (0, vitest_1.test)("should extract multiple classes from complex selector", () => { + const sample = ":is(.some-class .some-class\\:bg-dark::-moz-placeholder)[data-active=\'true\']"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["some-class", "some-class\\:bg-dark"] + }); + }); + (0, vitest_1.test)("should handle selector with no classes", () => { + const sample = "div"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: [] + }); + }); + (0, vitest_1.test)("should handle selector with action pseudo-classes and not extract them", () => { + const sample = ".btn:hover .btn-active::after"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["btn", "btn-active"] + }); + }); + (0, vitest_1.test)("should handle selector with vendor pseudo-classes and not extract them", () => { + const sample = ".btn-moz:-moz-focusring .btn-ms::-ms-placeholder .btn-webkit::-webkit-placeholder .btn-o::-o-placeholder"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["btn-moz", "btn-ms", "btn-webkit", "btn-o"] + }); + }); + (0, vitest_1.test)("should handle selector with escaped characters", () => { + const sample = ".escaped\\:class:action"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["escaped\\:class"] + }); + }); + (0, vitest_1.test)("should handle selector with multiple classes separated by spaces", () => { + const sample = ".class1 .class2 .class3"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1", "class2", "class3"] + }); + }); + (0, vitest_1.test)("should handle selector with multiple classes separated by commas", () => { + const sample = ".class1, .class2, .class3"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1", "class2", "class3"] + }); + }); + (0, vitest_1.test)("should handle selector with a combination of classes and ids", () => { + const sample = ".class1 #id .class2"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1", "class2"] + }); + }); + (0, vitest_1.test)("should handle [attribute] selector", () => { + const sample = ".class1[data-attr=\"value\"] .class2[data-attr='value']"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1", "class2"] + }); + }); + (0, vitest_1.test)("should handle action pseudo-class selector correctly", () => { + const sample = ".class1\\:hover\\:class2:after .class3\\:hover\\:class4:after:hover :is(.class5 .class6\\:hover\\:class7:hover:after) :is(.hover\\:class8\\:class9):after>:last-child"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1\\:hover\\:class2", "class3\\:hover\\:class4", "class5", "class6\\:hover\\:class7", "hover\\:class8\\:class9"] + }); + }); + (0, vitest_1.test)("should ignore [attribute] selector that not in the same scope as class", () => { + const sample = ":is(.class1 .class2\\:class3\\:\\!class4)[aria-selected=\"true\"]"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1", "class2\\:class3\\:\\!class4"] + }); + }); + (0, vitest_1.test)("should return null for invalid input types", () => { + (0, vitest_1.expect)(() => (0, css_2.extractClassFromSelector)(null)).toThrow(TypeError); + (0, vitest_1.expect)(() => (0, css_2.extractClassFromSelector)(undefined)).toThrow(TypeError); + (0, vitest_1.expect)(() => (0, css_2.extractClassFromSelector)(123)).toThrow(TypeError); + }); + (0, vitest_1.test)("should handle Tailwind CSS important selector '!'", () => { + const sample = ".\\!my-0 .some-class\\:\\!bg-white"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["\\!my-0", "some-class\\:\\!bg-white"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS selector with start with '-'", () => { + const sample = ".-class-1"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["-class-1"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS selector with '.' at the number", () => { + const sample = ".class-0\\.5 .class-1\\.125"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class-0\\.5", "class-1\\.125"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS selector with '/' at the number", () => { + const sample = ".class-1\\/2"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class-1\\/2"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS selector with '%' at the number", () => { + const sample = ".\\[\\.class1\\]\\:to-85\\%"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["\\[\\.class1\\]\\:to-85\\%"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS universal selector", () => { + const sample = ".\\*\\:class1 .class2\\*\\:class3 .class4\\*:.class5"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["\\*\\:class1", "class2\\*\\:class3", "class4\\*", "class5"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS [custom parameter] selector", () => { + const sample = ".class1\\[100\\] .class2-\\[200\\]"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1\\[100\\]", "class2-\\[200\\]"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS [custom parameter] selector with escaped characters", () => { + const sample = ".class1\\[1em\\] .class2-\\[2em\\] .class3\\[3\\%\\] .class4-\\[4\\%\\]"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1\\[1em\\]", "class2-\\[2em\\]", "class3\\[3\\%\\]", "class4-\\[4\\%\\]"] + }); + }); + (0, vitest_1.test)("should handle complex Tailwind CSS [custom parameter] selector", () => { + const sample = ".w-\\[calc\\(10\\%\\+5px\\)\\]"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["w-\\[calc\\(10\\%\\+5px\\)\\]"] + }); + }); + (0, vitest_1.test)("should ignore Tailwind CSS [custom parameter] selector that not in the same scope as class", () => { + const sample = ":is(.class1)[100]"; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: ["class1"] + }); + }); + (0, vitest_1.test)("should handle Tailwind CSS [custom selector] child elements selector", () => { + const sample = ` + .\\[\\&\\>\\class1\\]:after + .\\[\\&_class2\\]:hover + .\\[\\&_\\.class3\\]\\:class4 + .\\[\\&_\\#id1\\]::moz-placeholder + .\\[\\&_\\#id2\\]\\:class5.\\[\\&_\\#id\\]\\:class6 + `; + const result = (0, css_2.extractClassFromSelector)(sample); + (0, vitest_1.expect)(result).toEqual({ + selector: sample, + extractedClasses: [ + "\\[\\&\\>\\class1\\]", "\\[\\&_class2\\]", + "\\[\\&_\\.class3\\]\\:class4", "\\[\\&_\\#id1\\]", + "\\[\\&_\\#id2\\]\\:class5", "\\[\\&_\\#id\\]\\:class6" + ] + }); + }); +}); diff --git a/dist/handlers/html.d.ts b/dist/handlers/html.d.ts new file mode 100644 index 0000000..1370fa4 --- /dev/null +++ b/dist/handlers/html.d.ts @@ -0,0 +1,13 @@ +import { SelectorConversion } from "../types"; +declare function findHtmlTagContents(content: string, targetTag: string, targetClass?: string | null): string[]; +declare function findHtmlTagContentsByClass(content: string, targetClass: string): string[]; +declare function obfuscateHtmlClassNames({ html, selectorConversion, obfuscateMarkerClass, contentIgnoreRegexes, }: { + html: string; + selectorConversion: SelectorConversion; + obfuscateMarkerClass?: string; + contentIgnoreRegexes?: RegExp[]; +}): { + obfuscatedContent: string; + usedKeys: string[]; +}; +export { findHtmlTagContents, findHtmlTagContentsByClass, obfuscateHtmlClassNames, }; diff --git a/dist/handlers/html.js b/dist/handlers/html.js new file mode 100644 index 0000000..60cf143 --- /dev/null +++ b/dist/handlers/html.js @@ -0,0 +1,183 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.obfuscateHtmlClassNames = exports.findHtmlTagContentsByClass = exports.findHtmlTagContents = void 0; +const tslib_1 = require("tslib"); +const htmlparser2 = tslib_1.__importStar(require("htmlparser2")); +const utils_1 = require("../utils"); +const js_1 = require("./js"); +function findHtmlTagContentsRecursive(content, targetTag, targetClass = null, foundTagContents = [], deep = 0, maxDeep = -1) { + let contentAfterTag = content; + const startTagWithClassRegexStr = targetClass ? + `(<\\w+?\\s+?class\\s*=\\s*['\"][^'\"]*?\\b${targetClass}\\b)` + : ""; + const startTagRegexStr = `(<${targetTag}[\\s|>])`; + const endTagRegexStr = `(<\/${targetTag}>)`; + const clearContentBeforeStartTagRegex = new RegExp(`${startTagWithClassRegexStr ? startTagWithClassRegexStr + ".*|" + startTagRegexStr : startTagRegexStr + ".*"}`, "i"); + const contentAfterStartTagMatch = contentAfterTag.match(clearContentBeforeStartTagRegex); + if (contentAfterStartTagMatch) { + contentAfterTag = contentAfterStartTagMatch[0]; + } + let endTagCont = 0; + const endTagContRegex = new RegExp(endTagRegexStr, "gi"); + const endTagContMatch = contentAfterTag.match(endTagContRegex); + if (endTagContMatch) { + endTagCont = endTagContMatch.length; + } + let closeTagPoition = 0; + const tagPatternRegex = new RegExp(`${startTagWithClassRegexStr ? startTagWithClassRegexStr + "|" + startTagRegexStr : startTagRegexStr}|${endTagRegexStr}`, "gi"); + const tagPatternMatch = contentAfterTag.match(tagPatternRegex); + if (tagPatternMatch) { + let tagCount = 0; + let markedPosition = false; + for (let i = 0; i < tagPatternMatch.length; i++) { + if (tagPatternMatch[i].startsWith(")`, "i"); + const remainingHtmlTagMatch = remainingHtml.match(remainingHtmlTagRegex); + if (remainingHtmlTagMatch) { + if (maxDeep === -1 || deep < maxDeep) { + return findHtmlTagContentsRecursive(remainingHtml, targetTag, targetClass, foundTagContents, deep + 1, maxDeep); + } + else { + (0, utils_1.log)("warn", "HTML search:", "Max deep reached, recursive break"); + return foundTagContents; + } + } + } + return foundTagContents; +} +function findHtmlTagContents(content, targetTag, targetClass = null) { + return findHtmlTagContentsRecursive(content, targetTag, targetClass); +} +exports.findHtmlTagContents = findHtmlTagContents; +function findHtmlTagContentsByClass(content, targetClass) { + const regex = new RegExp(`(<(\\w+)\\s+class\\s*=\\s*['\"][^'\"]*?\\b${targetClass}\\b)`, "i"); + const match = content.match(regex); + if (match) { + const tag = match[2]; + return findHtmlTagContents(content, tag, targetClass); + } + else { + return []; + } +} +exports.findHtmlTagContentsByClass = findHtmlTagContentsByClass; +function obfuscateHtmlClassNames({ html, selectorConversion, obfuscateMarkerClass = "", contentIgnoreRegexes = [], }) { + const voidTags = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]; + let modifiedHtml = ""; + let insideObsClassScope = false; + let ObsClassScopeTagCount = 0; + let ObsClassScopeTag = ""; + let scriptContent = ""; + let isScriptTag = false; + const usedKeys = []; + const parser = new htmlparser2.Parser({ + onprocessinginstruction(name, data) { + modifiedHtml += `<${data}>`; + }, + onopentag(tagName, attribs) { + if (tagName === "script") { + isScriptTag = true; + scriptContent = ""; + } + if (attribs.class) { + if (!insideObsClassScope && obfuscateMarkerClass && attribs.class.includes(obfuscateMarkerClass)) { + insideObsClassScope = true; + ObsClassScopeTag = tagName; + } + if (insideObsClassScope || !obfuscateMarkerClass) { + const { obfuscatedContent, usedKeys: _usedKeys } = (0, utils_1.obfuscateKeys)(selectorConversion, attribs.class, [], true); + usedKeys.push(..._usedKeys); + attribs.class = obfuscatedContent; + } + } + if (insideObsClassScope && tagName === ObsClassScopeTag) { + ObsClassScopeTagCount++; + } + modifiedHtml += `<${tagName}`; + for (const key in attribs) { + modifiedHtml += ` ${key}="${attribs[key]}"`; + } + if (voidTags.includes(tagName)) { + modifiedHtml += " />"; + } + else { + modifiedHtml += ">"; + } + }, + oncomment(comment) { + modifiedHtml += ``; + }, + ontext(text) { + if (isScriptTag) { + scriptContent += text; + } + else { + modifiedHtml += text; + } + }, + onclosetag(tagname) { + if (voidTags.includes(tagname)) { + return; + } + if (tagname === "script" && isScriptTag) { + isScriptTag = false; + let obfuscatedScriptContent = scriptContent; + Object.keys(selectorConversion).forEach((key) => { + const className = key.slice(1); + const obfuscatedJs = (0, js_1.obfuscateJs)(obfuscatedScriptContent, className, { [key]: selectorConversion[key] }, "{a HTML file path}", contentIgnoreRegexes); + if (obfuscatedJs !== obfuscatedScriptContent) { + obfuscatedScriptContent = obfuscatedJs; + usedKeys.push(key); + } + }); + modifiedHtml += `${obfuscatedScriptContent}`; + } + modifiedHtml += ``; + if (insideObsClassScope && tagname === ObsClassScopeTag) { + ObsClassScopeTagCount--; + } + if (ObsClassScopeTagCount === 0) { + insideObsClassScope = false; + } + } + }, { decodeEntities: true }); + parser.write(html); + parser.end(); + return { + obfuscatedContent: modifiedHtml, + usedKeys: Array.from(new Set(usedKeys)) + }; +} +exports.obfuscateHtmlClassNames = obfuscateHtmlClassNames; diff --git a/dist/handlers/html.test.d.ts b/dist/handlers/html.test.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/handlers/html.test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/handlers/html.test.js b/dist/handlers/html.test.js new file mode 100644 index 0000000..67ec097 --- /dev/null +++ b/dist/handlers/html.test.js @@ -0,0 +1,116 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const vitest_1 = require("vitest"); +const html_1 = require("./html"); +(0, vitest_1.describe)("findHtmlTagContentsByClass", () => { + const content = `
12345678
901234
56789
0123456
`; + (0, vitest_1.it)("should return the correct content within the tag that with a given class", () => { + const targetClass = "test1"; + const expectedOutput = ['
12345678
901234
56789
']; + const result = (0, html_1.findHtmlTagContentsByClass)(content, targetClass); + (0, vitest_1.expect)(result).toEqual(expectedOutput); + }); + (0, vitest_1.it)("should return empty array if no content found", () => { + const targetClass = "test5"; + const expectedOutput = []; + const result = (0, html_1.findHtmlTagContentsByClass)(content, targetClass); + (0, vitest_1.expect)(result).toEqual(expectedOutput); + }); +}); +(0, vitest_1.describe)("obfuscateHtmlClassNames", () => { + (0, vitest_1.it)("should obfuscate class names correctly", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo"]); + }); + (0, vitest_1.it)("should handle nested tags with obfuscate class", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a" }; + const keyClass = "key"; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion, obfuscateMarkerClass: keyClass }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo"]); + }); + (0, vitest_1.it)("should not obfuscate class names outside of obfuscate class scope", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a", ".bar": ".b" }; + const keyClass = "key"; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion, obfuscateMarkerClass: keyClass }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([]); + }); + (0, vitest_1.it)("should handle script tags", () => { + const html = ``; + const selectorConversion = { ".fol": ".a", ".foo": ".b" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion, obfuscateMarkerClass: "" }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(``); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".fol", ".foo"]); + }); + (0, vitest_1.it)("should handle void tags", () => { + const html = ``; + const selectorConversion = { ".foo": ".a" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(``); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo"]); + }); + (0, vitest_1.it)("should handle comments", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo"]); + }); + (0, vitest_1.it)("should handle HTML without classes", () => { + const html = "
"; + const selectorConversion = {}; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual("
"); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([]); + }); + (0, vitest_1.it)("should handle empty HTML", () => { + const html = ""; + const selectorConversion = { ".foo": ".a" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(""); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([]); + }); + (0, vitest_1.it)("should handle HTML with multiple classes in one element", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a", ".bar": ".b", ".baz": ".c" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo", ".bar", ".baz"]); + }); + (0, vitest_1.it)("should handle HTML with nested structures and multiple classes", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a", ".bar": ".b", ".baz": ".c" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo", ".bar", ".baz"]); + }); + (0, vitest_1.it)("should handle HTML with obfuscate marker class", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a" }; + const obfuscateMarkerClass = "key"; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion, obfuscateMarkerClass }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo"]); + }); + (0, vitest_1.it)("should handle HTML with multiple classes and obfuscate marker class", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a", ".bar": ".b", ".baz": ".c" }; + const obfuscateMarkerClass = "key"; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion, obfuscateMarkerClass }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo", ".bar", ".baz"]); + }); + (0, vitest_1.it)("should handle HTML instruction", () => { + const html = `
`; + const selectorConversion = { ".foo": ".a" }; + const result = (0, html_1.obfuscateHtmlClassNames)({ html, selectorConversion }); + (0, vitest_1.expect)(result.obfuscatedContent).toEqual(`
`); + (0, vitest_1.expect)(result.usedKeys).to.deep.equal([".foo"]); + }); +}); diff --git a/dist/handlers/js-ast.d.ts b/dist/handlers/js-ast.d.ts new file mode 100644 index 0000000..0d84e51 --- /dev/null +++ b/dist/handlers/js-ast.d.ts @@ -0,0 +1,9 @@ +import { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import { type SelectorConversion } from "../types"; +declare function obfuscateJsWithAst(code: string, selectorConversion: SelectorConversion | undefined, startingKeys?: string[], stripUnnecessarySpace?: boolean): { + obfuscatedCode: string; + usedKeys: Set; +}; +declare function searchStringLiterals(path: NodePath, callback: (str: string) => void | string, scannedNodes?: Set): NodePath | undefined; +export { searchStringLiterals, obfuscateJsWithAst, }; diff --git a/dist/handlers/js-ast.js b/dist/handlers/js-ast.js new file mode 100644 index 0000000..f8cdd1e --- /dev/null +++ b/dist/handlers/js-ast.js @@ -0,0 +1,252 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.obfuscateJsWithAst = exports.searchStringLiterals = void 0; +const tslib_1 = require("tslib"); +const parser = tslib_1.__importStar(require("@babel/parser")); +const traverse_1 = tslib_1.__importDefault(require("@babel/traverse")); +const t = tslib_1.__importStar(require("@babel/types")); +const generator_1 = tslib_1.__importDefault(require("@babel/generator")); +const utils_1 = require("../utils"); +function obfuscateJsWithAst(code, selectorConversion, startingKeys = [], stripUnnecessarySpace = true) { + const ast = parser.parse(code, { sourceType: "module", plugins: ["jsx"] }); + const usedKeys = new Set(); + (0, traverse_1.default)(ast, { + ObjectProperty(path) { + if (t.isIdentifier(path.node.key) && path.node.key.name === "className") { + searchStringLiterals(path.get("value"), (str) => { + if (startingKeys.length > 0 && !startingKeys.includes(str)) { + return str; + } + if (!selectorConversion) { + return "{{obfuscated}}"; + } + str = stripUnnecessarySpace ? str.replace(/\s+/g, " ").trim() : str; + const { obfuscatedContent, usedKeys: obfuscateUsedKeys } = (0, utils_1.obfuscateKeys)(selectorConversion, str); + if (obfuscatedContent !== str) { + obfuscateUsedKeys.forEach(key => usedKeys.add(key)); + return obfuscatedContent; + } + }); + } + }, + }); + const options = { + compact: true, + concise: true, + retainLines: false, + comments: false, + minified: true, + }; + const obfuscatedCode = (0, generator_1.default)(ast, options, code); + return { + obfuscatedCode: obfuscatedCode.code, + usedKeys: usedKeys + }; +} +exports.obfuscateJsWithAst = obfuscateJsWithAst; +function searchStringLiterals(path, callback, scannedNodes = new Set()) { + if (path.node && scannedNodes.has(path.node)) { + return; + } + scannedNodes.add(path.node); + if (t.isBlockStatement(path.node)) { + const body = path.get("body"); + if (Array.isArray(body)) { + body.forEach(nodePath => { + switch (nodePath.node.type) { + case "ReturnStatement": + case "IfStatement": + case "SwitchStatement": + case "ExpressionStatement": + case "ForStatement": + case "WhileStatement": + case "TryStatement": + searchStringLiterals(nodePath, callback, scannedNodes); + break; + } + }); + } + else { + searchStringLiterals(body, callback, scannedNodes); + } + } + else if (t.isReturnStatement(path.node)) { + const argument = path.get("argument"); + if (argument && !Array.isArray(argument)) { + searchStringLiterals(argument, callback); + } + else if (Array.isArray(argument)) { + argument.forEach(arg => { + searchStringLiterals(arg, callback, scannedNodes); + }); + } + } + else if (t.isBinaryExpression(path.node)) { + const left = path.get("left"); + const right = path.get("right"); + if (left && !Array.isArray(left)) { + searchStringLiterals(left, callback, scannedNodes); + } + if (right && !Array.isArray(right)) { + searchStringLiterals(right, callback, scannedNodes); + } + } + else if (t.isStringLiteral(path.node)) { + const replacement = callback(path.node.value); + if (replacement) { + path.replaceWith(t.stringLiteral(replacement)); + } + } + else if (t.isCallExpression(path.node)) { + const callee = path.get("callee"); + if (callee && !Array.isArray(callee)) { + searchStringLiterals(callee, callback, scannedNodes); + } + const args = path.get("arguments"); + if (Array.isArray(args)) { + args.forEach(arg => { + if (t.isStringLiteral(arg.node)) { + const replacement = callback(arg.node.value); + if (replacement) { + arg.replaceWith(t.stringLiteral(replacement)); + } + } + else { + searchStringLiterals(arg, callback, scannedNodes); + } + }); + } + } + else if (t.isConditionalExpression(path.node)) { + const test = path.get("test"); + const consequent = path.get("consequent"); + const alternate = path.get("alternate"); + if (test && !Array.isArray(test)) { + searchStringLiterals(test, callback, scannedNodes); + } + if (consequent && !Array.isArray(consequent)) { + searchStringLiterals(consequent, callback, scannedNodes); + } + if (alternate && !Array.isArray(alternate)) { + searchStringLiterals(alternate, callback, scannedNodes); + } + } + else if (t.isIfStatement(path.node)) { + const test = path.get("test"); + const consequent = path.get("consequent"); + const alternate = path.get("alternate"); + if (test && !Array.isArray(test)) { + searchStringLiterals(test, callback, scannedNodes); + } + if (consequent && !Array.isArray(consequent)) { + searchStringLiterals(consequent, callback, scannedNodes); + } + if (alternate && !Array.isArray(alternate)) { + searchStringLiterals(alternate, callback, scannedNodes); + } + } + else if (t.isObjectExpression(path.node)) { + const properties = path.get("properties"); + if (Array.isArray(properties)) { + properties.forEach(prop => { + searchStringLiterals(prop, callback, scannedNodes); + }); + } + } + else if (t.isObjectProperty(path.node)) { + const value = path.get("value"); + if (value && !Array.isArray(value)) { + searchStringLiterals(value, callback, scannedNodes); + } + } + else if (t.isArrayExpression(path.node)) { + const elements = path.get("elements"); + if (Array.isArray(elements)) { + elements.forEach(element => { + searchStringLiterals(element, callback, scannedNodes); + }); + } + } + else if (t.isSwitchStatement(path.node)) { + const cases = path.get("cases"); + if (Array.isArray(cases)) { + cases.forEach(c => { + searchStringLiterals(c, callback, scannedNodes); + }); + } + } + else if (t.isSwitchCase(path.node)) { + const consequent = path.get("consequent"); + if (Array.isArray(consequent)) { + consequent.forEach(c => { + if (t.isReturnStatement(c.node)) { + searchStringLiterals(c, callback, scannedNodes); + } + }); + } + } + else if (t.isFunctionDeclaration(path.node)) { + const body = path.get("body"); + if (body && !Array.isArray(body)) { + searchStringLiterals(body, callback, scannedNodes); + } + } + else if (t.isForStatement(path.node)) { + const body = path.get("body"); + if (body && !Array.isArray(body)) { + searchStringLiterals(body, callback, scannedNodes); + } + } + else if (t.isExpressionStatement(path.node)) { + const expression = path.get("expression"); + if (expression && !Array.isArray(expression)) { + searchStringLiterals(expression, callback, scannedNodes); + } + } + else if (t.isAssignmentExpression(path.node)) { + const right = path.get("right"); + if (right && !Array.isArray(right)) { + searchStringLiterals(right, callback, scannedNodes); + } + } + else if (t.isWhileStatement(path.node)) { + const body = path.get("body"); + if (body && !Array.isArray(body)) { + searchStringLiterals(body, callback, scannedNodes); + } + } + else if (t.isSpreadElement(path.node)) { + const argument = path.get("argument"); + if (argument && !Array.isArray(argument)) { + searchStringLiterals(argument, callback, scannedNodes); + } + } + else if (t.isArrowFunctionExpression(path.node)) { + const body = path.get("body"); + if (body && !Array.isArray(body)) { + searchStringLiterals(body, callback, scannedNodes); + } + } + else if (t.isTryStatement(path.node)) { + const block = path.get("block"); + const handler = path.get("handler"); + if (block && !Array.isArray(block)) { + searchStringLiterals(block, callback, scannedNodes); + } + if (handler && !Array.isArray(handler)) { + const handlerBody = handler.get("body"); + if (handlerBody && !Array.isArray(handlerBody)) { + searchStringLiterals(handlerBody, callback, scannedNodes); + } + } + } + else { + path.traverse({ + Identifier(innerPath) { + searchStringLiterals(innerPath, callback, scannedNodes); + }, + }); + } + return path; +} +exports.searchStringLiterals = searchStringLiterals; diff --git a/dist/handlers/js-ast.test.d.ts b/dist/handlers/js-ast.test.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/handlers/js-ast.test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/handlers/js-ast.test.js b/dist/handlers/js-ast.test.js new file mode 100644 index 0000000..6c1c546 --- /dev/null +++ b/dist/handlers/js-ast.test.js @@ -0,0 +1,1156 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = require("tslib"); +const vitest_1 = require("vitest"); +const traverse_1 = tslib_1.__importDefault(require("@babel/traverse")); +const parser = tslib_1.__importStar(require("@babel/parser")); +const generator_1 = tslib_1.__importDefault(require("@babel/generator")); +const js_ast_1 = require("./js-ast"); +function stripCode(code) { + return code.replace(/\s/g, ""); +} +(0, vitest_1.describe)("searchStringLiterals", () => { + function findStartPointNode(ast) { + let startPointNode; + (0, traverse_1.default)(ast, { + FunctionDeclaration(path) { + var _a; + if (((_a = path.node.id) === null || _a === void 0 ? void 0 : _a.name) === "startPoint") { + startPointNode = path.get('body'); + } + }, + }); + return startPointNode; + } + (0, vitest_1.it)("should handle string literals correctly", () => { + const code = ` + const a = "test"; + + function startPoint() { + return "function"; + } + `; + const expectedCode = ` + const a = "test"; + + function startPoint() { + return "{{found}}"; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["function"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should not handle string literals in comments", () => { + const code = ` + // const a = "test"; + + function startPoint() { + return "function"; + } + `; + const expectedCode = ` + // const a = "test"; + + function startPoint() { + return "{{found}}"; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["function"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle variable declarations correctly", () => { + const code = ` + const a = "test"; + let b = "test2"; + var c = "test3"; + + function startPoint() { + const d = "test4"; + let e = "test5"; + var f = "test6"; + return a + b + c + d + e + f; + } + `; + const expectedCode = ` + const a = "{{found}}"; + let b = "{{found}}"; + var c = "{{found}}"; + + function startPoint() { + const d = "{{found}}"; + let e = "{{found}}"; + var f = "{{found}}"; + return a + b + c + d + e + f; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["test", "test2", "test3", "test4", "test5", "test6"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle variable declarations with multiple variables correctly", () => { + const code = ` + const a = "test", b = "test2"; + + function startPoint() { + return a + b; + } + `; + const expectedCode = ` + const a = "{{found}}", b = "{{found}}"; + + function startPoint() { + return a + b; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["test", "test2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle local variables correctly", () => { + const code = ` + const globalVar = "out"; + + function startPoint() { + const localVar = "world"; + return "hello" + localVar; + } + `; + const expectedCode = ` + const globalVar = "out"; + + function startPoint() { + const localVar = "{{found}}"; + return "{{found}}" + localVar; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "world"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle global variables correctly", () => { + const code = ` + const globalVar = "world"; + + function startPoint() { + const localVar = "local"; + return "hello" + globalVar; + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function startPoint() { + const localVar = "local"; + return "{{found}}" + globalVar; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "world"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle boolean expressions correctly", () => { + const code = ` + const globalVar = "world"; + + function startPoint() { + const localBool = true; + const localVar = "again"; + return "hello" + (localBool ? globalVar : localVar); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function startPoint() { + const localBool = true; + const localVar = "{{found}}"; + return "{{found}}" + (localBool ? globalVar : localVar); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "world", "again"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle if statements correctly", () => { + const code = ` + const globalVar = "world"; + + function startPoint() { + const localBool = true; + const localVar = "again"; + if (localBool) { + return "hello" + globalVar; + } + return "hello" + localVar; + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function startPoint() { + const localBool = true; + const localVar = "{{found}}"; + if (localBool) { + return "{{found}}" + globalVar; + } + return "{{found}}" + localVar; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "world", "hello", "again"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle function calls correctly", () => { + const code = ` + const globalVar = "global"; + + function call() { + return "world"; + } + + function startPoint() { + return "hello" + globalVar + call(); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function call() { + return "{{found}}"; + } + + function startPoint() { + return "{{found}}" + globalVar + call(); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "global", "world"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle function calls with arguments correctly", () => { + const code = ` + const globalVar = "global"; + + function call(arg) { + return arg; + } + + function startPoint() { + return "hello" + globalVar + call("world"); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function call(arg) { + return arg; + } + + function startPoint() { + return "{{found}}" + globalVar + call("{{found}}"); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "global", "world"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle function calls with multiple arguments correctly", () => { + const code = ` + const globalVar = "global"; + + function call(arg, arg2) { + return arg + arg2; + } + + function startPoint() { + return "hello" + globalVar + call("world", "again"); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function call(arg, arg2) { + return arg + arg2; + } + + function startPoint() { + return "{{found}}" + globalVar + call("{{found}}", "{{found}}"); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "global", "world", "again"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle function calls with nested function calls correctly", () => { + const code = ` + const globalVar = "global"; + + function call(arg, arg2) { + return arg + arg2; + } + + function call2(arg) { + return arg; + } + + function startPoint() { + return "hello" + globalVar + call(call2("world"), "again"); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function call(arg, arg2) { + return arg + arg2; + } + + function call2(arg) { + return arg; + } + + function startPoint() { + return "{{found}}" + globalVar + call(call2("{{found}}"), "{{found}}"); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "global", "world", "again"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle function calls with boolean expressions correctly", () => { + const code = ` + const globalVar = "global"; + + function call(arg, arg2) { + return arg + arg2; + } + + function call2(arg) { + return arg; + } + + function startPoint() { + const bool = true; + return "hello" + globalVar + call(bool ? "world1" : call2("world2"), "again"); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + function call(arg, arg2) { + return arg + arg2; + } + + function call2(arg) { + return arg; + } + + function startPoint() { + const bool = true; + return "{{found}}" + globalVar + call(bool ? "{{found}}" : call2("{{found}}"), "{{found}}"); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "global", "world1", "world2", "again"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle arrow function calls correctly", () => { + const code = ` + const globalVar = "global"; + + const call = (arg, arg2) => { + return arg + arg2; + }; + + function startPoint() { + return "hello" + globalVar + call("world", "again"); + } + `; + const expectedCode = ` + const globalVar = "{{found}}"; + + const call = (arg, arg2) => { + return arg + arg2; + }; + + function startPoint() { + return "{{found}}" + globalVar + call("{{found}}", "{{found}}"); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["hello", "global", "world", "again"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle document.{method} correctly", () => { + const code = ` + function startPoint() { + return document.getElementById("element"); + } + `; + const expectedCode = ` + function startPoint() { + return document.getElementById("{{found}}"); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle document.{method} with variables correctly", () => { + const code = ` + function startPoint() { + const id = "element"; + return document.getElementById(id); + } + `; + const expectedCode = ` + function startPoint() { + const id = "{{found}}"; + return document.getElementById(id); + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions correctly", () => { + const code = ` + function startPoint() { + const obj = { + key: "value" + }; + return obj.key; + } + `; + const expectedCode = ` + function startPoint() { + const obj = { + key: "{{found}}" + }; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["value"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions' operations correctly", () => { + const code = ` + function startPoint() { + const obj = { + key: "value" + }; + obj.key = "another"; + obj["key2"] = "another2"; + return obj.key; + } + `; + const expectedCode = ` + function startPoint() { + const obj = { + key: "{{found}}" + }; + obj.key = "{{found}}"; + obj["key2"] = "{{found}}"; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["another", "another2", "value"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions with variables correctly", () => { + const code = ` + function startPoint() { + const value = "value"; + const obj = { + key: value + }; + return obj.key; + } + `; + const expectedCode = ` + function startPoint() { + const value = "{{found}}"; + const obj = { + key: value + }; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["value"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions with function calls correctly", () => { + const code = ` + function call() { + return "call"; + } + + function startPoint() { + const obj = { + key: call() + }; + return obj.key; + } + `; + const expectedCode = ` + function call() { + return "{{found}}"; + } + + function startPoint() { + const obj = { + key: call() + }; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["call"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions with function calls with arguments correctly", () => { + const code = ` + function call(arg) { + return arg; + } + + function startPoint() { + const obj = { + key: call("call") + }; + return obj.key; + } + `; + const expectedCode = ` + function call(arg) { + return arg; + } + + function startPoint() { + const obj = { + key: call("{{found}}") + }; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["call"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions with function calls with boolean expressions correctly", () => { + const code = ` + function call(arg) { + return arg; + } + + function startPoint() { + const value = "value"; + const bool = true; + const obj = { + key: call(bool ? value : "another") + }; + return obj.key; + } + `; + const expectedCode = ` + function call(arg) { + return arg; + } + + function startPoint() { + const value = "{{found}}"; + const bool = true; + const obj = { + key: call(bool ? value : "{{found}}") + }; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["value", "another"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle object expressions with a variable key correctly", () => { + const code = ` + function call(arg) { + return arg; + } + + function startPoint() { + const key = "value"; + const bool = true; + const obj = { + key: call(bool ? key : "another") + }; + return obj.key; + } + `; + const expectedCode = ` + function call(arg) { + return arg; + } + + function startPoint() { + const key = "{{found}}"; + const bool = true; + const obj = { + key: call(bool ? key : "{{found}}") + }; + return obj.key; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["value", "another", "{{found}}"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle array expressions correctly", () => { + const code = ` + function startPoint() { + const arr = ["element_1", "element_2"]; + return arr[0]; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}", "{{found}}"]; + return arr[0]; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle array concatenation correctly and no duplicates node should be scaned", () => { + const code = ` + function startPoint() { + const arr = ["element_1"]; + const arr2 = ["element_2"]; + const arr3 = arr.concat(arr2); + const arr4 = [...arr, ...arr3]; + return arr4; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}"]; + const arr2 = ["{{found}}"]; + const arr3 = arr.concat(arr2); + const arr4 = [...arr, ...arr3]; + return arr4; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle array expressions with variables correctly", () => { + const code = ` + function startPoint() { + const element1 = "element_1"; + const element2 = "element_2"; + const arr = [element1, element2]; + return arr[0]; + } + `; + const expectedCode = ` + function startPoint() { + const element1 = "{{found}}"; + const element2 = "{{found}}"; + const arr = [element1, element2]; + return arr[0]; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle array expressions with function calls correctly", () => { + const code = ` + function call() { + return "element_1"; + } + + function startPoint() { + const arr = [call(), "element_2"]; + return arr[0]; + } + `; + const expectedCode = ` + function call() { + return "{{found}}"; + } + + function startPoint() { + const arr = [call(), "{{found}}"]; + return arr[0]; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle array expressions with function calls with arguments correctly", () => { + const code = ` + function call(arg) { + return arg; + } + + function startPoint() { + const arr = [call("element_1"), "element_2"]; + return arr[0]; + } + `; + const expectedCode = ` + function call(arg) { + return arg; + } + + function startPoint() { + const arr = [call("{{found}}"), "{{found}}"]; + return arr[0]; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle array expressions with function calls with boolean expressions correctly", () => { + const code = ` + function call(arg) { + return arg; + } + + function startPoint() { + const key = "value"; + const bool = true; + const arr = [call(bool ? key : "another"), "element_2"]; + return arr[0]; + } + `; + const expectedCode = ` + function call(arg) { + return arg; + } + + function startPoint() { + const key = "{{found}}"; + const bool = true; + const arr = [call(bool ? key : "{{found}}"), "{{found}}"]; + return arr[0]; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["value", "another", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle switch statements correctly", () => { + const code = ` + function startPoint() { + const key = "value"; + let result = ""; + switch (key) { + case "value": + const fakeVar = "fake"; + result = "value"; + case "another": + const fakeVar2 = "fake2"; + return "another"; + default: + const fakeVar3 = "fake3"; + return "default"; + } + } + `; + const expectedCode = ` + function startPoint() { + const key = "value"; + let result = ""; + switch (key) { + case "value": + const fakeVar = "fake"; + result = "value"; + case "another": + const fakeVar2 = "fake2"; + return "{{found}}"; + default: + const fakeVar3 = "fake3"; + return "{{found}}"; + } + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["another", "default"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle for statements correctly", () => { + const code = ` + function startPoint() { + const arr = ["element_1", "element_2"]; + const fakeArr = ["element_3", "element_4"]; + let result = "result"; + for (let i = 0; i < arr.length; i++) { + const fakeArr2 = ["element_5", "element_6"]; + result += arr[i]; + } + return result; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}", "{{found}}"]; + const fakeArr = ["element_3", "element_4"]; + let result = "{{found}}"; + for (let i = 0; i < arr.length; i++) { + const fakeArr2 = ["element_5", "element_6"]; + result += arr[i]; + } + return result; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2", "result"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle foreach statements correctly", () => { + const code = ` + function startPoint() { + const arr = ["element_1", "element_2"]; + let result = "result"; + arr.forEach((element) => { + const fakeVar = "fake"; + result += element; + }); + return result; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}", "{{found}}"]; + let result = "{{found}}"; + arr.forEach(element => { + const fakeVar = "fake"; + result += element; + }); + return result; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2", "result"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle map statements correctly", () => { + const code = ` + function startPoint() { + const arr = ["element_1", "element_2"]; + const result = arr.map((element) => { + const fakeVar = "fake"; + return element; + }); + return result; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}", "{{found}}"]; + const result = arr.map(element => { + const fakeVar = "fake"; + return element; + }); + return result; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle while statements correctly", () => { + const code = ` + function startPoint() { + const arr = ["element_1", "element_2"]; + const fakeArr = ["element_3", "element_4"]; + let result = "result"; + let i = 0; + while (i < arr.length) { + const fakeArr2 = ["element_5", "element_6"]; + result += arr[i]; + i++; + } + return result; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}", "{{found}}"]; + const fakeArr = ["element_3", "element_4"]; + let result = "{{found}}"; + let i = 0; + while (i < arr.length) { + const fakeArr2 = ["element_5", "element_6"]; + result += arr[i]; + i++; + } + return result; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2", "result"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); + (0, vitest_1.it)("should handle try catch statements correctly", () => { + const code = ` + function startPoint() { + const arr = ["element_1", "element_2"]; + const arr2 = ["element_3", "element_4"]; + let result = "result"; + try { + result = arr[0]; + throw new Error("error"); + } catch (e) { + result = arr2[0]; + result = e.message; + } + return result; + } + `; + const expectedCode = ` + function startPoint() { + const arr = ["{{found}}", "{{found}}"]; + const arr2 = ["{{found}}", "{{found}}"]; + let result = "{{found}}"; + try { + result = arr[0]; + throw new Error("error"); + } catch (e) { + result = arr2[0]; + result = e.message; + } + return result; + } + `; + const ast = parser.parse(code); + let result = []; + (0, js_ast_1.searchStringLiterals)(findStartPointNode(ast), (str) => { + result.push(str); + return "{{found}}"; + }); + const expected = ["element_1", "element_2", "element_3", "element_4", "result"]; + (0, vitest_1.expect)(result).toEqual(expected); + (0, vitest_1.expect)(stripCode((0, generator_1.default)(ast, {}, code).code)).toEqual(stripCode(expectedCode)); + }); +}); +(0, vitest_1.describe)("obfuscateJsWithAst", () => { + (0, vitest_1.it)("should handle basic real world example 1", () => { + const code = `(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{6773:function(e,t,n){Promise.resolve().then(n.bind(n,1845)),Promise.resolve().then(n.bind(n,7388)),Promise.resolve().then(n.bind(n,6016)),Promise.resolve().then(n.bind(n,1120)),Promise.resolve().then(n.bind(n,1255)),Promise.resolve().then(n.t.bind(n,5935,23)),Promise.resolve().then(n.t.bind(n,3710,23)),Promise.resolve().then(n.t.bind(n,3385,23)),Promise.resolve().then(n.bind(n,6212)),Promise.resolve().then(n.bind(n,1267)),Promise.resolve().then(n.bind(n,5322)),Promise.resolve().then(n.bind(n,9149))},6212:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return d}});var r=n(3827),i=n(703),l=n(8792),a={src:"/_next/static/media/some.svg",height:42,width:42,blurWidth:0,blurHeight:0},s=n(4090),o=n(7907);function d(){(0,o.usePathname)();let[e,t]=(0,s.useState)(!1),n=(0,s.useCallback)((e,t)=>{if(e.id===t)return!0;for(let r=0;r{t(n(document.body,"NotFoundPage"))},[n]),e?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)("header",{className:"h-[4.4dvw] size-full bg-transparent px-[1.41dvw] flex items-center",children:(0,r.jsx)(l.default,{href:"/",className:"flex items-center flex-shrink-0",children:(0,r.jsx)(i.default,{className:"w-auto h-[1.88dvw]",src:a,alt:"Logo"})})})})}},1267:function(e,t,n){"use strict";n.r(t),n.d(t,{default:function(){return o}});var r=n(3827),i=n(8792),l=n(7907),a=n(4090),s=n(6314);function o(){let e=(0,l.usePathname)(),[t,n]=(0,a.useState)(!1),o=(0,a.useCallback)((e,t)=>{if(e.id===t)return!0;for(let n=0;n{n(o(document.body,"NotFoundPage"))},[o]);let d=[{href:"/",label:"Home"},{href:"/tag1",label:"tag1"},{href:"/tag2",label:"tag2"},{href:"/tag3",label:"tag3"},{href:"/tag4",label:"tag4"},],[u,c]=(0,a.useState)(()=>{let t=d.find(t=>t.href===e);return t?t.label:"label"}),[h,f]=(0,a.useState)(()=>{if(e.startsWith("/tag1"))return"tag1";{let t=d.find(t=>t.href===e);return t?t.label:"label"}});return(0,a.useEffect)(()=>{e.startsWith("/tag1")&&f("tag1")},[e]),(0,a.useEffect)(()=>{e.startsWith("/tag1/")&&(f("tag1"),c("tag1"))},[e]),t?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)("div",{className:"z-0 w-[11dvw] h-dvh [&_.side-box]:absolute [&_.side-box]:left-[0.94dvw] [&_.side-box]:top-0 [&_.side-box]:-z-10 [&_.side-box]:h-full [&_.side-box]:w-[calc(100%-0.94dvw)] [&_.side-box]:rounded-tl-[0.94dvw] [&_.side-box]:rounded-bl-[0.94dvw] [&_.side-box]:bg-gradient-to-r [&_.side-box]:from-[#2858ff] [&_.side-box]:to-85%",children:(0,r.jsx)("div",{className:" flex flex-col items-start size-full *:relative *:w-full *:font-bold [&_a]:flex [&_a]:items-center [&_a]:w-full [&_a]:pe-[2.82dvw] [&_a]:h-[2.82dvw] [&_a]:transition-[padding_color] [&_a]:ease-bounce [&_a]:duration-300 [&_#side-box-line]:absolute [&_#side-box-line]:left-0 [&_#side-box-line]:top-0 [&_#side-box-line]:-z-10 [&_#side-box-line]:h-full [&_#side-box-line]:w-[0.235dvw] [&_#side-box-line]:transition-opacity [&_#side-box-line]:duration-0 [&_#side-box-line]:rounded-tr-full [&_#side-box-line]:rounded-br-full [&_#side-box-line]:bg-[#2858ff] ",children:d.map(t=>(0,r.jsx)(s.E.div,{onHoverStart:()=>c(t.label),onHoverEnd:()=>c(h),onClick:()=>f(t.label),children:(0,r.jsxs)(i.default,{href:t.href,className:t.href===e||e.startsWith("/tag1")&&"/tag1"===t.href?"text-white ps-[2.115dvw]":"text-white/50 ps-[1.41dvw]",children:[t.href===e||e.startsWith("/tag1/")&&"/tag1"===t.href?(0,r.jsx)(s.E.div,{transition:{type:"spring",duration:.65,mass:.5},layoutId:"sideBox",className:"side-box"}):null,t.label,t.label===u||e.startsWith("/tag1/")&&"/tag1/"===t.href?(0,r.jsx)(s.E.div,{transition:{type:"spring",duration:.8},layoutId:"sideBoxLine",id:"side-box-line"}):null,]})},t.href))})})})}},9149:function(e,t,n){"use strict";n.r(t);var r=n(4404),i=n(4090),l=n(7717);let a=e=>{let{color:t,height:n,crawl:r,crawlSpeed:a,initialPosition:s,easing:o,speed:d,shadow:u,template:c,zIndex:h=99999999,delay:f}=e,$=null!=t?t:"#29d";return(u||void 0===u)&&(u||"box-shadow:0 0 10px ".concat($,",0 0 5px ").concat($)),i.useEffect(()=>{let e;function t(){clearTimeout(e),e=setTimeout(l.start,null!=f?f:200)}function n(){clearTimeout(e),l.done()}l.configure({trickle:null==r||r,trickleSpeed:null!=a?a:200,minimum:null!=s?s:.55+.2*Math.random(),easing:null!=o?o:"ease-out",speed:null!=d?d:180,template:null!=c?c:'
'});var i=document.querySelectorAll("html");function u(e){try{let r=e.target,l=function(e){for(;e&&"a"!==e.tagName.toLowerCase();)e=e.parentElement;return e}(r),a=null==l?void 0:l.href;if(a){var s;let o=window.location.href,d="_blank"===l.target,u=a.startsWith("blob:"),c=function(e,t){let n=new URL(e),r=new URL(t);if(n.hostname===r.hostname&&n.pathname===r.pathname&&n.search===r.search){let i=n.hash,l=r.hash;return i!==l&&n.href.replace(i,"")===r.href.replace(l,"")}return!1}(o,a),h;a===o||c||d||u||e.ctrlKey?(t(),n(),[].forEach.call(i,function(e){e.classList.remove("nprogress-busy")})):(t(),h=(s=window.history).pushState,s.pushState=function(){return n(),[].forEach.call(i,function(e){e.classList.remove("nprogress-busy")}),h.apply(s,arguments)})}}catch(f){t(),n()}}return document.addEventListener("click",u),()=>{document.removeEventListener("click",u)}},[r,a,f,o,s,d,c]),null};t.default=a,a.propTypes={color:r.string,height:r.number,crawl:r.bool,crawlSpeed:r.number,initialPosition:r.number,easing:r.string,speed:r.number,delay:r.number,template:r.string,shadow:r.oneOfType([r.string,r.bool]),zIndex:r.number}},5322:function(e,t,n){"use strict";n.r(t);var r=n(3827),i=n(4090);t.default=()=>{let e=(0,i.useRef)(null);return(0,i.useEffect)(()=>{let t=e.current,n=null==t?void 0:t.getContext("2d"),r={x:.5*window.innerWidth,y:.5*window.innerHeight},i={pointsNumber:8,widthFactor:4,spring:.35,friction:.48},l=Array(i.pointsNumber);for(let a=0;a{r.x=e,r.y=t},o=()=>{e.current&&(e.current.width=window.innerWidth,e.current.height=window.innerHeight)},d=e=>{if(n&&(n.strokeStyle="#e2ecfc"),t&&(null==n||n.clearRect(0,0,t.width,t.height)),l.forEach((e,t)=>{let n=0===t?r:l[t-1],a=0===t?.4*i.spring:i.spring;e.dx+=(n.x-e.x)*a,e.dy+=(n.y-e.y)*a,e.dx*=i.friction,e.dy*=i.friction,e.x+=e.dx,e.y+=e.dy}),n){n.lineCap="round",n.beginPath(),n.moveTo(l[0].x,l[0].y);for(let a=1;a{o()},c=e=>{s(e.pageX,e.pageY)},h=e=>{s(e.pageX,e.pageY)},f=e=>{s(e.targetTouches[0].pageX,e.targetTouches[0].pageY)};return window.addEventListener("click",c),window.addEventListener("mousemove",h),window.addEventListener("touchmove",f),window.addEventListener("resize",u),o(),d(0),()=>{window.removeEventListener("click",c),window.removeEventListener("mousemove",h),window.removeEventListener("touchmove",f),window.removeEventListener("resize",u)}},[]),(0,r.jsx)("canvas",{ref:e})}},3385:function(){}},function(e){e.O(0,[314,250,134,336,971,69,744],function(){return e(e.s=6773)}),_N_E=e.O()},]);`; + const expectedCode = `(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{6773:function(e,t,n){Promise.resolve().then(n.bind(n,1845)),Promise.resolve().then(n.bind(n,7388)),Promise.resolve().then(n.bind(n,6016)),Promise.resolve().then(n.bind(n,1120)),Promise.resolve().then(n.bind(n,1255)),Promise.resolve().then(n.t.bind(n,5935,23)),Promise.resolve().then(n.t.bind(n,3710,23)),Promise.resolve().then(n.t.bind(n,3385,23)),Promise.resolve().then(n.bind(n,6212)),Promise.resolve().then(n.bind(n,1267)),Promise.resolve().then(n.bind(n,5322)),Promise.resolve().then(n.bind(n,9149))},6212:function(e,t,n){\"usestrict\";n.r(t),n.d(t,{default:function(){returnd}});varr=n(3827),i=n(703),l=n(8792),a={src:\"/_next/static/media/some.svg\",height:42,width:42,blurWidth:0,blurHeight:0},s=n(4090),o=n(7907);functiond(){(0,o.usePathname)();let[e,t]=(0,s.useState)(!1),n=(0,s.useCallback)((e,t)=>{if(e.id===t)return!0;for(letr=0;r{t(n(document.body,\"NotFoundPage\"))},[n]),e?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)(\"header\",{className:\"{{obfuscated}}\",children:(0,r.jsx)(l.default,{href:\"/\",className:\"{{obfuscated}}\",children:(0,r.jsx)(i.default,{className:\"{{obfuscated}}\",src:a,alt:\"Logo\"})})})})}},1267:function(e,t,n){\"usestrict\";n.r(t),n.d(t,{default:function(){returno}});varr=n(3827),i=n(8792),l=n(7907),a=n(4090),s=n(6314);functiono(){lete=(0,l.usePathname)(),[t,n]=(0,a.useState)(!1),o=(0,a.useCallback)((e,t)=>{if(e.id===t)return!0;for(letn=0;n{n(o(document.body,\"NotFoundPage\"))},[o]);letd=[{href:\"/\",label:\"Home\"},{href:\"/tag1\",label:\"tag1\"},{href:\"/tag2\",label:\"tag2\"},{href:\"/tag3\",label:\"tag3\"},{href:\"/tag4\",label:\"tag4\"}],[u,c]=(0,a.useState)(()=>{lett=d.find(t=>t.href===e);returnt?t.label:\"label\"}),[h,f]=(0,a.useState)(()=>{if(e.startsWith(\"/tag1\"))return\"tag1\";{lett=d.find(t=>t.href===e);returnt?t.label:\"label\"}});return(0,a.useEffect)(()=>{e.startsWith(\"/tag1\")&&f(\"tag1\")},[e]),(0,a.useEffect)(()=>{e.startsWith(\"/tag1/\")&&(f(\"tag1\"),c(\"tag1\"))},[e]),t?null:(0,r.jsx)(r.Fragment,{children:(0,r.jsx)(\"div\",{className:\"{{obfuscated}}\",children:(0,r.jsx)(\"div\",{className:\"{{obfuscated}}\",children:d.map(t=>(0,r.jsx)(s.E.div,{onHoverStart:()=>c(t.label),onHoverEnd:()=>c(h),onClick:()=>f(t.label),children:(0,r.jsxs)(i.default,{href:t.href,className:t.href===e||e.startsWith(\"/tag1\")&&\"/tag1\"===t.href?\"{{obfuscated}}\":\"{{obfuscated}}\",children:[t.href===e||e.startsWith(\"/tag1/\")&&\"/tag1\"===t.href?(0,r.jsx)(s.E.div,{transition:{type:\"spring\",duration:.65,mass:.5},layoutId:\"sideBox\",className:\"{{obfuscated}}\"}):null,t.label,t.label===u||e.startsWith(\"/tag1/\")&&\"/tag1/\"===t.href?(0,r.jsx)(s.E.div,{transition:{type:\"spring\",duration:.8},layoutId:\"sideBoxLine\",id:\"side-box-line\"}):null]})},t.href))})})})}},9149:function(e,t,n){\"usestrict\";n.r(t);varr=n(4404),i=n(4090),l=n(7717);leta=e=>{let{color:t,height:n,crawl:r,crawlSpeed:a,initialPosition:s,easing:o,speed:d,shadow:u,template:c,zIndex:h=99999999,delay:f}=e,$=null!=t?t:\"#29d\";return(u||void0===u)&&(u||\"box-shadow:0010px\".concat($,\",005px\").concat($)),i.useEffect(()=>{lete;functiont(){clearTimeout(e),e=setTimeout(l.start,null!=f?f:200)}functionn(){clearTimeout(e),l.done()}l.configure({trickle:null==r||r,trickleSpeed:null!=a?a:200,minimum:null!=s?s:.55+.2*Math.random(),easing:null!=o?o:\"ease-out\",speed:null!=d?d:180,template:null!=c?c:\"\"});vari=document.querySelectorAll(\"html\");functionu(e){try{letr=e.target,l=function(e){for(;e&&\"a\"!==e.tagName.toLowerCase();)e=e.parentElement;returne}(r),a=null==l?void0:l.href;if(a){vars;leto=window.location.href,d=\"_blank\"===l.target,u=a.startsWith(\"blob:\"),c=function(e,t){letn=newURL(e),r=newURL(t);if(n.hostname===r.hostname&&n.pathname===r.pathname&&n.search===r.search){leti=n.hash,l=r.hash;returni!==l&&n.href.replace(i,\"\")===r.href.replace(l,\"\")}return!1}(o,a),h;a===o||c||d||u||e.ctrlKey?(t(),n(),[].forEach.call(i,function(e){e.classList.remove(\"nprogress-busy\")})):(t(),h=(s=window.history).pushState,s.pushState=function(){returnn(),[].forEach.call(i,function(e){e.classList.remove(\"nprogress-busy\")}),h.apply(s,arguments)})}}catch(f){t(),n()}}returndocument.addEventListener(\"click\",u),()=>{document.removeEventListener(\"click\",u)}},[r,a,f,o,s,d,c]),null};t.default=a,a.propTypes={color:r.string,height:r.number,crawl:r.bool,crawlSpeed:r.number,initialPosition:r.number,easing:r.string,speed:r.number,delay:r.number,template:r.string,shadow:r.oneOfType([r.string,r.bool]),zIndex:r.number}},5322:function(e,t,n){\"usestrict\";n.r(t);varr=n(3827),i=n(4090);t.default=()=>{lete=(0,i.useRef)(null);return(0,i.useEffect)(()=>{lett=e.current,n=null==t?void0:t.getContext(\"2d\"),r={x:.5*window.innerWidth,y:.5*window.innerHeight},i={pointsNumber:8,widthFactor:4,spring:.35,friction:.48},l=Array(i.pointsNumber);for(leta=0;a{r.x=e,r.y=t},o=()=>{e.current&&(e.current.width=window.innerWidth,e.current.height=window.innerHeight)},d=e=>{if(n&&(n.strokeStyle=\"#e2ecfc\"),t&&(null==n||n.clearRect(0,0,t.width,t.height)),l.forEach((e,t)=>{letn=0===t?r:l[t-1],a=0===t?.4*i.spring:i.spring;e.dx+=(n.x-e.x)*a,e.dy+=(n.y-e.y)*a,e.dx*=i.friction,e.dy*=i.friction,e.x+=e.dx,e.y+=e.dy}),n){n.lineCap=\"round\",n.beginPath(),n.moveTo(l[0].x,l[0].y);for(leta=1;a{o()},c=e=>{s(e.pageX,e.pageY)},h=e=>{s(e.pageX,e.pageY)},f=e=>{s(e.targetTouches[0].pageX,e.targetTouches[0].pageY)};returnwindow.addEventListener(\"click\",c),window.addEventListener(\"mousemove\",h),window.addEventListener(\"touchmove\",f),window.addEventListener(\"resize\",u),o(),d(0),()=>{window.removeEventListener(\"click\",c),window.removeEventListener(\"mousemove\",h),window.removeEventListener(\"touchmove\",f),window.removeEventListener(\"resize\",u)}},[]),(0,r.jsx)(\"canvas\",{ref:e})}},3385:function(){}},function(e){e.O(0,[314,250,134,336,971,69,744],function(){returne(e.s=6773)}),_N_E=e.O()}]);`; + const { obfuscatedCode } = (0, js_ast_1.obfuscateJsWithAst)(code, undefined); + (0, vitest_1.expect)(stripCode(obfuscatedCode)).toEqual(stripCode(expectedCode)); + }); +}); diff --git a/dist/handlers/js.d.ts b/dist/handlers/js.d.ts new file mode 100644 index 0000000..90f501f --- /dev/null +++ b/dist/handlers/js.d.ts @@ -0,0 +1,9 @@ +import { SelectorConversion } from "../types"; +declare function searchForwardComponent(content: string): never[] | RegExpMatchArray; +declare function obfuscateForwardComponentJs(searchContent: string, wholeContent: string, selectorConversion: SelectorConversion): { + name: string; + componentCode: string; + componentObfuscatedCode: string; +}[]; +declare function obfuscateJs(content: string, key: string, selectorCoversion: SelectorConversion, filePath: string, contentIgnoreRegexes?: RegExp[], useAst?: boolean): string; +export { obfuscateForwardComponentJs, obfuscateJs, searchForwardComponent, }; diff --git a/dist/handlers/js.js b/dist/handlers/js.js new file mode 100644 index 0000000..2695413 --- /dev/null +++ b/dist/handlers/js.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.searchForwardComponent = exports.obfuscateJs = exports.obfuscateForwardComponentJs = void 0; +const utils_1 = require("../utils"); +const js_ast_1 = require("./js-ast"); +function searchForwardComponent(content) { + const componentSearchRegex = /(?<=\.jsx\()[^,|"|']+/g; + const match = content.match(componentSearchRegex); + if (match) { + return match; + } + return []; +} +exports.searchForwardComponent = searchForwardComponent; +function searchComponent(content, componentName) { + const componentSearchRegex = new RegExp(`\\b(?:const|let|var)\\s+(${componentName})\\s*=\\s*.*?(\\{)`, "g"); + const match = content.match(componentSearchRegex); + let openSymbolPos = -1; + if (match) { + openSymbolPos = content.indexOf(match[0]) + match[0].length; + } + const closeMarkerPos = (0, utils_1.findClosestSymbolPosition)(content, "{", "}", openSymbolPos, "forward"); + const componentContent = content.slice(openSymbolPos, closeMarkerPos); + return componentContent; +} +function obfuscateForwardComponentJs(searchContent, wholeContent, selectorConversion) { + const componentNames = searchForwardComponent(searchContent).filter((componentName) => { + return !componentName.includes("."); + }); + const componentsCode = componentNames.map(componentName => { + const componentContent = searchComponent(wholeContent, componentName); + return { + name: componentName, + code: componentContent + }; + }); + const componentsObfuscatedCode = componentsCode.map((componentContent) => { + const classNameBlocks = (0, utils_1.findContentBetweenMarker)(componentContent.code, "className:", "{", "}"); + const obfuscatedClassNameBlocks = classNameBlocks.map(block => { + const { obfuscatedContent, usedKeys } = (0, utils_1.obfuscateKeys)(selectorConversion, block); + (0, utils_1.addKeysToRegistery)(usedKeys); + return obfuscatedContent; + }); + if (classNameBlocks.length !== obfuscatedClassNameBlocks.length) { + (0, utils_1.log)("error", `Component obfuscation:`, `classNameBlocks.length !== obfuscatedClassNameBlocks.length`); + return componentContent; + } + let obscuredCode = componentContent.code; + for (let i = 0; i < classNameBlocks.length; i++) { + obscuredCode = (0, utils_1.replaceFirstMatch)(obscuredCode, classNameBlocks[i], obfuscatedClassNameBlocks[i]); + } + (0, utils_1.log)("debug", `Obscured keys in component:`, componentContent.name); + return { + name: componentContent.name, + code: obscuredCode + }; + }); + const componentObfuscatedcomponentCodePairs = []; + for (let i = 0; i < componentsCode.length; i++) { + if (componentsCode[i] !== componentsObfuscatedCode[i]) { + componentObfuscatedcomponentCodePairs.push({ + name: componentsCode[i].name, + componentCode: componentsCode[i].code, + componentObfuscatedCode: componentsObfuscatedCode[i].code + }); + } + } + for (let i = 0; i < componentsCode.length; i++) { + const childComponentObfuscatedcomponentCodePairs = obfuscateForwardComponentJs(componentsCode[i].code, wholeContent, selectorConversion); + componentObfuscatedcomponentCodePairs.push(...childComponentObfuscatedcomponentCodePairs); + } + return componentObfuscatedcomponentCodePairs; +} +exports.obfuscateForwardComponentJs = obfuscateForwardComponentJs; +function obfuscateJs(content, key, selectorCoversion, filePath, contentIgnoreRegexes = [], useAst = false) { + if (useAst) { + try { + const { obfuscatedCode, usedKeys } = (0, js_ast_1.obfuscateJsWithAst)(content, selectorCoversion, key ? [key] : [], true); + (0, utils_1.addKeysToRegistery)(usedKeys); + if (content !== obfuscatedCode) { + (0, utils_1.log)("debug", `Obscured keys with AST and marker "${key}":`, `${(0, utils_1.normalizePath)(filePath)}`); + } + return obfuscatedCode; + } + catch (error) { + if (error instanceof SyntaxError) { + (0, utils_1.log)("warn", "Syntax error ignored:", error); + (0, utils_1.log)("warn", "Obfuscation with AST failed:", "Falling back to regex obfuscation"); + } + else { + throw error; + } + } + } + const truncatedContents = (0, utils_1.findContentBetweenMarker)(content, key, "{", "}"); + truncatedContents.forEach((truncatedContent) => { + const { obfuscatedContent, usedKeys } = (0, utils_1.obfuscateKeys)(selectorCoversion, truncatedContent, contentIgnoreRegexes); + (0, utils_1.addKeysToRegistery)(usedKeys); + if (truncatedContent !== obfuscatedContent) { + content = content.replace(truncatedContent, obfuscatedContent); + (0, utils_1.log)("debug", `Obscured keys with marker "${key}":`, `${(0, utils_1.normalizePath)(filePath)}`); + } + }); + return content; +} +exports.obfuscateJs = obfuscateJs; diff --git a/dist/handlers/js.test.d.ts b/dist/handlers/js.test.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/handlers/js.test.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/handlers/js.test.js b/dist/handlers/js.test.js new file mode 100644 index 0000000..58d9483 --- /dev/null +++ b/dist/handlers/js.test.js @@ -0,0 +1,60 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const vitest_1 = require("vitest"); +const js_1 = require("./js"); +(0, vitest_1.describe)("searchForwardComponent", () => { + (0, vitest_1.test)("should return component name when jsx format is correct", () => { + const content = `const element = o.jsx(ComponentName, {data: dataValue, index: "date"});`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual(["ComponentName"]); + }); + (0, vitest_1.test)("should return multiple component names for multiple matches", () => { + const content = `o.jsx(FirstComponent, props); o.jsx(SecondComponent, otherProps);`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual(["FirstComponent", "SecondComponent"]); + }); + (0, vitest_1.test)("should return an empty array when no component name is found", () => { + const content = `o.jsx("h1", {data: dataValue, index: "date"});`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual([]); + }); + (0, vitest_1.test)("should return an empty array when content is empty", () => { + const content = ""; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual([]); + }); + (0, vitest_1.test)("should return an empty array when jsx is not used", () => { + const content = `const element = React.createElement("div", null, "Hello World");`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual([]); + }); + (0, vitest_1.test)("should handle special characters in component names", () => { + const content = `o.jsx($Comp_1, props); o.jsx(_Comp$2, otherProps);`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual(["$Comp_1", "_Comp$2"]); + }); + (0, vitest_1.test)("should not return component names when they are quoted", () => { + const content = `o.jsx("ComponentName", props); o.jsx('AnotherComponent', otherProps);`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual([]); + }); + (0, vitest_1.test)("should return component names when they are followed by a brace", () => { + const content = `o.jsx(ComponentName, {props: true});`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual(["ComponentName"]); + }); + (0, vitest_1.test)("should handle content with line breaks and multiple jsx calls", () => { + const content = ` + o.jsx(FirstComponent, {data: dataValue}); + o.jsx(SecondComponent, {index: "date"}); + o.jsx(ThirdComponent, {flag: true}); + `; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual(["FirstComponent", "SecondComponent", "ThirdComponent"]); + }); + (0, vitest_1.test)("should handle content with nested jsx calls", () => { + const content = `o.jsx(ParentComponent, {children: o.jsx(ChildComponent, {})})`; + const result = (0, js_1.searchForwardComponent)(content); + (0, vitest_1.expect)(result).toEqual(["ParentComponent", "ChildComponent"]); + }); +}); diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..a06a36e --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,3 @@ +import { OptionalOptions } from "./types"; +declare function obfuscateCli(): void; +export { obfuscateCli, type OptionalOptions as Options }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..7f3ad28 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,85 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.obfuscateCli = void 0; +const tslib_1 = require("tslib"); +const fs_1 = tslib_1.__importDefault(require("fs")); +const path_1 = tslib_1.__importDefault(require("path")); +const yargs_1 = tslib_1.__importDefault(require("yargs")); +const utils_1 = require("./utils"); +const css_1 = require("./handlers/css"); +const config_1 = tslib_1.__importDefault(require("./config")); +function obfuscate(options) { + (0, utils_1.setLogLevel)(options.logLevel); + if (!options.enable) { + (0, utils_1.log)("info", "Obfuscation", "Obfuscation disabled"); + return; + } + const classConversionJsonPaths = (0, utils_1.findAllFilesWithExt)(".json", options.classConversionJsonFolderPath); + if (options.refreshClassConversionJson && classConversionJsonPaths.length > 0) { + (0, utils_1.log)("info", "Obfuscation", "Refreshing class conversion JSON"); + for (const jsonPath of classConversionJsonPaths) { + fs_1.default.unlinkSync(jsonPath); + (0, utils_1.log)("success", "Obfuscation", `Deleted ${jsonPath}`); + } + } + (0, utils_1.log)("info", "Obfuscation", "Creating/Updating class conversion JSON"); + (0, css_1.createSelectorConversionJson)({ + selectorConversionJsonFolderPath: options.classConversionJsonFolderPath, + buildFolderPath: options.buildFolderPath, + mode: options.mode, + classNameLength: options.classLength, + classPrefix: options.classPrefix, + classSuffix: options.classSuffix, + classIgnore: options.classIgnore, + enableObfuscateMarkerClasses: options.enableMarkers, + generatorSeed: options.generatorSeed === "-1" ? undefined : options.generatorSeed, + }); + (0, utils_1.log)("success", "Obfuscation", "Class conversion JSON created/updated"); + if ((options.includeAnyMatchRegexes && options.includeAnyMatchRegexes.length > 0) + || (options.excludeAnyMatchRegexes && options.excludeAnyMatchRegexes.length > 0)) { + (0, utils_1.log)("warn", "Obfuscation", "'includeAnyMatchRegexes' and 'excludeAnyMatchRegexes' are deprecated, please use whiteListedFolderPaths and blackListedFolderPaths instead"); + } + (0, utils_1.replaceJsonKeysInFiles)({ + targetFolder: options.buildFolderPath, + allowExtensions: options.allowExtensions, + selectorConversionJsonFolderPath: options.classConversionJsonFolderPath, + contentIgnoreRegexes: options.contentIgnoreRegexes, + whiteListedFolderPaths: [...options.whiteListedFolderPaths, ...(options.includeAnyMatchRegexes || [])], + blackListedFolderPaths: [...options.blackListedFolderPaths, ...(options.excludeAnyMatchRegexes || [])], + enableObfuscateMarkerClasses: options.enableMarkers, + obfuscateMarkerClasses: options.markers, + removeObfuscateMarkerClassesAfterObfuscated: options.removeMarkersAfterObfuscated, + removeOriginalCss: options.removeOriginalCss, + enableJsAst: options.enableJsAst, + }); +} +function obfuscateCli() { + const argv = yargs_1.default.option("config", { + alias: "c", + type: "string", + description: "Path to the config file" + }).argv; + let configPath; + if (argv.config) { + configPath = path_1.default.resolve(process.cwd(), argv.config); + } + else { + const configFiles = [ + "next-css-obfuscator.config.ts", + "next-css-obfuscator.config.cjs", + "next-css-obfuscator.config.mjs", + "next-css-obfuscator.config.js", + ]; + for (const file of configFiles) { + const potentialPath = path_1.default.join(process.cwd(), file); + if (fs_1.default.existsSync(potentialPath)) { + configPath = potentialPath; + break; + } + } + } + const config = new config_1.default(configPath ? require(configPath) : undefined).get(); + obfuscate(config); + (0, utils_1.log)("success", "Obfuscation", "Completed~"); +} +exports.obfuscateCli = obfuscateCli; diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 0000000..a45feb2 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,57 @@ +type LogLevel = "debug" | "info" | "warn" | "error" | "success"; +type obfuscateMode = "random" | "simplify" | "simplify-seedable"; +type SelectorConversion = { + [key: string]: string; +}; +type Options = { + enable: boolean; + mode: obfuscateMode; + buildFolderPath: string; + classConversionJsonFolderPath: string; + refreshClassConversionJson: boolean; + classLength: number; + classPrefix: string; + classSuffix: string; + classIgnore: (string | RegExp)[]; + allowExtensions: string[]; + contentIgnoreRegexes: RegExp[]; + whiteListedFolderPaths: (string | RegExp)[]; + blackListedFolderPaths: (string | RegExp)[]; + includeAnyMatchRegexes?: RegExp[]; + excludeAnyMatchRegexes?: RegExp[]; + enableMarkers: boolean; + markers: string[]; + removeMarkersAfterObfuscated: boolean; + removeOriginalCss: boolean; + generatorSeed: string; + enableJsAst: boolean; + logLevel: LogLevel; +}; +type OptionalOptions = { + enable?: boolean; + mode?: obfuscateMode; + buildFolderPath?: string; + classConversionJsonFolderPath?: string; + refreshClassConversionJson?: boolean; + classLength?: number; + classPrefix?: string; + classSuffix?: string; + classIgnore?: string[]; + allowExtensions?: string[]; + contentIgnoreRegexes: RegExp[]; + whiteListedFolderPaths?: (string | RegExp)[]; + blackListedFolderPaths?: (string | RegExp)[]; + includeAnyMatchRegexes?: RegExp[]; + excludeAnyMatchRegexes?: RegExp[]; + enableMarkers?: boolean; + markers?: string[]; + removeMarkersAfterObfuscated?: boolean; + removeOriginalCss?: boolean; + generatorSeed?: string; + enableJsAst?: boolean; + logLevel?: LogLevel; +}; +interface HtmlCharacterEntityConversion { + [key: string]: string; +} +export { type LogLevel, type obfuscateMode, type SelectorConversion, type Options, type OptionalOptions, type HtmlCharacterEntityConversion }; diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/types.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/utils.d.ts b/dist/utils.d.ts new file mode 100644 index 0000000..db2fb0c --- /dev/null +++ b/dist/utils.d.ts @@ -0,0 +1,44 @@ +import { type LogLevel, type SelectorConversion } from "./types"; +declare function log(type: LogLevel, task: string, data: any): void; +declare function setLogLevel(level: LogLevel): void; +declare const usedKeyRegistery: Set; +declare function replaceJsonKeysInFiles({ targetFolder, allowExtensions, selectorConversionJsonFolderPath, contentIgnoreRegexes, whiteListedFolderPaths, blackListedFolderPaths, enableObfuscateMarkerClasses, obfuscateMarkerClasses, removeObfuscateMarkerClassesAfterObfuscated, removeOriginalCss, enableJsAst, }: { + targetFolder: string; + allowExtensions: string[]; + selectorConversionJsonFolderPath: string; + contentIgnoreRegexes: RegExp[]; + whiteListedFolderPaths: (string | RegExp)[]; + blackListedFolderPaths: (string | RegExp)[]; + enableObfuscateMarkerClasses: boolean; + obfuscateMarkerClasses: string[]; + removeObfuscateMarkerClassesAfterObfuscated: boolean; + removeOriginalCss: boolean; + enableJsAst: boolean; +}): void; +declare function obfuscateKeys(selectorConversion: SelectorConversion, fileContent: string, contentIgnoreRegexes?: RegExp[], useHtmlEntity?: boolean): { + obfuscatedContent: string; + usedKeys: Set; +}; +declare function getFilenameFromPath(filePath: string): string; +declare function normalizePath(filePath: string): string; +declare function loadAndMergeJsonFiles(jsonFolderPath: string): { + [key: string]: any; +}; +declare function findClosestSymbolPosition(content: string, openMarker: string, closeMarker: string, startPosition?: number, direction?: "forward" | "backward"): number; +declare function findContentBetweenMarker(content: string, targetStr: string, openMarker: string, closeMarker: string): string[]; +declare function addKeysToRegistery(usedKeys: Set | string[]): void; +declare function findAllFilesWithExt(ext: string, targetFolderPath: string): string[]; +declare function getRandomString(length: number, seed?: string, rngStateCode?: string, str?: string): { + rngStateCode: string; + randomString: string; +}; +declare function seedableSimplifyString(str: string, seed?: string, rngStateCode?: string): { + rngStateCode: string; + randomString: string; +}; +declare function simplifyString(alphabetPoistion: number): string; +declare function replaceFirstMatch(source: string, find: string, replace: string): string; +declare function duplicationCheck(arr: string[]): boolean; +declare function createKey(str: string): string; +declare function decodeKey(str: string): string; +export { getFilenameFromPath, log, normalizePath, loadAndMergeJsonFiles, replaceJsonKeysInFiles, setLogLevel, findContentBetweenMarker, replaceFirstMatch, findAllFilesWithExt, getRandomString, seedableSimplifyString, usedKeyRegistery, obfuscateKeys, findClosestSymbolPosition, addKeysToRegistery, duplicationCheck, createKey, decodeKey, simplifyString }; diff --git a/dist/utils.js b/dist/utils.js new file mode 100644 index 0000000..906db97 --- /dev/null +++ b/dist/utils.js @@ -0,0 +1,381 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.simplifyString = exports.decodeKey = exports.createKey = exports.duplicationCheck = exports.addKeysToRegistery = exports.findClosestSymbolPosition = exports.obfuscateKeys = exports.usedKeyRegistery = exports.seedableSimplifyString = exports.getRandomString = exports.findAllFilesWithExt = exports.replaceFirstMatch = exports.findContentBetweenMarker = exports.setLogLevel = exports.replaceJsonKeysInFiles = exports.loadAndMergeJsonFiles = exports.normalizePath = exports.log = exports.getFilenameFromPath = void 0; +const tslib_1 = require("tslib"); +const fs_1 = tslib_1.__importDefault(require("fs")); +const path_1 = tslib_1.__importDefault(require("path")); +const recoverable_random_1 = tslib_1.__importDefault(require("recoverable-random")); +const css_1 = require("./handlers/css"); +const html_1 = require("./handlers/html"); +const js_1 = require("./handlers/js"); +const issuer = "[next-css-obfuscator]"; +let logLevel = "info"; +const levels = ["debug", "info", "warn", "error", "success"]; +function log(type, task, data) { + if (levels.indexOf(type) < levels.indexOf(logLevel)) { + return; + } + const mainColor = "\x1b[38;2;99;102;241m%s\x1b[0m"; + switch (type) { + case "debug": + console.debug(mainColor, issuer, "[Debug] \x1b[37m", task, data, "\x1b[0m"); + break; + case "info": + console.info(mainColor, issuer, "🗯️ \x1b[36m", task, data, "\x1b[0m"); + break; + case "warn": + console.warn(mainColor, issuer, "⚠️ \x1b[33m", task, data, "\x1b[0m"); + break; + case "error": + console.error(mainColor, issuer, "⛔ \x1b[31m", task, data, "\x1b[0m"); + break; + case "success": + console.log(mainColor, issuer, "✅ \x1b[32m", task, data, "\x1b[0m"); + break; + default: + console.log("'\x1b[0m'", issuer, task, data, "\x1b[0m"); + break; + } +} +exports.log = log; +function setLogLevel(level) { + logLevel = level; +} +exports.setLogLevel = setLogLevel; +const usedKeyRegistery = new Set(); +exports.usedKeyRegistery = usedKeyRegistery; +function replaceJsonKeysInFiles({ targetFolder, allowExtensions, selectorConversionJsonFolderPath, contentIgnoreRegexes, whiteListedFolderPaths, blackListedFolderPaths, enableObfuscateMarkerClasses, obfuscateMarkerClasses, removeObfuscateMarkerClassesAfterObfuscated, removeOriginalCss, enableJsAst, }) { + const classConversion = loadAndMergeJsonFiles(selectorConversionJsonFolderPath); + if (removeObfuscateMarkerClassesAfterObfuscated) { + obfuscateMarkerClasses.forEach(obfuscateMarkerClass => { + classConversion[`.${obfuscateMarkerClass}`] = ""; + }); + } + const cssPaths = []; + const replaceJsonKeysInFile = (filePath) => { + const fileExt = path_1.default.extname(filePath).toLowerCase(); + if (fs_1.default.statSync(filePath).isDirectory()) { + fs_1.default.readdirSync(filePath).forEach((subFilePath) => { + replaceJsonKeysInFile(path_1.default.join(filePath, subFilePath)); + }); + } + else if (allowExtensions.includes(fileExt)) { + let isTargetFile = true; + if (whiteListedFolderPaths.length > 0) { + isTargetFile = whiteListedFolderPaths.some((incloudPath) => { + if (typeof incloudPath === "string") { + return normalizePath(filePath).includes(incloudPath); + } + const regex = new RegExp(incloudPath); + return regex.test(normalizePath(filePath)); + }); + } + if (blackListedFolderPaths.length > 0) { + const res = !blackListedFolderPaths.some((incloudPath) => { + if (typeof incloudPath === "string") { + return normalizePath(filePath).includes(incloudPath); + } + const regex = new RegExp(incloudPath); + return regex.test(normalizePath(filePath)); + }); + if (!res) { + isTargetFile = false; + } + } + if (!isTargetFile) { + return; + } + let fileContent = fs_1.default.readFileSync(filePath, "utf-8"); + const fileContentOriginal = fileContent; + if (enableObfuscateMarkerClasses) { + obfuscateMarkerClasses.forEach(obfuscateMarkerClass => { + const isHtml = [".html"].includes(fileExt); + if (isHtml) { + const htmlRegex = new RegExp(`(<(.*)>(.*)<\/([^br][A-Za-z0-9]+)>)`, 'g'); + const htmlMatch = fileContent.match(htmlRegex); + if (htmlMatch) { + const htmlOriginal = htmlMatch[0]; + const { obfuscatedContent, usedKeys } = (0, html_1.obfuscateHtmlClassNames)({ + html: htmlOriginal, + selectorConversion: classConversion, + obfuscateMarkerClass: obfuscateMarkerClass, + contentIgnoreRegexes: contentIgnoreRegexes, + }); + addKeysToRegistery(usedKeys); + if (htmlOriginal !== obfuscatedContent) { + fileContent = fileContent.replace(htmlOriginal, obfuscatedContent); + } + } + } + else { + const obfuscateScriptContent = (0, js_1.obfuscateJs)(fileContent, obfuscateMarkerClass, classConversion, filePath, contentIgnoreRegexes, enableJsAst); + if (fileContent !== obfuscateScriptContent) { + fileContent = obfuscateScriptContent; + log("debug", `Obscured keys in JS like content file:`, normalizePath(filePath)); + } + } + }); + } + else { + if ([".js"].includes(fileExt)) { + const obfuscateScriptContent = (0, js_1.obfuscateJs)(fileContent, enableJsAst ? "" : "jsx", classConversion, filePath, contentIgnoreRegexes, enableJsAst); + if (fileContent !== obfuscateScriptContent) { + fileContent = obfuscateScriptContent; + log("debug", `Obscured keys in JSX related file:`, normalizePath(filePath)); + } + } + else if ([".html"].includes(fileExt)) { + const { obfuscatedContent, usedKeys } = (0, html_1.obfuscateHtmlClassNames)({ + html: fileContent, + selectorConversion: classConversion, + contentIgnoreRegexes: contentIgnoreRegexes, + }); + fileContent = obfuscatedContent; + addKeysToRegistery(usedKeys); + } + else { + const { obfuscatedContent, usedKeys } = obfuscateKeys(classConversion, fileContent, contentIgnoreRegexes); + fileContent = obfuscatedContent; + addKeysToRegistery(usedKeys); + } + } + if (fileContentOriginal !== fileContent) { + log("success", "Data obfuscated:", normalizePath(filePath)); + fs_1.default.writeFileSync(filePath, fileContent); + } + } + else if (fileExt === ".css") { + cssPaths.push(filePath); + } + }; + replaceJsonKeysInFile(targetFolder); + cssPaths.forEach((cssPath) => { + (0, css_1.obfuscateCss)(classConversion, cssPath, removeOriginalCss, !enableObfuscateMarkerClasses); + }); +} +exports.replaceJsonKeysInFiles = replaceJsonKeysInFiles; +function obfuscateKeys(selectorConversion, fileContent, contentIgnoreRegexes = [], useHtmlEntity = false) { + const usedKeys = new Set(); + Object.keys(selectorConversion).forEach((key) => { + const fileContentOriginal = fileContent; + let keyUse = key.slice(1); + keyUse = escapeRegExp(keyUse.replace(/\\/g, "")); + let exactMatchRegex = new RegExp(`([\\s"'\\\`]|^)(${keyUse})(?=$|[\\s"'\\\`]|\\\\n|\\\\",|\\\\"})`, 'g'); + const replacement = `$1` + selectorConversion[key].slice(1).replace(/\\/g, ""); + const matches = fileContent.match(exactMatchRegex); + const originalObscuredContentPairs = matches === null || matches === void 0 ? void 0 : matches.map((match) => { + return { originalContent: match, obscuredContent: match.replace(exactMatchRegex, replacement) }; + }); + fileContent = fileContent.replace(exactMatchRegex, replacement); + if (contentIgnoreRegexes.length > 0) { + contentIgnoreRegexes.forEach((regex) => { + const originalContentFragments = fileContentOriginal.match(regex); + originalContentFragments === null || originalContentFragments === void 0 ? void 0 : originalContentFragments.map((originalContentFragment) => { + originalObscuredContentPairs === null || originalObscuredContentPairs === void 0 ? void 0 : originalObscuredContentPairs.map((pair) => { + if (originalContentFragments === null || originalContentFragments === void 0 ? void 0 : originalContentFragments.some((fragment) => fragment.includes(pair.originalContent))) { + log("debug", "Obscured keys:", `Ignored ${pair.originalContent} at ${originalContentFragment}`); + fileContent = fileContent.replace(originalContentFragment.replace(pair.originalContent, pair.obscuredContent), originalContentFragment); + } + }); + }); + }); + } + if (fileContentOriginal !== fileContent && !usedKeys.has(key)) { + usedKeys.add(key); + } + }); + return { obfuscatedContent: fileContent, usedKeys: usedKeys }; +} +exports.obfuscateKeys = obfuscateKeys; +function escapeRegExp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} +function getFilenameFromPath(filePath) { + return filePath.replace(/^.*[\\/]/, ''); +} +exports.getFilenameFromPath = getFilenameFromPath; +function normalizePath(filePath) { + return filePath.replace(/\\/g, "/"); +} +exports.normalizePath = normalizePath; +function loadAndMergeJsonFiles(jsonFolderPath) { + const jsonFiles = {}; + fs_1.default.readdirSync(jsonFolderPath).forEach((file) => { + const filePath = path_1.default.join(jsonFolderPath, file); + const fileData = JSON.parse(fs_1.default.readFileSync(filePath, "utf-8")); + Object.assign(jsonFiles, fileData); + }); + return jsonFiles; +} +exports.loadAndMergeJsonFiles = loadAndMergeJsonFiles; +function findClosestSymbolPosition(content, openMarker, closeMarker, startPosition = 0, direction = "backward") { + let level = 0; + let currentPos = startPosition; + if (direction === "backward") { + while (currentPos >= 0 && level >= 0) { + if (content.slice(currentPos, currentPos + openMarker.length) === openMarker) { + level--; + } + else if (content.slice(currentPos, currentPos + closeMarker.length) === closeMarker) { + level++; + } + currentPos--; + } + if (level < 0) { + currentPos += 2; + } + } + else { + while (currentPos < content.length && level >= 0) { + if (content.slice(currentPos, currentPos + openMarker.length) === openMarker) { + level++; + } + else if (content.slice(currentPos, currentPos + closeMarker.length) === closeMarker) { + level--; + } + currentPos++; + } + if (level < 0) { + currentPos--; + } + } + return currentPos; +} +exports.findClosestSymbolPosition = findClosestSymbolPosition; +function findContentBetweenMarker(content, targetStr, openMarker, closeMarker) { + if (openMarker === closeMarker) { + throw new Error("openMarker and closeMarker can not be the same"); + } + let targetStrPosition = content.indexOf(targetStr); + const truncatedContents = []; + while (targetStrPosition !== -1 && targetStrPosition < content.length) { + const openPos = findClosestSymbolPosition(content, openMarker, closeMarker, targetStrPosition, "backward"); + const closePos = findClosestSymbolPosition(content, openMarker, closeMarker, targetStrPosition, "forward"); + if (openPos === -1 && closePos === -1) { + break; + } + if (openPos > -1 && closePos > -1) { + truncatedContents.push(content.slice(openPos, closePos)); + targetStrPosition = content.indexOf(targetStr, closePos + 1); + } + else { + targetStrPosition = content.indexOf(targetStr, targetStrPosition + 1); + } + } + return truncatedContents; +} +exports.findContentBetweenMarker = findContentBetweenMarker; +function addKeysToRegistery(usedKeys) { + usedKeys.forEach((key) => { + usedKeyRegistery.add(key); + }); +} +exports.addKeysToRegistery = addKeysToRegistery; +function findAllFilesWithExt(ext, targetFolderPath) { + if (!fs_1.default.existsSync(targetFolderPath)) { + return []; + } + const targetExtFiles = []; + function findCssFiles(dir) { + const files = fs_1.default.readdirSync(dir); + files.forEach((file) => { + const filePath = normalizePath(path_1.default.join(dir, file)); + if (fs_1.default.statSync(filePath).isDirectory()) { + findCssFiles(filePath); + } + else { + if (file.endsWith(ext)) { + targetExtFiles.push(filePath); + } + } + }); + } + findCssFiles(targetFolderPath); + return targetExtFiles; +} +exports.findAllFilesWithExt = findAllFilesWithExt; +let rng = undefined; +function getRandomString(length, seed, rngStateCode, str) { + if (length <= 0 || !Number.isInteger(length)) { + throw new Error("Length must be a positive integer"); + } + if (!rng) { + rng = new recoverable_random_1.default(seed); + } + if (rngStateCode) { + rng.recoverState(rngStateCode); + } + let rn = rng.random(0, 1, true); + if (str && seed) { + rn = parseFloat(`0.${recoverable_random_1.default.stringToSeed(str) + recoverable_random_1.default.stringToSeed(seed)}`); + } + const randomString = rn.toString(36).substring(2, length - 1 + 2); + const randomLetter = String.fromCharCode(Math.floor(rng.random(0, 1, true) * 26) + 97); + return { + rngStateCode: rng.getStateCode(), + randomString: `${randomLetter}${randomString}`, + }; +} +exports.getRandomString = getRandomString; +function seedableSimplifyString(str, seed, rngStateCode) { + if (!str) { + throw new Error("String can not be empty"); + } + if (!rng) { + rng = new recoverable_random_1.default(seed); + } + if (rngStateCode) { + rng.recoverState(rngStateCode); + } + const tempStr = str.replace(/[aeiouw\d_-]/gi, ""); + return { + rngStateCode: rng.getStateCode(), + randomString: tempStr.length < 1 + ? String.fromCharCode(Math.floor(rng.random(0, 1, true) * 26) + 97) + tempStr + : tempStr, + }; +} +exports.seedableSimplifyString = seedableSimplifyString; +function simplifyString(alphabetPoistion) { + if (alphabetPoistion <= 0 || !Number.isInteger(alphabetPoistion)) { + throw new Error("Position must be a positive integer"); + } + let dividend = alphabetPoistion; + let columnName = ""; + let modulo = 0; + while (dividend > 0) { + modulo = (dividend - 1) % 26; + columnName = String.fromCharCode(97 + modulo) + columnName; + dividend = Math.floor((dividend - modulo) / 26); + } + return columnName; +} +exports.simplifyString = simplifyString; +function replaceFirstMatch(source, find, replace) { + const index = source.indexOf(find); + if (index !== -1) { + return source.slice(0, index) + replace + source.slice(index + find.length); + } + return source; +} +exports.replaceFirstMatch = replaceFirstMatch; +function duplicationCheck(arr) { + const set = new Set(arr); + return arr.length !== set.size; +} +exports.duplicationCheck = duplicationCheck; +function createKey(str) { + const b64 = Buffer.from(str).toString("base64").replace(/=/g, ""); + return `{{{{{{${b64}}}}}}}`; +} +exports.createKey = createKey; +function decodeKey(str) { + const regex = /{{{{{{([\w\+\/]+)}}}}}}/g; + str = str.replace(regex, (match, p1) => { + const padding = p1.length % 4 === 0 ? 0 : 4 - (p1.length % 4); + const b64 = p1 + "=".repeat(padding); + return Buffer.from(b64, "base64").toString("ascii"); + }); + return str; +} +exports.decodeKey = decodeKey; diff --git a/package-lock.json b/package-lock.json index 594bad7..b4428f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "next-css-obfuscator", - "version": "2.2.14-beta.2", + "version": "2.2.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "next-css-obfuscator", - "version": "2.2.14-beta.2", + "version": "2.2.16", "license": "MIT", "dependencies": { "@babel/generator": "^7.23.6", diff --git a/package.json b/package.json index eacbef1..9addedc 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "tsc & vitest", "test@cover": "tsc & vitest run --coverage", - "build": "npm run test && tsc", + "build": "tsc", "dev": "tsc -w", "pub": "npm run build && npm publish", "pub@beta": "npm run build && npm publish --tag beta" diff --git a/src/handlers/js-ast.ts b/src/handlers/js-ast.ts index aadf976..affc447 100644 --- a/src/handlers/js-ast.ts +++ b/src/handlers/js-ast.ts @@ -136,21 +136,21 @@ function searchStringLiterals(path: NodePath, path.replaceWith(t.stringLiteral(replacement)); } } - else if (t.isIdentifier(path.node)) { - const variableName = path.node.name; - const binding = path.scope.getBinding(variableName); - if (binding && t.isVariableDeclarator(binding.path.node)) { - const init = binding.path.get("init"); - if (init && !Array.isArray(init)) { - searchStringLiterals(init, callback, scannedNodes); - } - } else if (binding && t.isFunctionDeclaration(binding.path.node)) { - const body = binding.path.get("body"); - if (body && !Array.isArray(body)) { - searchStringLiterals(body, callback, scannedNodes); - } - } - } + // else if (t.isIdentifier(path.node)) { + // const variableName = path.node.name; + // const binding = path.scope.getBinding(variableName); + // if (binding && t.isVariableDeclarator(binding.path.node)) { + // const init = binding.path.get("init"); + // if (init && !Array.isArray(init)) { + // searchStringLiterals(init, callback, scannedNodes); + // } + // } else if (binding && t.isFunctionDeclaration(binding.path.node)) { + // const body = binding.path.get("body"); + // if (body && !Array.isArray(body)) { + // searchStringLiterals(body, callback, scannedNodes); + // } + // } + // } /* call expression (e.g. const a = call()) */ else if (t.isCallExpression(path.node)) { const callee = path.get("callee");