1+ import ivm from 'isolated-vm' ;
2+ import https from 'https' ;
3+
4+ function extractFetchUrls ( str ) {
5+ const regex = / \/ \/ @ f e t c h \( ( h t t p s ? : \/ \/ [ ^ \s ] + ) \) / g;
6+ let match ;
7+ const urls = [ ] ;
8+
9+ while ( ( match = regex . exec ( str ) ) !== null ) {
10+ urls . push ( match [ 1 ] ) ;
11+ }
12+
13+ return urls ;
14+ }
15+ function fetchCodeFromCDN ( url ) {
16+ return new Promise ( ( resolve , reject ) => {
17+ https
18+ . get ( url , ( res ) => {
19+ let data = '' ;
20+ res . on ( 'data' , ( chunk ) => ( data += chunk ) ) ;
21+ res . on ( 'end' , ( ) => resolve ( data ) ) ;
22+ } )
23+ . on ( 'error' , reject ) ;
24+ } ) ;
25+ }
26+
27+ async function setupIsolate ( ) {
28+ try {
29+ const isolate = new ivm . Isolate ( { memoryLimit : 128 } ) ;
30+ const context = await isolate . createContext ( ) ;
31+ const jail = context . global ;
32+ await jail . set ( 'global' , jail . derefInto ( ) ) ;
33+ // Define a SafeBuffer object
34+ const ___internal = {
35+ b64decode : ( str ) => Buffer . from ( str , 'base64' ) . toString ( 'utf8' ) ,
36+ b64encode : ( str ) => Buffer . from ( str , 'utf8' ) . toString ( 'base64' ) ,
37+ } ;
38+
39+ //const closureStr =
40+ const keys = Object . keys ( ___internal ) ;
41+ const functions = keys . map ( ( key ) => ___internal [ key ] ) ;
42+ const closure = `
43+ globalThis.___internal = {
44+ ${ keys . map ( ( key , i ) => `${ key } : $${ i } ` ) . join ( ',\n' ) }
45+ }` ;
46+
47+ await context . evalClosure ( closure , functions ) ;
48+
49+ return { isolate, context, jail } ;
50+ } catch ( error ) {
51+ console . error ( error ) ;
52+ throw error ;
53+ }
54+ }
55+
56+ export async function runJs ( code : string ) {
57+ try {
58+ if ( ! code ) {
59+ throw new Error ( 'No code provided' ) ;
60+ }
61+
62+ if ( ! code . endsWith ( ';' ) ) code += ';' ;
63+
64+ const { isolate, context, jail } = await setupIsolate ( ) ;
65+ const remoteUrls = await extractFetchUrls ( code ) ;
66+ for ( const url of remoteUrls ) {
67+ const remoteCode = await fetchCodeFromCDN ( url ) ;
68+ await context . eval ( `${ remoteCode } ` ) ;
69+ }
70+
71+ const executionCode = `
72+ (async () => {
73+ ${ code }
74+ globalThis.__finalResult = result;
75+ })();
76+ ` ;
77+
78+ // Execute the original code
79+ const executeScript = await isolate . compileScript ( executionCode ) . catch ( ( err ) => {
80+ console . error ( err ) ;
81+ return { error : 'Compile Error - ' + err . message } ;
82+ } ) ;
83+ if ( 'error' in executeScript ) {
84+ throw new Error ( executeScript . error ) ;
85+ }
86+
87+ await executeScript . run ( context ) . catch ( ( err ) => {
88+ console . error ( err ) ;
89+ throw new Error ( 'Run Error - ' + err . message ) ;
90+ } ) ;
91+
92+ // Try to get the result from the global variable first, then fallback to 'result'
93+ let rawResult = await context . eval ( 'globalThis.__finalResult' ) . catch ( ( err ) => {
94+ console . error ( 'Failed to get __finalResult:' , err ) ;
95+ return null ;
96+ } ) ;
97+
98+ if ( rawResult ?. error ) {
99+ throw new Error ( rawResult . error ) ;
100+ }
101+ return { Output : rawResult } ;
102+ } catch ( error ) {
103+ console . error ( error ) ;
104+ throw new Error ( error . message ) ;
105+ }
106+ }
107+
108+ function getParametersString ( parameters : string [ ] , inputs : Record < string , any > ) {
109+ let params = [ ] ;
110+ for ( const parameter of parameters ) {
111+ if ( typeof inputs [ parameter ] === 'string' ) {
112+ params . push ( `'${ inputs [ parameter ] } '` ) ;
113+ } else {
114+ params . push ( `${ inputs [ parameter ] } ;` ) ;
115+ }
116+ }
117+ return params . join ( ',' ) ;
118+ }
119+
120+ export function generateExecutableCode ( code : string , parameters : string [ ] , inputs : Record < string , any > ) {
121+ const executableCode = `
122+ ${ code }
123+ const result = await main(${ getParametersString ( parameters , inputs ) } );
124+ `
125+ return executableCode ;
126+ }
0 commit comments