server-function-hub (2).js 8.1 KB

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