Skip to content

Commit

Permalink
feat: improve script component errors
Browse files Browse the repository at this point in the history
Related to #1102
  • Loading branch information
Skaiir committed Apr 2, 2024
1 parent ee7ee0a commit e4a0fa2
Showing 1 changed file with 51 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Sandbox from '@jetbrains/websandbox';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { useExpressionEvaluation, useDeepCompareMemoize, usePrevious } from '../../hooks';
import { isObject } from 'min-dash';
import { v4 as uuidv4 } from 'uuid';
Expand All @@ -16,12 +16,25 @@ export function JSFunctionField(props) {
const [ sandbox, setSandbox ] = useState(null);
const [ hasRunLoad, setHasRunLoad ] = useState(false);
const [ iframeContainerId ] = useState(`fjs-sandbox-iframe-container_${uuidv4()}`);
const iframeContainerRef = useRef(null);

const paramsEval = useExpressionEvaluation(paramsDefinition);
const params = useDeepCompareMemoize(isObject(paramsEval) ? paramsEval : {});

const clearValue = useCallback(() => onChange({ field, value: undefined }), [ field, onChange ]);

const sandboxError = useCallback((errorType, ...details) => {

const baseError = `Sandbox error (${field.key}) - ${errorType}`;

if (details.length) {
console.error(baseError, '-', ...details);
} else {
console.error(baseError);
}

}, [ field.key ]);

const safeSetValue = useCallback((value) => {

if (value !== undefined) {
Expand All @@ -31,53 +44,71 @@ export function JSFunctionField(props) {
value = JSON.parse(JSON.stringify(value));
onChange({ field, value });
} catch (e) {
sandboxError('Unparsable return value');
clearValue();
}
}

}, [ field, onChange, clearValue ]);
}, [ onChange, field, sandboxError, clearValue ]);

useEffect(() => {

// (1) check for syntax validity of user code
try {
new Function(functionDefinition);
} catch (e) {

if (e instanceof SyntaxError) {
sandboxError('Invalid syntax', e.message);
}

return;
}

// (2) create a new sandbox instance
const hostAPI = {
setValue: safeSetValue,
error: (e) => {
runtimeError: (e) => {
sandboxError('Runtime error', e.message);
clearValue();
}
};

// @ts-ignore
const wrappedUserCode = `
const ___executeUserCode___ = (data) => {
try {
const setValue = Websandbox.connection.remote.setValue;
${functionDefinition}
}
catch (e) {
Websandbox.connection.remote.runtimeError(e);
}
}
Websandbox.connection.setLocalApi({ compute: ___executeUserCode___ });
`;

const _sandbox = Sandbox.create(hostAPI, {
frameContainer: `#${iframeContainerId}`,
frameClassName: 'fjs-sandbox-iframe'
});

const wrappedUserCode = `
const computeCallThisFunctionIfYouWantToCrashYourBrowser = (data) => {
try {
const setValue = Websandbox.connection.remote.setValue;
${functionDefinition}
}
catch (e) {
Websandbox.connection.remote.error(e);
}
}
Websandbox.connection.setLocalApi({ compute: computeCallThisFunctionIfYouWantToCrashYourBrowser });
`;
const iframe = iframeContainerRef.current.querySelector('iframe');
iframe.removeAttribute('allow');

// (3) run user code in sandbox
_sandbox.promise.then((sandboxInstance) => {
sandboxInstance

// @ts-ignore
.run(wrappedUserCode)
.catch(() => { onChange({ field, value: null }); })
.then(() => { setSandbox(sandboxInstance); setHasRunLoad(false); });
});

return () => {
_sandbox.destroy();
};
}, [ iframeContainerId, functionDefinition, onChange, field, paramsDefinition, computeOn, interval, safeSetValue, clearValue ]);
}, [ iframeContainerId, functionDefinition, onChange, field, paramsDefinition, computeOn, interval, safeSetValue, clearValue, sandboxError ]);

const prevParams = usePrevious(params);
const prevSandbox = usePrevious(sandbox);
Expand Down Expand Up @@ -109,7 +140,7 @@ export function JSFunctionField(props) {
}, [ params, prevParams, sandbox, prevSandbox, onChange, field, computeOn, hasRunLoad, interval, clearValue, safeSetValue ]);

return (
<div id={ iframeContainerId } className="fjs-sandbox-iframe-container"></div>
<div ref={ iframeContainerRef } id={ iframeContainerId } className="fjs-sandbox-iframe-container"></div>
);
}

Expand Down

0 comments on commit e4a0fa2

Please sign in to comment.