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       13140x 13140x     9x 90x 11972x 11972x 11972x 11972x         9x 90x     9x     9x 13140x                                             13140x     9x                     9x 90x 90x   90x   90x   90x       90x       90x 13140x       13140x   90x               90x     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;