1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { ApiExtractor } from '../common/ApiExtractor'; 17import { FileUtils } from './FileUtils'; 18import { AtKeepCollections, UnobfuscationCollections } from './CommonCollections'; 19import * as crypto from 'crypto'; 20import * as ts from 'typescript'; 21import fs from 'fs'; 22import path from 'path'; 23 24export function addToSet<T>(targetSet: Set<T>, sourceSet: Set<T>): void { 25 sourceSet.forEach((element) => targetSet.add(element)); 26} 27 28export function arrayToSet<T>(array: T[]): Set<T> { 29 return new Set(array); 30} 31 32export function setToArray<T>(set: Set<T>): T[] { 33 return Array.from(set); 34} 35 36export function areSetsEqual<T>(setA: Set<T>, setB: Set<T>): boolean { 37 if (setA.size !== setB.size) { 38 return false; 39 } 40 for (const setItem of setA) { 41 if (!setB.has(setItem)) { 42 return false; 43 } 44 } 45 return true; 46} 47 48export const SOURCE_FILE_PATHS: string = 'sourceFilePaths.cache'; 49export const TRANSFORMED_PATH: string = 'transformed'; 50export const FILE_NAMES_MAP: string = 'transformedFileNamesMap.json'; 51export const FILE_WHITE_LISTS: string = 'fileWhiteLists.json'; 52export const PROJECT_WHITE_LIST: string = 'projectWhiteList.json'; 53 54export interface KeepInfo { 55 propertyNames: Set<string>; 56 globalNames: Set<string>; 57} 58 59/** 60 * Informantion of build files 61 */ 62export interface ModuleInfo { 63 content: string; 64 /** 65 * The path in build cache dir 66 */ 67 buildFilePath: string; 68 /** 69 * The `originSourceFilePath` relative to project root dir. 70 */ 71 relativeSourceFilePath: string; 72 /** 73 * The origin source file path will be set with rollup moduleId when obfuscate intermediate js source code, 74 * whereas be set with tsc node.fileName when obfuscate intermediate ts source code. 75 */ 76 originSourceFilePath?: string; 77 rollupModuleId?: string; 78} 79 80export interface FileContent { 81 moduleInfo: ModuleInfo; 82 previousStageSourceMap?: ts.RawSourceMap; 83} 84 85/** 86 * We have these structure to store project white list and file white lists, 87 * project white list is merged by each file white list. 88 * 89 * We have keepinfo and reservedinfo in white lists: 90 * KeepInfo: names that we cannot obfuscate or obfuscate as. 91 * ReservedInfo: names that we cannot obfuscate as. 92 * 93 * ProjectWhiteList 94 * ├── projectKeepInfo: ProjectKeepInfo 95 * │ ├── propertyNames: Set<string> 96 * │ └── globalNames: Set<string> 97 * └── projectReservedInfo: ProjectReservedInfo 98 * ├── enumProperties: Set<string> 99 * └── propertyParams: Set<string> 100 * FileWhiteList 101 * ├── fileKeepInfo: FileKeepInfo 102 * │ ├── keepSymbol?: KeepInfo (optional) 103 * │ │ ├── propertyNames: Set<string> 104 * │ │ └── globalNames: Set<string> 105 * │ ├── keepAsConsumer?: KeepInfo (optional) 106 * │ │ ├── propertyNames: Set<string> 107 * │ │ └── globalNames: Set<string> 108 * │ ├── structProperties: Set<string> 109 * │ ├── exported: KeepInfo 110 * │ │ ├── propertyNames: Set<string> 111 * │ │ └── globalNames: Set<string> 112 * │ ├── enumProperties: Set<string> 113 * │ └── stringProperties: Set<string> 114 * └── fileReservedInfo: FileReservedInfo 115 * ├── enumProperties: Set<string> 116 * └── propertyParams: Set<string> 117 */ 118export interface FileKeepInfo { 119 keepSymbol?: KeepInfo; // Names marked with "@KeepSymbol". 120 keepAsConsumer?: KeepInfo; // Names marked with "@KeepAsConsumer". 121 structProperties: Set<string>; // Struct properties collected from struct. 122 exported: KeepInfo; // Exported names and properties. 123 enumProperties: Set<string>; // Enum properties. 124 stringProperties: Set<string>; // String properties. 125} 126 127export interface FileReservedInfo { 128 enumProperties: Set<string>; // Enum members, collected when has initializer. 129 propertyParams: Set<string>; // Properties parameters in constructor. 130} 131 132export interface FileWhiteList { 133 fileKeepInfo: FileKeepInfo; 134 fileReservedInfo: FileReservedInfo; 135} 136 137export interface ProjectKeepInfo { 138 propertyNames: Set<string>; 139 globalNames: Set<string>; 140} 141 142export interface ProjectReservedInfo { 143 enumProperties: Set<string>; 144 propertyParams: Set<string>; 145} 146 147export interface ProjectWhiteList { 148 projectKeepInfo: ProjectKeepInfo; 149 projectReservedInfo: ProjectReservedInfo; 150} 151 152export interface ProjectWhiteListJsonData { 153 projectKeepInfo: { 154 propertyNames: Array<string>; 155 globalNames: Array<string>; 156 }; 157 projectReservedInfo: { 158 enumProperties: Array<string>; 159 propertyParams: Array<string>; 160 }; 161} 162 163 164// The object to manage project white lists, should be initialized in arkObfuscator 165export let projectWhiteListManager: ProjectWhiteListManager | undefined; 166 167// Initialize projectWhiteListManager 168export function initProjectWhiteListManager(cachePath: string, isIncremental: boolean, enableAtKeep: boolean): void { 169 projectWhiteListManager = new ProjectWhiteListManager(cachePath, isIncremental, enableAtKeep); 170} 171 172// Clear projectWhiteListManager 173export function clearProjectWhiteListManager(): void { 174 projectWhiteListManager = undefined; 175} 176 177/** 178 * This class is used to manage project white lists. 179 * Used to collect white list of each file and merge them into project white lists. 180 */ 181export class ProjectWhiteListManager { 182 // If atKeep is enabled 183 private enableAtKeep: boolean; 184 185 // Cache path for file white lists 186 private fileWhiteListsCachePath: string; 187 188 // Cache path for project white lists 189 private projectWhiteListCachePath: string; 190 191 // If it is incremental compliation 192 private isIncremental: boolean = false; 193 194 // If we should reObfuscate all files 195 private shouldReObfuscate: boolean = false; 196 197 // White lists of each file 198 private fileWhiteListMap: Map<string, FileWhiteList>; 199 200 // White lists for one file 201 public fileWhiteListInfo: FileWhiteList | undefined; 202 203 public getEnableAtKeep(): boolean { 204 return this.enableAtKeep; 205 } 206 207 public getFileWhiteListsCachePath(): string { 208 return this.fileWhiteListsCachePath; 209 } 210 211 public getProjectWhiteListCachePath(): string { 212 return this.projectWhiteListCachePath; 213 } 214 215 public getIsIncremental(): boolean { 216 return this.isIncremental; 217 } 218 219 public getShouldReObfuscate(): boolean { 220 return this.shouldReObfuscate; 221 } 222 223 public getFileWhiteListMap(): Map<string, FileWhiteList> { 224 return this.fileWhiteListMap; 225 } 226 227 constructor(cachePath: string, isIncremental: boolean, enableAtKeep: boolean) { 228 this.fileWhiteListsCachePath = path.join(cachePath, FILE_WHITE_LISTS); 229 this.projectWhiteListCachePath = path.join(cachePath, PROJECT_WHITE_LIST); 230 this.isIncremental = isIncremental; 231 this.enableAtKeep = enableAtKeep; 232 this.fileWhiteListMap = new Map(); 233 } 234 235 private createDefaultFileKeepInfo(): FileKeepInfo { 236 return { 237 structProperties: new Set<string>(), 238 exported: { 239 propertyNames: new Set<string>(), 240 globalNames: new Set<string>(), 241 }, 242 enumProperties: new Set<string>(), 243 stringProperties: new Set<string>(), 244 }; 245 } 246 247 private createDefaultFileReservedInfo(): FileReservedInfo { 248 return { 249 enumProperties: new Set<string>(), 250 propertyParams: new Set<string>(), 251 }; 252 } 253 254 // Create one fileWhilteList object, with keepSymbol & keepAsConsumer if atKeep is enabled 255 public createFileWhiteList(): FileWhiteList { 256 const fileKeepInfo = this.enableAtKeep 257 ? { 258 keepSymbol: { 259 propertyNames: new Set<string>(), 260 globalNames: new Set<string>(), 261 }, 262 keepAsConsumer: { 263 propertyNames: new Set<string>(), 264 globalNames: new Set<string>(), 265 }, 266 ...this.createDefaultFileKeepInfo(), 267 } 268 : this.createDefaultFileKeepInfo(); 269 return { 270 fileKeepInfo, 271 fileReservedInfo: this.createDefaultFileReservedInfo(), 272 }; 273 } 274 275 // Initialize current collector, 276 // should be called before we collect fileWhiteLists. 277 public setCurrentCollector(path: string): void { 278 const unixPath = FileUtils.toUnixPath(path); 279 let fileWhiteListInfo: FileWhiteList | undefined = this.fileWhiteListMap.get(unixPath); 280 if (!fileWhiteListInfo) { 281 fileWhiteListInfo = this.createFileWhiteList(); 282 this.fileWhiteListMap.set(unixPath, fileWhiteListInfo); 283 } 284 this.fileWhiteListInfo = fileWhiteListInfo; 285 } 286 287 private readFileWhiteLists(filePath: string): Map<string, FileWhiteList> { 288 const fileContent = fs.readFileSync(filePath, 'utf8'); 289 const parsed: Object = JSON.parse(fileContent); 290 291 const map = new Map<string, FileWhiteList>(); 292 for (const key in parsed) { 293 if (Object.prototype.hasOwnProperty.call(parsed, key)) { 294 const fileKeepInfo: FileKeepInfo = { 295 keepSymbol: parsed[key].fileKeepInfo.keepSymbol 296 ? { 297 propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.propertyNames), 298 globalNames: arrayToSet(parsed[key].fileKeepInfo.keepSymbol.globalNames), 299 } 300 : undefined, 301 keepAsConsumer: parsed[key].fileKeepInfo.keepAsConsumer 302 ? { 303 propertyNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.propertyNames), 304 globalNames: arrayToSet(parsed[key].fileKeepInfo.keepAsConsumer.globalNames), 305 } 306 : undefined, 307 structProperties: arrayToSet(parsed[key].fileKeepInfo.structProperties), 308 exported: { 309 propertyNames: arrayToSet(parsed[key].fileKeepInfo.exported.propertyNames), 310 globalNames: arrayToSet(parsed[key].fileKeepInfo.exported.globalNames), 311 }, 312 enumProperties: arrayToSet(parsed[key].fileKeepInfo.enumProperties), 313 stringProperties: arrayToSet(parsed[key].fileKeepInfo.stringProperties), 314 }; 315 316 const fileReservedInfo: FileReservedInfo = { 317 enumProperties: arrayToSet(parsed[key].fileReservedInfo.enumProperties), 318 propertyParams: arrayToSet(parsed[key].fileReservedInfo.propertyParams), 319 }; 320 321 map.set(key, { fileKeepInfo, fileReservedInfo }); 322 } 323 } 324 325 return map; 326 } 327 328 private writeFileWhiteLists(filePath: string, fileWhiteLists: Map<string, FileWhiteList>): void { 329 const jsonData: Object = {}; 330 for (const [key, value] of fileWhiteLists) { 331 jsonData[key] = { 332 fileKeepInfo: { 333 keepSymbol: value.fileKeepInfo.keepSymbol 334 ? { 335 propertyNames: setToArray(value.fileKeepInfo.keepSymbol.propertyNames), 336 globalNames: setToArray(value.fileKeepInfo.keepSymbol.globalNames), 337 } 338 : undefined, 339 keepAsConsumer: value.fileKeepInfo.keepAsConsumer 340 ? { 341 propertyNames: setToArray(value.fileKeepInfo.keepAsConsumer.propertyNames), 342 globalNames: setToArray(value.fileKeepInfo.keepAsConsumer.globalNames), 343 } 344 : undefined, 345 structProperties: setToArray(value.fileKeepInfo.structProperties), 346 exported: { 347 propertyNames: setToArray(value.fileKeepInfo.exported.propertyNames), 348 globalNames: setToArray(value.fileKeepInfo.exported.globalNames), 349 }, 350 enumProperties: setToArray(value.fileKeepInfo.enumProperties), 351 stringProperties: setToArray(value.fileKeepInfo.stringProperties), 352 }, 353 fileReservedInfo: { 354 enumProperties: setToArray(value.fileReservedInfo.enumProperties), 355 propertyParams: setToArray(value.fileReservedInfo.propertyParams), 356 }, 357 }; 358 } 359 360 const jsonString = JSON.stringify(jsonData, null, 2); 361 fs.writeFileSync(filePath, jsonString, 'utf8'); 362 } 363 364 private readProjectWhiteList(filePath: string): ProjectWhiteList { 365 const fileContent = fs.readFileSync(filePath, 'utf8'); 366 const parsed: ProjectWhiteListJsonData = JSON.parse(fileContent); 367 368 const projectKeepInfo: ProjectKeepInfo = { 369 propertyNames: arrayToSet(parsed.projectKeepInfo.propertyNames), 370 globalNames: arrayToSet(parsed.projectKeepInfo.globalNames), 371 }; 372 373 const projectReservedInfo: ProjectReservedInfo = { 374 enumProperties: arrayToSet(parsed.projectReservedInfo.enumProperties), 375 propertyParams: arrayToSet(parsed.projectReservedInfo.propertyParams), 376 }; 377 378 return { 379 projectKeepInfo, 380 projectReservedInfo, 381 }; 382 } 383 384 private writeProjectWhiteList(filePath: string, projectWhiteList: ProjectWhiteList): void { 385 const jsonData: ProjectWhiteListJsonData = { 386 projectKeepInfo: { 387 propertyNames: setToArray(projectWhiteList.projectKeepInfo.propertyNames), 388 globalNames: setToArray(projectWhiteList.projectKeepInfo.globalNames), 389 }, 390 projectReservedInfo: { 391 enumProperties: setToArray(projectWhiteList.projectReservedInfo.enumProperties), 392 propertyParams: setToArray(projectWhiteList.projectReservedInfo.propertyParams), 393 }, 394 }; 395 396 const jsonString = JSON.stringify(jsonData, null, 2); 397 fs.writeFileSync(filePath, jsonString, 'utf8'); 398 } 399 400 public updateFileWhiteListMap(deletedFilePath?: Set<string>): void { 401 const lastFileWhiteLists: Map<string, FileWhiteList> = this.readFileWhiteLists(this.fileWhiteListsCachePath); 402 403 deletedFilePath?.forEach((path) => { 404 lastFileWhiteLists.delete(path); 405 }); 406 this.fileWhiteListMap.forEach((value, key) => { 407 lastFileWhiteLists.set(key, value); 408 }); 409 this.writeFileWhiteLists(this.fileWhiteListsCachePath, lastFileWhiteLists); 410 this.fileWhiteListMap = lastFileWhiteLists; 411 } 412 413 public createProjectWhiteList(fileWhiteLists: Map<string, FileWhiteList>): ProjectWhiteList { 414 const projectKeepInfo: ProjectKeepInfo = { 415 propertyNames: new Set(), 416 globalNames: new Set(), 417 }; 418 419 const projectReservedInfo: ProjectReservedInfo = { 420 enumProperties: new Set(), 421 propertyParams: new Set(), 422 }; 423 424 fileWhiteLists.forEach((fileWhiteList) => { 425 // 1. Collect fileKeepInfo 426 // Collect keepSymbol 427 fileWhiteList.fileKeepInfo.keepSymbol?.globalNames.forEach((globalName) => { 428 projectKeepInfo.globalNames.add(globalName); 429 }); 430 fileWhiteList.fileKeepInfo.keepSymbol?.propertyNames.forEach((propertyName) => { 431 projectKeepInfo.propertyNames.add(propertyName); 432 }); 433 434 // Collect keepAsConsumer 435 fileWhiteList.fileKeepInfo.keepAsConsumer?.globalNames.forEach((globalName) => { 436 projectKeepInfo.globalNames.add(globalName); 437 }); 438 fileWhiteList.fileKeepInfo.keepAsConsumer?.propertyNames.forEach((propertyName) => { 439 projectKeepInfo.propertyNames.add(propertyName); 440 }); 441 442 // Collect structProperties 443 fileWhiteList.fileKeepInfo.structProperties.forEach((propertyName) => { 444 projectKeepInfo.propertyNames.add(propertyName); 445 }); 446 447 // Collect exportedNames 448 fileWhiteList.fileKeepInfo.exported.globalNames.forEach((globalName) => { 449 projectKeepInfo.globalNames.add(globalName); 450 }); 451 fileWhiteList.fileKeepInfo.exported.propertyNames.forEach((propertyName) => { 452 projectKeepInfo.propertyNames.add(propertyName); 453 }); 454 455 // Collect enumProperties 456 fileWhiteList.fileKeepInfo.enumProperties.forEach((propertyName) => { 457 projectKeepInfo.propertyNames.add(propertyName); 458 }); 459 460 // Collect stringProperties 461 fileWhiteList.fileKeepInfo.stringProperties.forEach((propertyName) => { 462 projectKeepInfo.propertyNames.add(propertyName); 463 }); 464 465 // 2. Collect fileReservedInfo 466 // Collect enumProperties 467 fileWhiteList.fileReservedInfo.enumProperties.forEach((enumPropertyName) => { 468 projectReservedInfo.enumProperties.add(enumPropertyName); 469 }); 470 471 // Collect propertyParams 472 fileWhiteList.fileReservedInfo.propertyParams.forEach((propertyParam) => { 473 projectReservedInfo.propertyParams.add(propertyParam); 474 }); 475 }); 476 477 const projectWhiteList = { 478 projectKeepInfo: projectKeepInfo, 479 projectReservedInfo: projectReservedInfo, 480 }; 481 482 return projectWhiteList; 483 } 484 485 // Determine if the project's white list has been updated. 486 private areProjectWhiteListsEqual(whiteList1: ProjectWhiteList, whiteList2: ProjectWhiteList): boolean { 487 const projectKeepInfoEqual = 488 areSetsEqual(whiteList1.projectKeepInfo.propertyNames, whiteList2.projectKeepInfo.propertyNames) && 489 areSetsEqual(whiteList1.projectKeepInfo.globalNames, whiteList2.projectKeepInfo.globalNames); 490 491 if (!projectKeepInfoEqual) { 492 return false; 493 } 494 495 const projectReservedInfoEqual = 496 areSetsEqual(whiteList1.projectReservedInfo.enumProperties, whiteList2.projectReservedInfo.enumProperties) && 497 areSetsEqual(whiteList1.projectReservedInfo.propertyParams, whiteList2.projectReservedInfo.propertyParams); 498 499 return projectReservedInfoEqual; 500 } 501 502 // We only scan updated files or newly created files in incremental compliation, 503 // so we need to update the UnobfuscationCollections and reserved names use all file white lists. 504 // This should be called in incremental compliation after we updated file white list. 505 private updateUnobfuscationCollections(): void { 506 this.fileWhiteListMap.forEach((fileWhiteList) => { 507 if (this.enableAtKeep) { 508 addToSet(AtKeepCollections.keepSymbol.propertyNames, fileWhiteList.fileKeepInfo.keepSymbol.propertyNames); 509 addToSet(AtKeepCollections.keepSymbol.globalNames, fileWhiteList.fileKeepInfo.keepSymbol.globalNames); 510 addToSet(AtKeepCollections.keepAsConsumer.propertyNames, fileWhiteList.fileKeepInfo.keepAsConsumer.propertyNames); 511 addToSet(AtKeepCollections.keepAsConsumer.globalNames, fileWhiteList.fileKeepInfo.keepAsConsumer.globalNames); 512 } 513 addToSet(UnobfuscationCollections.reservedStruct, fileWhiteList.fileKeepInfo.structProperties); 514 addToSet(UnobfuscationCollections.reservedEnum, fileWhiteList.fileKeepInfo.enumProperties); 515 addToSet(UnobfuscationCollections.reservedExportName, fileWhiteList.fileKeepInfo.exported.globalNames); 516 addToSet(UnobfuscationCollections.reservedExportNameAndProp, fileWhiteList.fileKeepInfo.exported.propertyNames); 517 addToSet(UnobfuscationCollections.reservedStrProp, fileWhiteList.fileKeepInfo.stringProperties); 518 addToSet(ApiExtractor.mConstructorPropertySet, fileWhiteList.fileReservedInfo.propertyParams); 519 addToSet(ApiExtractor.mEnumMemberSet, fileWhiteList.fileReservedInfo.enumProperties); 520 }); 521 } 522 523 private updateProjectWhiteList(): ProjectWhiteList { 524 const lastProjectWhiteList: ProjectWhiteList = this.readProjectWhiteList(this.projectWhiteListCachePath); 525 const newestProjectWhiteList: ProjectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap); 526 if (!this.areProjectWhiteListsEqual(lastProjectWhiteList, newestProjectWhiteList)) { 527 this.writeProjectWhiteList(this.projectWhiteListCachePath, newestProjectWhiteList); 528 this.shouldReObfuscate = true; 529 } 530 return newestProjectWhiteList; 531 } 532 533 public createOrUpdateWhiteListCaches(deletedFilePath?: Set<string>): void { 534 let projectWhiteList: ProjectWhiteList; 535 if (!this.isIncremental) { 536 this.writeFileWhiteLists(this.fileWhiteListsCachePath, this.fileWhiteListMap); 537 projectWhiteList = this.createProjectWhiteList(this.fileWhiteListMap); 538 this.writeProjectWhiteList(this.projectWhiteListCachePath, projectWhiteList); 539 } else { 540 this.updateFileWhiteListMap(deletedFilePath); 541 projectWhiteList = this.updateProjectWhiteList(); 542 this.updateUnobfuscationCollections(); 543 } 544 this.fileWhiteListMap.clear(); 545 } 546} 547 548/** 549 * This class is used to manage sourceFilePath.json file. 550 * Will be created when initialize arkObfuscator. 551 */ 552export class FilePathManager { 553 // Stores all source files paths 554 private sourceFilePaths: Set<string>; 555 556 // Files deleted in incremental build 557 private deletedSourceFilePaths: Set<string>; 558 559 // Files added in incremental build 560 private addedSourceFilePaths: Set<string>; 561 562 // Cache path of sourceFilePaths.cache file 563 private filePathsCache: string; 564 565 public getSourceFilePaths(): Set<string> { 566 return this.sourceFilePaths; 567 } 568 569 public getDeletedSourceFilePaths(): Set<string> { 570 return this.deletedSourceFilePaths; 571 } 572 573 public getAddedSourceFilePaths(): Set<string> { 574 return this.addedSourceFilePaths; 575 } 576 577 public getFilePathsCache(): string { 578 return this.filePathsCache; 579 } 580 581 constructor(cachePath: string) { 582 this.filePathsCache = path.join(cachePath, SOURCE_FILE_PATHS); 583 this.sourceFilePaths = new Set(); 584 this.deletedSourceFilePaths = new Set(); 585 this.addedSourceFilePaths = new Set(); 586 } 587 588 private setSourceFilePaths(sourceFilePaths: Set<string>): void { 589 sourceFilePaths.forEach((filePath) => { 590 if (!FileUtils.isReadableFile(filePath) || !ApiExtractor.isParsableFile(filePath)) { 591 return; 592 } 593 this.sourceFilePaths.add(filePath); 594 }); 595 } 596 597 private writeSourceFilePaths(): void { 598 const content = Array.from(this.sourceFilePaths).join('\n'); 599 FileUtils.writeFile(this.filePathsCache, content); 600 } 601 602 // Update sourceFilePaths.cache and get deleted file list and added file list 603 private updateSourceFilePaths(): void { 604 const cacheContent = FileUtils.readFile(this.filePathsCache).split('\n'); 605 const cacheSet = new Set(cacheContent); 606 607 for (const path of cacheSet) { 608 if (!this.sourceFilePaths.has(path)) { 609 this.deletedSourceFilePaths.add(path); 610 } 611 } 612 613 for (const path of this.sourceFilePaths) { 614 if (!cacheSet.has(path)) { 615 this.addedSourceFilePaths.add(path); 616 } 617 } 618 619 this.writeSourceFilePaths(); 620 } 621 622 // Create or update sourceFilePaths.cache 623 public createOrUpdateSourceFilePaths(sourceFilePaths: Set<string>): void { 624 this.setSourceFilePaths(sourceFilePaths); 625 if (this.isIncremental()) { 626 this.updateSourceFilePaths(); 627 } else { 628 this.writeSourceFilePaths(); 629 } 630 } 631 632 public isIncremental(): boolean { 633 return fs.existsSync(this.filePathsCache); 634 } 635} 636 637/** 638 * This class is used to manage transfromed file content. 639 * Will be created when initialize arkObfuscator. 640 */ 641export class FileContentManager { 642 // Path that stores all transfromed file content 643 private transformedFilesDir: string; 644 645 // Path of fileNamesMap 646 private fileNamesMapPath: string; 647 648 // Stores [originpath -> transformed cache path] 649 public fileNamesMap: Map<string, string>; 650 651 // If it is incremental compilation 652 private isIncremental: boolean; 653 654 public getTransformedFilesDir(): string { 655 return this.transformedFilesDir; 656 } 657 658 public getFileNamesMapPath(): string { 659 return this.fileNamesMapPath; 660 } 661 662 public getIsIncremental(): boolean { 663 return this.isIncremental; 664 } 665 666 constructor(cachePath: string, isIncremental: boolean) { 667 this.transformedFilesDir = path.join(cachePath, TRANSFORMED_PATH); 668 this.fileNamesMapPath = path.join(this.transformedFilesDir, FILE_NAMES_MAP); 669 this.fileNamesMap = new Map(); 670 this.isIncremental = isIncremental; 671 FileUtils.createDirectory(this.transformedFilesDir); 672 } 673 674 // Generate hash from filePah 675 private generatePathHash(filePath: string): string { 676 const hash = crypto.createHash('md5'); 677 hash.update(filePath); 678 return hash.digest('hex').slice(0, 16); 679 } 680 681 // Generate new file name for transfromed sourcefiles. 682 // Generated from origin filePath's hash & time stamp. 683 private generateFileName(filePath: string): string { 684 const hash = this.generatePathHash(filePath); 685 const timestamp = Date.now().toString(); 686 return `${hash}_${timestamp}`; 687 } 688 689 // Delete a set of files, used in incremental compliation if we have deleted some files 690 public deleteFileContent(deletedSourceFilePaths: Set<string>): void { 691 deletedSourceFilePaths.forEach((filePath) => { 692 this.deleteTransformedFile(filePath); 693 }); 694 } 695 696 // Delete transfromed file 697 private deleteTransformedFile(deletedSourceFilePath: string): void { 698 if (this.fileNamesMap.has(deletedSourceFilePath)) { 699 const transformedFilePath: string = this.fileNamesMap.get(deletedSourceFilePath); 700 this.fileNamesMap.delete(deletedSourceFilePath); 701 FileUtils.deleteFile(transformedFilePath); 702 } 703 } 704 705 public readFileNamesMap(): void { 706 const jsonObject = FileUtils.readFileAsJson(this.fileNamesMapPath); 707 this.fileNamesMap = new Map<string, string>(Object.entries(jsonObject)); 708 } 709 710 public writeFileNamesMap(): void { 711 const jsonObject = Object.fromEntries(this.fileNamesMap.entries()); 712 const jsonString = JSON.stringify(jsonObject, null, 2); 713 FileUtils.writeFile(this.fileNamesMapPath, jsonString); 714 this.fileNamesMap.clear(); 715 } 716 717 public readFileContent(transformedFilePath: string): FileContent { 718 const fileContentJson = FileUtils.readFile(transformedFilePath); 719 const fileConstent: FileContent = JSON.parse(fileContentJson); 720 return fileConstent; 721 } 722 723 private writeFileContent(filePath: string, fileContent: FileContent): void { 724 const jsonString = JSON.stringify(fileContent, null, 2); 725 FileUtils.writeFile(filePath, jsonString); 726 } 727 728 // Update file content for newly created files or updated files 729 public updateFileContent(fileContent: FileContent): void { 730 const originPath = FileUtils.toUnixPath(fileContent.moduleInfo.originSourceFilePath); 731 if (this.isIncremental) { 732 this.deleteTransformedFile(originPath); 733 } 734 const fileName = this.generateFileName(originPath); 735 const transformedFilePath = path.join(this.transformedFilesDir, fileName); 736 this.fileNamesMap.set(originPath, transformedFilePath); 737 this.writeFileContent(transformedFilePath, fileContent); 738 } 739 740 public getSortedFiles(): string[] { 741 return (this.fileNamesMap && Array.from(this.fileNamesMap.keys())).sort((a, b) => a.localeCompare(b)) || []; 742 } 743} 744