server-function-hub.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // server/server-function-hub.js
  2. /**
  3. * Promise-Aware Server Function Hub
  4. * Enhanced to properly handle async functions and Promises
  5. */
  6. const WebSocket = require('ws');
  7. const https = require('https');
  8. const fs = require('fs');
  9. const path = require('path');
  10. require('dotenv').config();
  11. // Import security helpers
  12. const { processEncryptedCredentials } = require('./security-helpers');
  13. // Configuration
  14. const WS_PORT = process.env.WS_PORT || 8080;
  15. const SSL_PREFIX = process.env.SSL_PREFIX || '/etc/letsencrypt/live/';
  16. const DOMAIN = process.env.DOMAIN || 'tuvme.xyz';
  17. // SSL Certificate paths
  18. const SSL_KEY_PATH = process.env.SSL_KEY_PATH || path.join(SSL_PREFIX, DOMAIN, 'privkey.pem');
  19. const SSL_CERT_PATH = process.env.SSL_CERT_PATH || path.join(SSL_PREFIX, DOMAIN, 'fullchain.pem');
  20. /**
  21. * Enhanced logging function
  22. */
  23. function log(label, data = null) {
  24. const timestamp = new Date().toISOString();
  25. console.log(`[${timestamp}] [SERVER-FUNCTION-HUB] ${label}`);
  26. if (data) {
  27. console.log(JSON.stringify(data, null, 2));
  28. }
  29. }
  30. /**
  31. * In-memory store for functions
  32. */
  33. const functionStore = new Map();
  34. /**
  35. * Stats
  36. */
  37. const stats = {
  38. totalConnections: 0,
  39. activeConnections: 0,
  40. registrations: 0,
  41. executions: 0,
  42. errors: 0
  43. };
  44. /**
  45. * Register a function or code snippet
  46. */
  47. function registerFunction(functionId, functionBody, paramNames = []) {
  48. stats.registrations++;
  49. // Log full function body without any truncation
  50. log(`======= REGISTERING FUNCTION: ${functionId} =======`);
  51. console.log("FUNCTION BODY BEGIN:");
  52. console.log(functionBody);
  53. console.log("FUNCTION BODY END");
  54. log(`========================================`);
  55. // Store the code info without any validation
  56. functionStore.set(functionId, {
  57. body: functionBody,
  58. params: paramNames
  59. });
  60. log(`Code successfully registered: ${functionId}`);
  61. return true;
  62. }
  63. /**
  64. * Helper function to check if a value is a Promise or Promise-like
  65. */
  66. function isPromiseLike(value) {
  67. return value && typeof value.then === 'function';
  68. }
  69. /**
  70. * Execute registered function
  71. */
  72. async function executeFunction(functionId, args = []) {
  73. stats.executions++;
  74. log(`======= EXECUTING FUNCTION: ${functionId} =======`);
  75. log(`Arguments:`, args);
  76. if (!functionStore.has(functionId)) {
  77. log(`Code not found: ${functionId}`);
  78. throw new Error(`Code not found: ${functionId}`);
  79. }
  80. try {
  81. const funcInfo = functionStore.get(functionId);
  82. // Process and decrypt any encrypted credentials in the function body
  83. const processedBody = processEncryptedCredentials(funcInfo.body);
  84. // Log full stored function body
  85. log(`EXECUTING THIS EXACT CODE:`);
  86. console.log(processedBody);
  87. log(`========================================`);
  88. // Create execution context
  89. const executeCode = (code, args) => {
  90. // Create a safe execution context with allowed Node.js modules
  91. const vm = require('vm');
  92. const sandbox = {
  93. require,
  94. process,
  95. console,
  96. Buffer,
  97. setTimeout,
  98. clearTimeout,
  99. setInterval,
  100. clearInterval,
  101. setImmediate,
  102. clearImmediate,
  103. args,
  104. Promise // Explicit Promise reference
  105. };
  106. // Create context and run code
  107. const context = vm.createContext(sandbox);
  108. // FIXED: Instead of double-wrapping in IIFEs, check if the code is already
  109. // returning a Promise or has an async function signature.
  110. // If the code appears to be async, we'll just run it directly.
  111. let codeToRun = code;
  112. // Check if code appears to be an async IIFE already
  113. const isAsyncIIFE = /^\s*\(?\s*async\s+function/.test(code) ||
  114. code.includes('new Promise');
  115. // If not an async IIFE already, but is a plain function or statement, wrap it
  116. if (!isAsyncIIFE) {
  117. log(`Code is not an async IIFE, wrapping in function...`);
  118. codeToRun = `(function() { ${code} })()`;
  119. } else {
  120. log(`Code appears to be async/Promise-based, executing as-is`);
  121. }
  122. log(`EXECUTING FINAL CODE:`);
  123. console.log(codeToRun);
  124. // Execute the code
  125. const result = vm.runInContext(codeToRun, context);
  126. log(`Raw execution result type: ${typeof result}`);
  127. return result;
  128. };
  129. // Execute the code with the provided arguments
  130. let result = executeCode(processedBody, args);
  131. // Log the result type
  132. log(`EXECUTION RESULT TYPE: ${typeof result}`);
  133. // If result is a Promise or Promise-like, await it
  134. if (isPromiseLike(result)) {
  135. log(`DETECTED PROMISE RESULT - AWAITING...`);
  136. try {
  137. result = await result;
  138. log(`PROMISE RESOLVED TO:`, result);
  139. } catch (promiseError) {
  140. log(`PROMISE REJECTION:`, {
  141. error: promiseError.message,
  142. stack: promiseError.stack
  143. });
  144. throw promiseError;
  145. }
  146. } else {
  147. log(`EXECUTION RESULT (NOT A PROMISE):`, result);
  148. }
  149. return result;
  150. } catch (error) {
  151. stats.errors++;
  152. log(`ERROR EXECUTING CODE: ${functionId}`, {
  153. errorType: error.constructor.name,
  154. errorMessage: error.message,
  155. errorStack: error.stack
  156. });
  157. throw error;
  158. }
  159. }
  160. /**
  161. * Start the WebSocket server
  162. */
  163. function startServer() {
  164. const server = https.createServer({
  165. key: fs.readFileSync(SSL_KEY_PATH),
  166. cert: fs.readFileSync(SSL_CERT_PATH)
  167. });
  168. const wss = new WebSocket.Server({
  169. server,
  170. path: '/ws'
  171. });
  172. wss.on('connection', (ws, req) => {
  173. stats.totalConnections++;
  174. stats.activeConnections++;
  175. const connectionId = `conn-${Date.now()}-${stats.totalConnections}`;
  176. log(`New connection: ${connectionId}`, { ip: req.socket.remoteAddress });
  177. ws.on('message', async (message) => {
  178. try {
  179. const data = JSON.parse(message);
  180. log(`Received message:`, data);
  181. const requestId = data.requestId || 'unknown';
  182. // Handle function registration
  183. if (data.type === 'register') {
  184. const { functionId, functionBody, paramNames = [] } = data;
  185. log(`Processing code registration: ${functionId}`);
  186. const success = registerFunction(functionId, functionBody, paramNames);
  187. ws.send(JSON.stringify({
  188. requestId,
  189. status: 'ok',
  190. result: { registered: success }
  191. }));
  192. }
  193. // Handle function execution
  194. else if (data.type === 'execute') {
  195. const { functionId, args = [] } = data;
  196. log(`Processing code execution: ${functionId}`);
  197. try {
  198. // Now executeFunction is async and will properly await Promises
  199. const result = await executeFunction(functionId, args);
  200. log(`Final result to send for ${requestId}:`, result);
  201. ws.send(JSON.stringify({
  202. requestId,
  203. result,
  204. markerId: data.markerId
  205. }));
  206. } catch (execError) {
  207. log(`Error executing code:`, { error: execError.message, stack: execError.stack });
  208. ws.send(JSON.stringify({ requestId, error: execError.message }));
  209. }
  210. }
  211. // Unknown message type
  212. else {
  213. log(`Unknown message type received`, { type: data.type });
  214. ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${data.type}` }));
  215. }
  216. } catch (error) {
  217. log(`Error processing message:`, { error: error.message, message: message.toString().substring(0, 200) });
  218. stats.errors++;
  219. try {
  220. ws.send(JSON.stringify({ error: error.message }));
  221. } catch (sendError) {
  222. log(`Error sending error response:`, { error: sendError.message });
  223. }
  224. }
  225. });
  226. ws.on('close', () => {
  227. stats.activeConnections--;
  228. log(`Connection closed: ${connectionId}`);
  229. });
  230. ws.send(JSON.stringify({
  231. type: 'system',
  232. message: 'Secure WebSocket server connected',
  233. connectionId,
  234. timestamp: new Date().toISOString()
  235. }));
  236. });
  237. server.listen(WS_PORT, () => {
  238. log(`Server listening on port ${WS_PORT}`);
  239. });
  240. return wss;
  241. }
  242. // Register a test function
  243. registerFunction('test', 'return "Hello, World!";', []);
  244. // Start server when run directly
  245. if (require.main === module) {
  246. log('Starting server...');
  247. startServer();
  248. }
  249. // Export functions
  250. module.exports = {
  251. startServer,
  252. registerFunction,
  253. executeFunction
  254. };