* Copyright (c) 2025-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiExtractor } from '../common/ApiExtractor';
import { FileUtils } from './FileUtils';
import {
AtIntentCollections,
AtKeepCollections,
BytecodeObfuscationCollections,
UnobfuscationCollections
} from './CommonCollections';
import * as crypto from 'crypto';
import * as ts from 'typescript';
import fs from 'fs';
import path from 'path';
export function addToSet<T>(targetSet: Set<T>, sourceSet: Set<T>): void {
sourceSet.forEach((element) => targetSet.add(element));
}
export function arrayToSet<T>(array: T[]): Set<T> {
return new Set(array);
}
export function setToArray<T>(set: Set<T>): T[] {
return Array.from(set);
}
export function areSetsEqual<T>(setA: Set<T>, setB: Set<T>): boolean {
if (setA.size !== setB.size) {
return false;
}
for (const setItem of setA) {
if (!setB.has(setItem)) {
return false;
}
}
return true;
}
export const SOURCE_FILE_PATHS: string = 'sourceFilePaths.cache';
export const TRANSFORMED_PATH: string = 'transformed';
export const FILE_NAMES_MAP: string = 'transformedFileNamesMap.json';
export const FILE_WHITE_LISTS: string = 'fileWhiteLists.json';
export const PROJECT_WHITE_LIST: string = 'projectWhiteList.json';
export const DECORATOR_WHITE_LIST = [
'Monitor',
'Track',
'Trace',
'AnimatableExtend'
];
export interface KeepInfo {
propertyNames: Set<string>;
globalNames: Set<string>;
}
* Informantion of build files
*/
export interface ModuleInfo {
content: string;
* The path in build cache dir
*/
buildFilePath: string;
* The `originSourceFilePath` relative to project root dir.
*/
relativeSourceFilePath: string;
* The origin source file path will be set with rollup moduleId when obfuscate intermediate js source code,
* whereas be set with tsc node.fileName when obfuscate intermediate ts source code.
*/
originSourceFilePath?: string;
rollupModuleId?: string;
}
export interface FileContent {
moduleInfo: ModuleInfo;
previousStageSourceMap?: ts.RawSourceMap;
}
* We have these structure to store project white list and file white lists,
* project white list is merged by each file white list.
*
* We have keepinfo and reservedinfo in white lists:
* KeepInfo: names that we cannot obfuscate or obfuscate as.
* ReservedInfo: names that we cannot obfuscate as.
*
* ProjectWhiteList
* ├── projectKeepInfo: ProjectKeepInfo
* │ ├── propertyNames: Set<string>
* │ └── globalNames: Set<string>
* └── projectReservedInfo: ProjectReservedInfo
* ├── enumProperties: Set<string>
* └── propertyParams: Set<string>
* FileWhiteList
* ├── fileKeepInfo: FileKeepInfo
* │ ├── keepSymbol?: KeepInfo (optional)
* │ │ ├── propertyNames: Set<string>
* │ │ └── globalNames: Set<string>
* │ ├── keepAsConsumer?: KeepInfo (optional)
* │ │ ├── propertyNames: Set<string>
* │ │ └── globalNames: Set<string>
* │ ├── structProperties: Set<string>
* │ ├── exported: KeepInfo
* │ │ ├── propertyNames: Set<string>
* │ │ └── globalNames: Set<string>
* │ ├── enumProperties: Set<string>
* │ ├── stringProperties: Set<string>
* | ├── objectProperties?: Set<string>
* │ └── arkUIKeepInfo: KeepInfo
* │ ├── propertyNames: Set<string>
* │ └── globalNames: Set<string>
* └── bytecodeObfuscateKeepInfo: BytecodeObfuscateKeepInfo
* └── decoratorMap?: Map<string, string[]>
* └── fileReservedInfo: FileReservedInfo
* ├── enumProperties: Set<string>
* └── propertyParams: Set<string>
*/
export interface FileKeepInfo {
keepSymbol?: KeepInfo;
keepAsConsumer?: KeepInfo;
structProperties: Set<string>;
exported: KeepInfo;
enumProperties: Set<string>;
stringProperties: Set<string>;
objectProperties?: Set<string>;
arkUIKeepInfo?: KeepInfo;
}
export interface FileReservedInfo {
enumProperties: Set<string>;
propertyParams: Set<string>;
}
export interface BytecodeObfuscateKeepInfo {
decoratorMap?: Object;
}
export interface FileWhiteList {
fileKeepInfo: FileKeepInfo;
fileReservedInfo: FileReservedInfo;
bytecodeObfuscateKeepInfo?: BytecodeObfuscateKeepInfo
}
export interface ProjectKeepInfo {
propertyNames: Set<string>;
globalNames: Set<string>;
}
export interface ProjectReservedInfo {
enumProperties: Set<string>;
propertyParams: Set<string>;
}
export interface ProjectWhiteList {
projectKeepInfo: ProjectKeepInfo;
projectReservedInfo: ProjectReservedInfo;
}
export interface ProjectWhiteListJsonData {
projectKeepInfo: {
propertyNames: Array<string>;
globalNames: Array<string>;
};
projectReservedInfo: {
enumProperties: Array<string>;
propertyParams: Array<string>;
};
}
export let projectWhiteListManager: ProjectWhiteListManager | undefined;
export function initProjectWhiteListManager(cachePath: string, isIncremental: boolean, enableAtKeep: boolean, keepObjectProperty: boolean): void {
projectWhiteListManager = new ProjectWhiteListManager(cachePath, isIncremental, enableAtKeep, keepObjectProperty);
}
export function clearProjectWhiteListManager(): void {
projectWhiteListManager = undefined;
}
* This class is used to manage project white lists.
* Used to collect white list of each file and merge them into project white lists.
*/
export class ProjectWhiteListManager {
private enableAtKeep: boolean;
private keepObjectProperty: boolean;
private fileWhiteListsCachePath: string;
private projectWhiteListCachePath: string;
private isIncremental: boolean = false;
private shouldReObfuscate: boolean = false;
private fileWhiteListMap: Map<string, FileWhiteList>;
public fileWhiteListInfo: FileWhiteList | undefined;
public getEnableAtKeep(): boolean {
return this.enableAtKeep;
}
public getKeepObjectProperty(): boolean {
return this.keepObjectProperty;
}
public getFileWhiteListsCachePath(): string {
return this.fileWhiteListsCachePath;
}
public getProjectWhiteListCachePath(): string {
return this.projectWhiteListCachePath;
}
public getIsIncremental(): boolean {
return this.isIncremental;
}
public getShouldReObfuscate(): boolean {
return this.shouldReObfuscate;
}
public getFileWhiteListMap(): Map<string, FileWhiteList> {
return this.fileWhiteListMap;
}
constructor(cachePath: string, isIncremental: boolean, enableAtKeep: boolean, keepObjectProperty: boolean) {
this.fileWhiteListsCachePath = path.join(cachePath, FILE_WHITE_LISTS);
this.projectWhiteListCachePath = path.join(cachePath, PROJECT_WHITE_LIST);
this.isIncremental = isIncremental;
this.enableAtKeep = enableAtKeep;
this.fileWhiteListMap = new Map();
this.keepObjectProperty = keepObjectProperty;
}
private createDefaultFileKeepInfo(): FileKeepInfo {
return {
structProperties: new Set<string>(),
exported: {
propertyNames: new Set<string>(),
globalNames: new Set<string>(),
},
enumProperties: new Set<string>(),
stringProperties: new Set<string>(),
arkUIKeepInfo: {
propertyNames: new Set<string>(),
globalNames: new Set<string>(),
},
};
}
private createDefaultFileReservedInfo(): FileReservedInfo {
return {
enumProperties: new Set<string>(),
propertyParams: new Set<string>(),
};
}
public createFileWhiteList(): FileWhiteList {
const fileKeepInfo = this.enableAtKeep ?
{
keepSymbol: {
propertyNames: new Set<string>(),
globalNames: new Set<string>(),
},
keepAsConsumer: {
propertyNames: new Set<string>(),
globalNames: new Set<string>(),
},
...(this.keepObjectProperty ? { objectProperties: new Set<string>() } : {}),
...this.createDefaultFileKeepInfo()
} :
{
...(this.keepObjectProperty ? { objectProperties: new Set<string>() } : {}),
...this.createDefaultFileKeepInfo()
}
return {
fileKeepInfo,
fileReservedInfo: this.createDefaultFileReservedInfo(),
};
}
public setCurrentCollector(path: string): void {
const unixPath = FileUtils.toUnixPath(path);
let fileWhiteListInfo: FileWhiteList | undefined = this.fileWhiteListMap.get(unixPath);
if (!fileWhiteListInfo) {
fileWhiteListInfo = this.createFileWhiteList();
this.fileWhiteListMap.set(unixPath, fileWhiteListInfo);
}
this.fileWhiteListInfo = fileWhiteListInfo;
}
private readFileWhiteLists(filePath: string): Map<string, FileWhiteList> {
const fileContent = fs.readFileSync(filePath, 'utf8');
const parsed: Object = JSON.parse(fileContent);
const map = new Map<string, FileWhiteList>();
for (const key in parsed) {
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
const fileKeepInfo: FileKeepInfo = {
keepSymbol: parsed[key].fileKeepInfo.keepSymbol
? {
propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.propertyNames),
globalNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.globalNames),
}
: undefined,
keepAsConsumer: parsed[key].fileKeepInfo.keepAsConsumer
? {
propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.propertyNames),
globalNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.globalNames),
}
: undefined,
structProperties: arrayToSet(parsed[key].fileKeepInfo.structProperties),
exported: {
propertyNames: arrayToSet(parsed[key].fileKeepInfo.exported.propertyNames),
globalNames: arrayToSet(parsed[key].fileKeepInfo.exported.globalNames),
},
enumProperties: arrayToSet(parsed[key].fileKeepInfo.enumProperties),
stringProperties: arrayToSet(parsed[key].fileKeepInfo.stringProperties),
objectProperties: parsed[key].fileKeepInfo.objectProperties ? arrayToSet(parsed[key].fileKeepInfo.objectProperties) : undefined,
arkUIKeepInfo: parsed[key].fileKeepInfo.arkUIKeepInfo
? {
propertyNames: arrayToSet(parsed[key].fileKeepInfo.arkUIKeepInfo.propertyNames),
globalNames: arrayToSet(parsed[key].fileKeepInfo.arkUIKeepInfo.globalNames),
}
: undefined,
};
const fileReservedInfo: FileReservedInfo = {
enumProperties: arrayToSet(parsed[key].fileReservedInfo.enumProperties),
propertyParams: arrayToSet(parsed[key].fileReservedInfo.propertyParams),
};
const bytecodeObfuscateKeepInfo: BytecodeObfuscateKeepInfo = {
decoratorMap: parsed[key].bytecodeObfuscateKeepInfo?.decoratorMap,
};
map.set(key, { fileKeepInfo, fileReservedInfo, bytecodeObfuscateKeepInfo });
}
}
return map;
}
private writeFileWhiteLists(filePath: string, fileWhiteLists: Map<string, FileWhiteList>): void {
const jsonData: Object = {};
for (const [key, value] of fileWhiteLists) {
jsonData[key] = {
fileKeepInfo: {
keepSymbol: value.fileKeepInfo.keepSymbol
? {
propertyNames: setToArray(value.fileKeepInfo.keepSymbol.propertyNames),
globalNames: setToArray(value.fileKeepInfo.keepSymbol.globalNames),
}
: undefined,
keepAsConsumer: value.fileKeepInfo.keepAsConsumer
? {
propertyNames: setToArray(value.fileKeepInfo.keepAsConsumer.propertyNames),
globalNames: setToArray(value.fileKeepInfo.keepAsConsumer.globalNames),
}
: undefined,
structProperties: setToArray(value.fileKeepInfo.structProperties),
exported: {
propertyNames: setToArray(value.fileKeepInfo.exported.propertyNames),
globalNames: setToArray(value.fileKeepInfo.exported.globalNames),
},
enumProperties: setToArray(value.fileKeepInfo.enumProperties),
stringProperties: setToArray(value.fileKeepInfo.stringProperties),
objectProperties: value.fileKeepInfo.objectProperties ? setToArray(value.fileKeepInfo.objectProperties) : undefined,
arkUIKeepInfo: value.fileKeepInfo.arkUIKeepInfo
? {
propertyNames: setToArray(value.fileKeepInfo.arkUIKeepInfo.propertyNames),
globalNames: setToArray(value.fileKeepInfo.arkUIKeepInfo.globalNames),
}
: undefined,
},
fileReservedInfo: {
enumProperties: setToArray(value.fileReservedInfo.enumProperties),
propertyParams: setToArray(value.fileReservedInfo.propertyParams),
},
};
if (value.bytecodeObfuscateKeepInfo?.decoratorMap) {
jsonData[key].bytecodeObfuscateKeepInfo = {
decoratorMap: value.bytecodeObfuscateKeepInfo.decoratorMap,
};
}
}
const jsonString = JSON.stringify(jsonData, null, 2);
fs.writeFileSync(filePath, jsonString, 'utf8');
}
private readProjectWhiteList(filePath: string): ProjectWhiteList {
const fileContent = fs.readFileSync(filePath, 'utf8');
const parsed: ProjectWhiteListJsonData = JSON.parse(fileContent);
const projectKeepInfo: ProjectKeepInfo = {
propertyNames: arrayToSet(parsed.projectKeepInfo.propertyNames),
globalNames: arrayToSet(parsed.projectKeepInfo.globalNames),
};
const projectReservedInfo: ProjectReservedInfo = {
enumProperties: arrayToSet(parsed.projectReservedInfo.enumProperties),
propertyParams: arrayToSet(parsed.projectReservedInfo.propertyParams),
};
return {
projectKeepInfo,
projectReservedInfo,
};
}
private writeProjectWhiteList(filePath: string, projectWhiteList: ProjectWhiteList): void {
const jsonData: ProjectWhiteListJsonData = {
projectKeepInfo: {
propertyNames: setToArray(projectWhiteList.projectKeepInfo.propertyNames),
globalNames: setToArray(projectWhiteList.projectKeepInfo.globalNames),
},
projectReservedInfo: {
enumProperties: setToArray(projectWhiteList.projectReservedInfo.enumProperties),
propertyParams: setToArray(projectWhiteList.projectReservedInfo.propertyParams),
},
};
const jsonString = JSON.stringify(jsonData, null, 2);
fs.writeFileSync(filePath, jsonString, 'utf8');
}
public updateFileWhiteListMap(deletedFilePath?: Set<string>): void {
const lastFileWhiteLists: Map<string, FileWhiteList> = this.readFileWhiteLists(this.fileWhiteListsCachePath);
deletedFilePath?.forEach((path) => {
lastFileWhiteLists.delete(path);
});
this.fileWhiteListMap.forEach((value, key) => {
lastFileWhiteLists.set(key, value);
});
this.writeFileWhiteLists(this.fileWhiteListsCachePath, lastFileWhiteLists);
this.fileWhiteListMap = lastFileWhiteLists;
}
public createProjectWhiteList(fileWhiteLists: Map<string, FileWhiteList>): ProjectWhiteList {
const projectKeepInfo: ProjectKeepInfo = {
propertyNames: new Set(),
globalNames: new Set(),
};
const projectReservedInfo: ProjectReservedInfo = {
enumProperties: new Set(),
propertyParams: new Set(),
};
fileWhiteLists.forEach((fileWhiteList) => {
fileWhiteList.fileKeepInfo.keepSymbol?.globalNames.forEach((globalName) => {
projectKeepInfo.globalNames.add(globalName);
});
fileWhiteList.fileKeepInfo.keepSymbol?.propertyNames.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.keepAsConsumer?.globalNames.forEach((globalName) => {
projectKeepInfo.globalNames.add(globalName);
});
fileWhiteList.fileKeepInfo.keepAsConsumer?.propertyNames.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.structProperties.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.exported.globalNames.forEach((globalName) => {
projectKeepInfo.globalNames.add(globalName);
});
fileWhiteList.fileKeepInfo.exported.propertyNames.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.enumProperties.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.stringProperties.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.objectProperties?.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
});
fileWhiteList.fileKeepInfo.arkUIKeepInfo?.globalNames.forEach((globalName) => {
projectKeepInfo.globalNames.add(globalName);
AtIntentCollections.globalNames.add(globalName);
});
fileWhiteList.fileKeepInfo.arkUIKeepInfo?.propertyNames.forEach((propertyName) => {
projectKeepInfo.propertyNames.add(propertyName);
AtIntentCollections.propertyNames.add(propertyName);
});
fileWhiteList.fileReservedInfo.enumProperties.forEach((enumPropertyName) => {
projectReservedInfo.enumProperties.add(enumPropertyName);
});
fileWhiteList.fileReservedInfo.propertyParams.forEach((propertyParam) => {
projectReservedInfo.propertyParams.add(propertyParam);
});
const decoratorMap = fileWhiteList.bytecodeObfuscateKeepInfo?.decoratorMap;
for (const key in decoratorMap) {
if (Object.prototype.hasOwnProperty.call(decoratorMap, key)) {
decoratorMap[key]?.forEach(item => projectKeepInfo.globalNames.add(item));
}
}
});
const projectWhiteList = {
projectKeepInfo: projectKeepInfo,
projectReservedInfo: projectReservedInfo,
};
return projectWhiteList;
}
private areProjectWhiteListsEqual(whiteList1: ProjectWhiteList, whiteList2: ProjectWhiteList): boolean {
const projectKeepInfoEqual =
areSetsEqual(whiteList1.projectKeepInfo.propertyNames, whiteList2.projectKeepInfo.propertyNames) &&
areSetsEqual(whiteList1.projectKeepInfo.globalNames, whiteList2.projectKeepInfo.globalNames);
if (!projectKeepInfoEqual) {
return false;
}
const projectReservedInfoEqual =
areSetsEqual(whiteList1.projectReservedInfo.enumProperties, whiteList2.projectReservedInfo.enumProperties) &&
areSetsEqual(whiteList1.projectReservedInfo.propertyParams, whiteList2.projectReservedInfo.propertyParams);
return projectReservedInfoEqual;
}
private updateUnobfuscationCollections(): void {
this.fileWhiteListMap.forEach((fileWhiteList) => {
if (this.enableAtKeep) {
addToSet(AtKeepCollections.keepSymbol.propertyNames, fileWhiteList.fileKeepInfo.keepSymbol.propertyNames);
addToSet(AtKeepCollections.keepSymbol.globalNames, fileWhiteList.fileKeepInfo.keepSymbol.globalNames);
addToSet(AtKeepCollections.keepAsConsumer.propertyNames, fileWhiteList.fileKeepInfo.keepAsConsumer.propertyNames);
addToSet(AtKeepCollections.keepAsConsumer.globalNames, fileWhiteList.fileKeepInfo.keepAsConsumer.globalNames);
}
addToSet(UnobfuscationCollections.reservedStruct, fileWhiteList.fileKeepInfo.structProperties);
addToSet(UnobfuscationCollections.reservedEnum, fileWhiteList.fileKeepInfo.enumProperties);
addToSet(UnobfuscationCollections.reservedExportName, fileWhiteList.fileKeepInfo.exported.globalNames);
addToSet(UnobfuscationCollections.reservedExportNameAndProp, fileWhiteList.fileKeepInfo.exported.propertyNames);
addToSet(UnobfuscationCollections.reservedStrProp, fileWhiteList.fileKeepInfo.stringProperties);
if (this.keepObjectProperty) {
addToSet(UnobfuscationCollections.reservedObjProp, fileWhiteList.fileKeepInfo.objectProperties);
}
if (fileWhiteList.fileKeepInfo.arkUIKeepInfo) {
addToSet(AtIntentCollections.propertyNames, fileWhiteList.fileKeepInfo.arkUIKeepInfo.propertyNames);
addToSet(AtIntentCollections.globalNames, fileWhiteList.fileKeepInfo.arkUIKeepInfo.globalNames);
}
addToSet(ApiExtractor.mConstructorPropertySet, fileWhiteList.fileReservedInfo.propertyParams);
addToSet(ApiExtractor.mEnumMemberSet, fileWhiteList.fileReservedInfo.enumProperties);
const decoratorMap = fileWhiteList.bytecodeObfuscateKeepInfo?.decoratorMap;
for (const key in decoratorMap) {
if (Object.prototype.hasOwnProperty.call(decoratorMap, key)) {
decoratorMap[key]?.forEach(item => BytecodeObfuscationCollections.decoratorProp.add(item));
}
}
});
}
private updateProjectWhiteList(): ProjectWhiteList {
const lastProjectWhiteList: ProjectWhiteList = this.readProjectWhiteList(this.projectWhiteListCachePath);
const newestProjectWhiteList: ProjectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap);
if (!this.areProjectWhiteListsEqual(lastProjectWhiteList, newestProjectWhiteList)) {
this.writeProjectWhiteList(this.projectWhiteListCachePath, newestProjectWhiteList);
this.shouldReObfuscate = true;
}
return newestProjectWhiteList;
}
public createOrUpdateWhiteListCaches(deletedFilePath?: Set<string>): void {
let projectWhiteList: ProjectWhiteList;
if (!this.isIncremental) {
this.writeFileWhiteLists(this.fileWhiteListsCachePath, this.fileWhiteListMap);
projectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap);
this.writeProjectWhiteList(this.projectWhiteListCachePath, projectWhiteList);
} else {
this.updateFileWhiteListMap(deletedFilePath);
projectWhiteList = this.updateProjectWhiteList();
this.updateUnobfuscationCollections();
}
this.fileWhiteListMap.clear();
}
}
* This class is used to manage sourceFilePath.json file.
* Will be created when initialize arkObfuscator.
*/
export class FilePathManager {
private sourceFilePaths: Set<string>;
private deletedSourceFilePaths: Set<string>;
private addedSourceFilePaths: Set<string>;
private filePathsCache: string;
public getSourceFilePaths(): Set<string> {
return this.sourceFilePaths;
}
public getDeletedSourceFilePaths(): Set<string> {
return this.deletedSourceFilePaths;
}
public getAddedSourceFilePaths(): Set<string> {
return this.addedSourceFilePaths;
}
public getFilePathsCache(): string {
return this.filePathsCache;
}
constructor(cachePath: string) {
this.filePathsCache = path.join(cachePath, SOURCE_FILE_PATHS);
this.sourceFilePaths = new Set();
this.deletedSourceFilePaths = new Set();
this.addedSourceFilePaths = new Set();
}
private setSourceFilePaths(sourceFilePaths: Set<string>): void {
sourceFilePaths.forEach((filePath) => {
if (!FileUtils.isReadableFile(filePath) || !ApiExtractor.isParsableFile(filePath)) {
return;
}
this.sourceFilePaths.add(filePath);
});
}
private writeSourceFilePaths(): void {
const content = Array.from(this.sourceFilePaths).join('\n');
FileUtils.writeFile(this.filePathsCache, content);
}
private updateSourceFilePaths(): void {
const cacheContent = FileUtils.readFile(this.filePathsCache).split('\n');
const cacheSet = new Set(cacheContent);
for (const path of cacheSet) {
if (!this.sourceFilePaths.has(path)) {
this.deletedSourceFilePaths.add(path);
}
}
for (const path of this.sourceFilePaths) {
if (!cacheSet.has(path)) {
this.addedSourceFilePaths.add(path);
}
}
this.writeSourceFilePaths();
}
public createOrUpdateSourceFilePaths(sourceFilePaths: Set<string>): void {
this.setSourceFilePaths(sourceFilePaths);
if (this.isIncremental()) {
this.updateSourceFilePaths();
} else {
this.writeSourceFilePaths();
}
}
public isIncremental(): boolean {
return FileUtils.fileExists(this.filePathsCache);
}
}
* This class is used to manage transfromed file content.
* Will be created when initialize arkObfuscator.
*/
export class FileContentManager {
private transformedFilesDir: string;
private fileNamesMapPath: string;
public fileNamesMap: Map<string, string>;
private isIncremental: boolean;
public getTransformedFilesDir(): string {
return this.transformedFilesDir;
}
public getFileNamesMapPath(): string {
return this.fileNamesMapPath;
}
public getIsIncremental(): boolean {
return this.isIncremental;
}
constructor(cachePath: string, isIncremental: boolean) {
this.transformedFilesDir = path.join(cachePath, TRANSFORMED_PATH);
this.fileNamesMapPath = path.join(this.transformedFilesDir, FILE_NAMES_MAP);
this.fileNamesMap = new Map();
this.isIncremental = isIncremental;
FileUtils.createDirectory(this.transformedFilesDir);
}
private generatePathHash(filePath: string): string {
const hash = crypto.createHash('md5');
hash.update(filePath);
return hash.digest('hex').slice(0, 16);
}
private generateFileName(filePath: string): string {
const hash = this.generatePathHash(filePath);
const timestamp = Date.now().toString();
return `${hash}_${timestamp}`;
}
public deleteFileContent(deletedSourceFilePaths: Set<string>): void {
deletedSourceFilePaths.forEach((filePath) => {
this.deleteTransformedFile(filePath);
});
}
private deleteTransformedFile(deletedSourceFilePath: string): void {
if (this.fileNamesMap.has(deletedSourceFilePath)) {
const transformedFilePath: string = this.fileNamesMap.get(deletedSourceFilePath);
this.fileNamesMap.delete(deletedSourceFilePath);
FileUtils.deleteFile(transformedFilePath);
}
}
public readFileNamesMap(): void {
const jsonObject = FileUtils.readFileAsJson(this.fileNamesMapPath);
this.fileNamesMap = new Map<string, string>(Object.entries(jsonObject));
}
public writeFileNamesMap(): void {
const jsonObject = Object.fromEntries(this.fileNamesMap.entries());
const jsonString = JSON.stringify(jsonObject, null, 2);
FileUtils.writeFile(this.fileNamesMapPath, jsonString);
this.fileNamesMap.clear();
}
public readFileContent(transformedFilePath: string): FileContent {
const fileContentJson = FileUtils.readFile(transformedFilePath);
const fileConstent: FileContent = JSON.parse(fileContentJson);
return fileConstent;
}
private writeFileContent(filePath: string, fileContent: FileContent): void {
const jsonString = JSON.stringify(fileContent, null, 2);
FileUtils.writeFile(filePath, jsonString);
}
public updateFileContent(fileContent: FileContent): void {
const originPath = FileUtils.toUnixPath(fileContent.moduleInfo.originSourceFilePath);
if (this.isIncremental) {
this.deleteTransformedFile(originPath);
}
const fileName = this.generateFileName(originPath);
const transformedFilePath = path.join(this.transformedFilesDir, fileName);
this.fileNamesMap.set(originPath, transformedFilePath);
this.writeFileContent(transformedFilePath, fileContent);
}
public getSortedFiles(): string[] {
return (this.fileNamesMap && Array.from(this.fileNamesMap.keys())).sort((a, b) => a.localeCompare(b)) || [];
}
}