All files / src/lib/project loadScriptEventHandlers.ts

72.88% Statements 43/59
22.22% Branches 2/9
55.56% Functions 5/9
72.41% Lines 42/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 1319x 9x 9x         9x 9x 9x 9x 9x 9x   9x   9x   9x   9x                       9x       11952x 11952x     9x 83x 10725x 10725x 10725x 10725x         9x 83x     9x     9x 11952x                                             11952x     9x                     9x 83x 83x   83x   83x   83x       83x       83x 11952x       11952x   83x               83x     9x  
import glob from "glob";
import { promisify } from "util";
import { eventsRoot } from "consts";
import {
  FileReaderFn,
  ScriptEventHandlerWithCleanup,
} from "lib/scriptEventsHandlers/handlerTypes";
import { readFile, readFileSync } from "fs-extra";
import { loadScriptEventHandlerFromUntrustedString } from "lib/scriptEventsHandlers/untrustedHandler";
import { loadScriptEventHandlerFromTrustedString } from "lib/scriptEventsHandlers/trustedHandler";
import { dirname, join } from "path";
import { isAssetWithinProject } from "lib/helpers/assets";
import l10n from "shared/lib/lang/l10n";
 
const globAsync = promisify(glob);
 
const eventHandlers: Record<string, ScriptEventHandlerWithCleanup> = {};
 
const fileCache = new Map<string, string | Buffer>();
 
const loadUntrustedScriptEventHandler = async (
  path: string,
  fileReader: FileReaderFn,
): Promise<ScriptEventHandlerWithCleanup> => {
  const handlerCode = await readFile(path, "utf8");
  return loadScriptEventHandlerFromUntrustedString(
    handlerCode,
    path,
    fileReader,
  );
};
 
const loadTrustedScriptEventHandler = async (
  path: string,
  fileReader: FileReaderFn,
): Promise<ScriptEventHandlerWithCleanup> => {
  const handlerCode = await readFile(path, "utf8");
  return loadScriptEventHandlerFromTrustedString(handlerCode, path, fileReader);
};
 
const cleanupScriptEventHandlers = () => {
  for (const key in eventHandlers) {
    const handler = eventHandlers[key];
    if (handler) {
      handler.cleanup();
      delete eventHandlers[key];
    }
  }
};
 
const cleanupFileCache = () => {
  fileCache.clear();
};
 
const cacheKey = (absPath: string, enc?: BufferEncoding) =>
  `${absPath}::${enc ?? "<binary>"}`;
 
const createFileReaderForHandler = (pluginPath: string): FileReaderFn => {
  const dirPath = dirname(pluginPath);
 
  function readPluginFile(filePath: string, encoding: "utf8"): string;
  function readPluginFile(
    filePath: string,
    encoding?: BufferEncoding,
  ): string | Buffer;
 
  function readPluginFile(filePath: string, encoding?: BufferEncoding) {
    const absPath = join(dirPath, filePath);
    guardFileWithinPlugin(absPath, dirPath);
 
    const key = cacheKey(absPath, encoding);
    const cached = fileCache.get(key);
    Iif (cached !== undefined) {
      return cached;
    }
 
    const value = readFileSync(absPath, encoding);
    fileCache.set(key, value);
    return value;
  }
 
  return readPluginFile;
};
 
export const guardFileWithinPlugin = (
  assetPath: string,
  projectRoot: string,
) => {
  Iif (!isAssetWithinProject(assetPath, projectRoot)) {
    throw new Error(
      l10n("ERROR_FILE_DOESNT_BELONG_TO_CURRENT_PLUGIN", { file: assetPath }),
    );
  }
};
 
const loadAllScriptEventHandlers = async (projectRoot: string) => {
  cleanupScriptEventHandlers();
  cleanupFileCache();
 
  const forceUntrusted = process.env.FORCE_QUICKJS_PLUGINS === "true";
 
  const corePaths = await globAsync(`${eventsRoot}/event*.js`);
 
  const pluginPaths = await globAsync(
    `${projectRoot}/plugins/*/**/events/event*.js`,
  );
 
  const trustedHandler = forceUntrusted
    ? loadUntrustedScriptEventHandler
    : loadTrustedScriptEventHandler;
 
  for (const path of corePaths) {
    const handler = await trustedHandler(
      path,
      createFileReaderForHandler(path),
    );
    eventHandlers[handler.id] = handler;
  }
  for (const path of pluginPaths) {
    const handler = await loadUntrustedScriptEventHandler(
      path,
      createFileReaderForHandler(path),
    );
    eventHandlers[handler.id] = handler;
  }
 
  return eventHandlers;
};
 
export default loadAllScriptEventHandlers;