All files / src/lib/project loadScriptEventHandlers.ts

71.92% Statements 41/57
16.66% Branches 2/12
55.55% Functions 5/9
71.42% Lines 40/56

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 131 13211x 11x         11x 11x 11x 11x 11x 11x   11x   11x   11x                       11x       47740x 47740x     11x 308x 46345x 46345x 46345x 46345x         11x 308x     11x     11x 47740x                                             47740x     11x                     11x 308x 308x   308x   308x         308x         308x       308x 47740x       47740x   308x               308x     11x  
import { glob } from "lib/helpers/glob";
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 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];
    Eif (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);
    if (cached !== undefined) {
      return cached;
    }
 
    const value = readFileSync(absPath, encoding);
    fileCache.set(key, value);
    return value;
  }
 
  return readPluginFile;
};
 
export const guardFileWithinPlugin = (
  assetPath: string,
  projectRoot: string,
) => {
  if (!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 glob("event*.js", {
    cwd: eventsRoot,
    absolute: true,
  });
 
  const pluginPaths = await glob("plugins/*/**/events/event*.js", {
    cwd: projectRoot,
    absolute: true,
  });
 
  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;