All files / src/lib/scriptEventsHandlers trustedHandler.ts

100% Statements 42/42
100% Branches 6/6
100% Functions 7/7
100% Lines 41/41

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 132                      9x                         9x         13350x     13350x 13350x 13350x   13350x 20x       13350x 13350x     13278x 13275x   3x         13350x 26700x     13350x               13350x 13347x   8x       13342x     13338x 13338x     13338x     13338x       197x 197x 194x         3x       13338x             13338x   23x 23x 22x   1x         13338x 174x 174x   6x         13338x   13338x   12x      
/**
 * Trusted Script Event Handler Loader
 *
 * Executes handler code using native eval for trusted contexts.
 */
 
import type {
  FileReaderFn,
  ScriptEventHandler,
  ScriptEventHandlerWithCleanup,
} from "lib/scriptEventsHandlers/handlerTypes";
import {
  detectModuleSystem,
  createMockRequire,
  validateHandlerMetadata,
  createHandlerBase,
  finalizeHandler,
  noReadFileFn,
  convertESMToCommonJS,
} from "./handlerCommon";
 
/**
 * Loads script event handlers using native eval for trusted code execution.
 */
export const loadScriptEventHandlerFromTrustedString = async (
  code: string,
  filename: string,
  readFile: FileReaderFn = noReadFileFn,
): Promise<ScriptEventHandlerWithCleanup> => {
  try {
    // Execute handler code with ES module conversion
    let metadata: unknown;
    try {
      const moduleSystem = detectModuleSystem(code);
      let processedCode = code;
 
      if (moduleSystem === "module") {
        processedCode = convertESMToCommonJS(code);
      }
 
      // Create controlled sandbox with mock modules
      const mockRequire = createMockRequire(readFile);
      const sandbox = {
        module: { exports: {} },
        require: (moduleName: string): unknown => {
          if (mockRequire[moduleName]) {
            return mockRequire[moduleName];
          }
          throw new Error(`Module '${moduleName}' not found in mock modules`);
        },
      };
 
      // Prepare sandbox variable declarations
      const contextSetup = Object.keys(sandbox)
        .map((key) => `var ${key} = arguments[0].${key};`)
        .join("\n");
 
      const wrappedCode = `
        ${contextSetup}
        ${processedCode}
        return module.exports;
      `;
 
      // Execute code with eval (must be trusted)
      // eslint-disable-next-line no-new-func
      const vmFunction = new Function(wrappedCode);
      metadata = vmFunction.call(null, sandbox);
    } catch (error) {
      throw error;
    }
 
    // Validate required handler metadata
    validateHandlerMetadata(metadata, filename);
 
    // Store module exports for function calling
    const moduleExports = metadata as Record<string, unknown>;
    const hasAutoLabelFunction = typeof moduleExports.autoLabel === "function";
 
    // No-op cleanup for eval-based execution
    const cleanup = () => {};
 
    // Direct function calls without VM overhead
    const callModuleFunction = (
      functionName: string,
      args: unknown[],
    ): unknown => {
      const targetFunction = moduleExports[functionName];
      if (typeof targetFunction === "function") {
        return (targetFunction as (...args: unknown[]) => unknown).apply(
          moduleExports,
          args,
        );
      }
      throw new Error(`Function '${functionName}' not found in module exports`);
    };
 
    // Create base handler using shared utilities
    const handler = createHandlerBase(
      metadata as Record<string, unknown>,
      hasAutoLabelFunction,
      cleanup,
    ) as ScriptEventHandler & { cleanup: () => void };
 
    // Add execution-specific methods
    handler.autoLabel = hasAutoLabelFunction
      ? (lookup: (key: string) => string, input: Record<string, unknown>) => {
          try {
            const result = callModuleFunction("autoLabel", [lookup, input]);
            return String(result);
          } catch (error) {
            throw error;
          }
        }
      : undefined;
 
    handler.compile = (input: unknown, helpers: unknown) => {
      try {
        callModuleFunction("compile", [input, helpers]);
      } catch (error) {
        throw error;
      }
    };
 
    // Finalize handler with lookup tables and validation
    finalizeHandler(handler);
 
    return handler;
  } catch (error) {
    throw error;
  }
};