++static::$request_id, 'method' => 'lint', 'files' => [$file_path] ]; fwrite($sock, json_encode($request) . "\n"); // Read response $response = fgets($sock); fclose($sock); if (!$response) { throw new \RuntimeException("No response from RPC server"); } $data = json_decode($response, true); if (!$data || !is_array($data)) { throw new \RuntimeException("Invalid JSON response from RPC server"); } if (isset($data['error'])) { throw new \RuntimeException("RPC error: " . $data['error']); } if (!isset($data['results'][$file_path])) { throw new \RuntimeException("No result for file in RPC response"); } $result = $data['results'][$file_path]; if ($result['status'] === 'success') { // Return the error info if present, null if no errors return $result['error']; } // Handle error response if ($result['status'] === 'error' && isset($result['error'])) { throw new \RuntimeException("Lint error: " . ($result['error']['message'] ?? 'Unknown error')); } return null; } catch (\Exception $e) { throw new \RuntimeException( "JavaScript lint RPC error for {$file_path}: " . $e->getMessage() ); } } /** * Analyze this-usage via RPC server */ protected static function _analyze_this_via_rpc(string $file_path): array { $base_path = function_exists('base_path') ? base_path() : '/var/www/html/system'; $socket_path = $base_path . '/' . self::RPC_SOCKET; try { $sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5); if (!$sock) { throw new \RuntimeException("Failed to connect to RPC server: {$errstr}"); } // Set blocking mode for reliable reads stream_set_blocking($sock, true); // Send analyze_this request $request = [ 'id' => ++static::$request_id, 'method' => 'analyze_this', 'files' => [$file_path] ]; fwrite($sock, json_encode($request) . "\n"); // Read response $response = fgets($sock); fclose($sock); if (!$response) { throw new \RuntimeException("No response from RPC server"); } $data = json_decode($response, true); if (!$data || !is_array($data)) { throw new \RuntimeException("Invalid JSON response from RPC server"); } if (isset($data['error'])) { throw new \RuntimeException("RPC error: " . $data['error']); } if (!isset($data['results'][$file_path])) { throw new \RuntimeException("No result for file in RPC response"); } $result = $data['results'][$file_path]; if ($result['status'] === 'success') { return $result['violations'] ?? []; } // Handle error response - return empty violations, don't fail return []; } catch (\Exception $e) { // Log error but don't fail the check if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', "RPC error for {$file_path}: " . $e->getMessage()); } return []; } } /** * Start the RPC server */ public static function start_rpc_server(): void { $base_path = function_exists('base_path') ? base_path() : '/var/www/html/system'; $socket_path = $base_path . '/' . self::RPC_SOCKET; if (file_exists($socket_path)) { // Server might be running, force stop it if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', 'Found existing socket, forcing shutdown'); } static::stop_rpc_server(force: true); } // Start new server $server_script = $base_path . '/' . self::RPC_SERVER_SCRIPT; if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', 'Starting RPC server: ' . $server_script); } $process = new Process([ 'node', $server_script, '--socket=' . $socket_path ]); $process->start(); static::$rpc_server_process = $process; // Register shutdown handler register_shutdown_function([self::class, 'stop_rpc_server']); // Wait for server to be ready (ping/pong up to 10 seconds) if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', 'Waiting for RPC server to be ready...'); } $max_attempts = 200; // 10 seconds (50ms * 200) $ready = false; for ($i = 0; $i < $max_attempts; $i++) { usleep(50000); // 50ms if (file_exists($socket_path)) { // Try to ping if (static::ping_rpc_server()) { $ready = true; if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', 'RPC server ready after ' . ($i * 50) . 'ms'); } break; } } } if (!$ready) { static::stop_rpc_server(); throw new \RuntimeException('Failed to start JS Code Quality RPC server - timeout after 10 seconds'); } if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', 'RPC server started successfully'); } } /** * Ping the RPC server */ protected static function ping_rpc_server(): bool { $base_path = function_exists('base_path') ? base_path() : '/var/www/html/system'; $socket_path = $base_path . '/' . self::RPC_SOCKET; try { $sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5); if (!$sock) { return false; } // Set blocking mode for reliable reads stream_set_blocking($sock, true); // Send ping fwrite($sock, json_encode(['id' => ++static::$request_id, 'method' => 'ping']) . "\n"); // Read response $response = fgets($sock); fclose($sock); if (!$response) { return false; } $data = json_decode($response, true); return isset($data['result']) && $data['result'] === 'pong'; } catch (\Exception $e) { return false; } } /** * Stop the RPC server */ public static function stop_rpc_server(bool $force = false): void { $base_path = function_exists('base_path') ? base_path() : '/var/www/html/system'; $socket_path = $base_path . '/' . self::RPC_SOCKET; // Try graceful shutdown if (file_exists($socket_path)) { try { $sock = @stream_socket_client("unix://{$socket_path}", $errno, $errstr, 0.5); if ($sock) { fwrite($sock, json_encode(['id' => 0, 'method' => 'shutdown']) . "\n"); fclose($sock); } } catch (\Exception $e) { // Ignore errors } } // Only wait and force kill if $force = true if ($force && static::$rpc_server_process && static::$rpc_server_process->isRunning()) { if (function_exists('console_debug')) { console_debug('JS_CODE_QUALITY', 'Force stopping RPC server'); } // Wait for graceful shutdown sleep(1); // Force kill if still running if (static::$rpc_server_process->isRunning()) { static::$rpc_server_process->stop(3, SIGTERM); } } // Clean up socket file if (file_exists($socket_path)) { @unlink($socket_path); } } }