mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 09:23:17 +01:00
parent
7e305be817
commit
26f8dd7a7b
23
.github/workflows/tests.yml
vendored
23
.github/workflows/tests.yml
vendored
@ -26,7 +26,18 @@ jobs:
|
||||
npm run build --workspace=packages/bruno-common
|
||||
npm run build --workspace=packages/bruno-query
|
||||
|
||||
# test
|
||||
# rebuild isolated-vm for bruno-js
|
||||
- name: Rebuild bruno-js isolated-vm
|
||||
run: |
|
||||
cd packages/bruno-js/node_modules/isolated-vm
|
||||
npm run rebuild || true
|
||||
|
||||
# tests
|
||||
- name: Test Package bruno-js
|
||||
run: npm run test --workspace=packages/bruno-js
|
||||
- name: Test Package bruno-cli
|
||||
run: npm run test --workspace=packages/bruno-cli
|
||||
|
||||
- name: Test Package bruno-query
|
||||
run: npm run test --workspace=packages/bruno-query
|
||||
- name: Test Package bruno-lang
|
||||
@ -35,12 +46,8 @@ jobs:
|
||||
run: npm run test --workspace=packages/bruno-schema
|
||||
- name: Test Package bruno-app
|
||||
run: npm run test --workspace=packages/bruno-app
|
||||
- name: Test Package bruno-js
|
||||
run: npm run test --workspace=packages/bruno-js
|
||||
- name: Test Package bruno-common
|
||||
run: npm run test --workspace=packages/bruno-common
|
||||
- name: Test Package bruno-cli
|
||||
run: npm run test --workspace=packages/bruno-cli
|
||||
- name: Test Package bruno-electron
|
||||
run: npm run test --workspace=packages/bruno-electron
|
||||
|
||||
@ -63,6 +70,12 @@ jobs:
|
||||
npm run build --workspace=packages/bruno-query
|
||||
npm run build --workspace=packages/bruno-common
|
||||
|
||||
# rebuild isolated-vm for bruno-js
|
||||
- name: Rebuild bruno-js isolated-vm
|
||||
run: |
|
||||
cd packages/bruno-js/node_modules/isolated-vm
|
||||
npm run rebuild || true
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd packages/bruno-tests/collection
|
||||
|
27072
package-lock.json
generated
27072
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -47,12 +47,14 @@
|
||||
"test:prettier:web": "npm run test:prettier --workspace=packages/bruno-app",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"rollup": "3.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"isolated-vm": "^5.0.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
"lossless-json": "^4.0.1"
|
||||
"lossless-json": "^4.0.1",
|
||||
"rollup-plugin-node-builtins": "^2.1.2",
|
||||
"rollup-plugin-node-globals": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const ModalHeader = ({ title, handleCancel, customHeader }) => (
|
||||
const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
|
||||
<div className="bruno-modal-header">
|
||||
{customHeader ? customHeader : <>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>}
|
||||
{handleCancel ? (
|
||||
{handleCancel && !hideClose ? (
|
||||
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null}>
|
||||
×
|
||||
</div>
|
||||
@ -63,6 +63,7 @@ const Modal = ({
|
||||
confirmDisabled,
|
||||
hideCancel,
|
||||
hideFooter,
|
||||
hideClose,
|
||||
disableCloseOnOutsideClick,
|
||||
disableEscapeKey,
|
||||
onClick,
|
||||
@ -100,7 +101,12 @@ const Modal = ({
|
||||
return (
|
||||
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
||||
<div className={`bruno-modal-card modal-${size}`}>
|
||||
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} customHeader={customHeader} />
|
||||
<ModalHeader
|
||||
title={title}
|
||||
hideClose={hideClose}
|
||||
handleCancel={() => closeModal({ type: 'icon' })}
|
||||
customHeader={customHeader}
|
||||
/>
|
||||
<ModalContent>{children}</ModalContent>
|
||||
<ModalFooter
|
||||
confirmText={confirmText}
|
||||
|
@ -18,6 +18,7 @@ import CollectionSettings from 'components/CollectionSettings';
|
||||
import { DocExplorer } from '@usebruno/graphql-docs';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import SecuritySettings from 'components/SecuritySettings/index';
|
||||
import FolderSettings from 'components/FolderSettings';
|
||||
|
||||
const MIN_LEFT_PANE_WIDTH = 300;
|
||||
@ -137,6 +138,10 @@ const RequestTabPanel = () => {
|
||||
return <FolderSettings collection={collection} folder={folder} />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'security-settings') {
|
||||
return <SecuritySettings collection={collection} />;
|
||||
}
|
||||
|
||||
const item = findItemInCollection(collection, activeTabUid);
|
||||
if (!item || !item.uid) {
|
||||
return <RequestNotFound itemUid={activeTabUid} />;
|
||||
|
@ -1,13 +1,15 @@
|
||||
import React from 'react';
|
||||
import { uuid } from 'utils/common';
|
||||
import { IconFiles, IconRun, IconEye, IconSettings } from '@tabler/icons';
|
||||
import { IconFiles, IconRun, IconEye, IconSettings, IconShieldLock } from '@tabler/icons';
|
||||
import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import SecuritySettingsIcon from 'components/SecuritySettings/SecurityIconWithModal/index';
|
||||
|
||||
const CollectionToolBar = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const appMode = collection?.securityConfig?.appMode;
|
||||
|
||||
const handleRun = () => {
|
||||
dispatch(
|
||||
@ -39,6 +41,16 @@ const CollectionToolBar = ({ collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const viewSecuritySettings = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'security-settings'
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="flex items-center p-2">
|
||||
@ -47,6 +59,17 @@ const CollectionToolBar = ({ collection }) => {
|
||||
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-end">
|
||||
{appMode && (
|
||||
<span
|
||||
className={`mr-4 border border-slate-500 px-2 py-1 rounded-md text-xs cursor-pointer opacity-70 ${appMode}`}
|
||||
onClick={viewSecuritySettings}
|
||||
>
|
||||
{appMode} mode
|
||||
</span>
|
||||
)}
|
||||
<span className="mr-2">
|
||||
<SecuritySettingsIcon collection={collection} />
|
||||
</span>
|
||||
<span className="mr-2">
|
||||
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />
|
||||
</span>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { IconVariable, IconSettings, IconRun, IconFolder } from '@tabler/icons';
|
||||
import { IconVariable, IconSettings, IconRun, IconFolder, IconShieldLock } from '@tabler/icons';
|
||||
|
||||
const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
||||
const getTabInfo = (type, tabName) => {
|
||||
@ -12,6 +12,14 @@ const SpecialTab = ({ handleCloseClick, type, tabName }) => {
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'security-settings': {
|
||||
return (
|
||||
<>
|
||||
<IconShieldLock size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||
<span className="ml-1">Security</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
case 'folder-settings': {
|
||||
return (
|
||||
<div className="flex items-center flex-nowrap overflow-hidden">
|
||||
|
@ -81,7 +81,7 @@ const RequestTab = ({ tab, collection, folderUid }) => {
|
||||
return color;
|
||||
};
|
||||
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
|
||||
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner'].includes(tab.type)) {
|
||||
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
|
||||
return (
|
||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||
{tab.type === 'folder-settings' ? (
|
||||
|
@ -0,0 +1,77 @@
|
||||
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useState } from 'react';
|
||||
|
||||
const AppMode = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [selectedAppMode, setSelectedAppMode] = useState(collection?.securityConfig?.appMode || 'developer');
|
||||
|
||||
const handleAppModeChange = (e) => {
|
||||
setSelectedAppMode(e.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
dispatch(
|
||||
saveCollectionSecurityConfig(collection?.uid, {
|
||||
appMode: selectedAppMode,
|
||||
runtime: selectedAppMode === 'developer' ? 'vm2' : selectedAppMode === 'safe' ? 'isolated-vm' : undefined
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
toast.success('App Mode updated successfully');
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('Failed to update JS AppMode'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-xs opacity-70">Choose the app mode for this collection.</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="restricted" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="restricted"
|
||||
name="appMode"
|
||||
value="restricted"
|
||||
checked={selectedAppMode === 'restricted'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
Restricted
|
||||
</label>
|
||||
<label htmlFor="safe" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="safe"
|
||||
name="appMode"
|
||||
value="safe"
|
||||
checked={selectedAppMode === 'safe'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
Safe
|
||||
</label>
|
||||
<label htmlFor="developer" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="developer"
|
||||
name="appMode"
|
||||
value="developer"
|
||||
checked={selectedAppMode === 'developer'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
Developer
|
||||
</label>
|
||||
</div>
|
||||
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppMode;
|
@ -0,0 +1,76 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useState } from 'react';
|
||||
|
||||
const JSRuntime = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const runtime = collection?.brunoConfig?.security?.runtime;
|
||||
const appMode = collection?.brunoConfig?.security?.appMode;
|
||||
const [selectedRuntime, setSelectedRuntime] = useState(runtime || 'vm2');
|
||||
|
||||
if (appMode === 'restricted') {
|
||||
return <div className="">No Runtime. Code Execution is blocked in restricted mode.</div>;
|
||||
}
|
||||
|
||||
if (appMode === 'safe') {
|
||||
return <div className="">In Safe Mode the code execution happens isolated-vm runtime.</div>;
|
||||
}
|
||||
|
||||
const handleRuntimeChange = (e) => {
|
||||
setSelectedRuntime(e.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
let brunoConfig = cloneDeep(collection.brunoConfig) || {};
|
||||
brunoConfig.security = {
|
||||
...(brunoConfig?.security || {}),
|
||||
runtime: selectedRuntime
|
||||
};
|
||||
dispatch(updateBrunoConfig(brunoConfig, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('JS Runtime updated successfully');
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('Failed to update JS Runtime'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-xs opacity-70">Choose the JavaScript runtime for this collection.</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="node-vm" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="node-vm"
|
||||
name="runtime"
|
||||
value="node-vm"
|
||||
checked={selectedRuntime === 'node-vm'}
|
||||
// onChange={handleRuntimeChange}
|
||||
className="cursor-pointer opacity-50"
|
||||
/>
|
||||
Node:VM
|
||||
</label>
|
||||
<label htmlFor="vm2" className="flex flex-row gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
id="vm2"
|
||||
name="runtime"
|
||||
value="vm2"
|
||||
checked={selectedRuntime === 'vm2'}
|
||||
onChange={handleRuntimeChange}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
VM2
|
||||
</label>
|
||||
</div>
|
||||
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JSRuntime;
|
@ -0,0 +1,102 @@
|
||||
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useState } from 'react';
|
||||
import Portal from 'components/Portal/index';
|
||||
import Modal from 'components/Modal/index';
|
||||
|
||||
const AppModeModal = ({ collection, onClose }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [selectedAppMode, setSelectedAppMode] = useState(collection?.securityConfig?.appMode || 'developer');
|
||||
|
||||
const handleAppModeChange = (e) => {
|
||||
setSelectedAppMode(e.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
dispatch(
|
||||
saveCollectionSecurityConfig(collection?.uid, {
|
||||
appMode: selectedAppMode,
|
||||
runtime: selectedAppMode === 'developer' ? 'vm2' : selectedAppMode === 'safe' ? 'isolated-vm' : undefined
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
toast.success('App Mode updated successfully');
|
||||
onClose();
|
||||
})
|
||||
.catch((err) => console.log(err) && toast.error('Failed to update JS AppMode'));
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'App Mode'}
|
||||
confirmText="Save"
|
||||
handleConfirm={handleSave}
|
||||
hideCancel={true}
|
||||
hideClose={true}
|
||||
disableCloseOnOutsideClick={true}
|
||||
disableEscapeKey={true}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-xs opacity-70">Choose a app mode for this collection.</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label htmlFor="restricted" className="flex flex-row gap-2 cursor-pointer items-start">
|
||||
<input
|
||||
type="radio"
|
||||
id="restricted"
|
||||
name="appMode"
|
||||
value="restricted"
|
||||
checked={selectedAppMode === 'restricted'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer mt-1"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
Restricted
|
||||
<div className="opacity-50 text-xs">Code execution is not allowed.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label htmlFor="safe" className="flex flex-row gap-2 cursor-pointer items-start">
|
||||
<input
|
||||
type="radio"
|
||||
id="safe"
|
||||
name="appMode"
|
||||
value="safe"
|
||||
checked={selectedAppMode === 'safe'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer mt-1"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
Safe
|
||||
<div className="opacity-50 text-xs">Code is executed in isolated-vm.</div>
|
||||
</div>
|
||||
</label>
|
||||
<label htmlFor="developer" className="flex flex-row gap-2 cursor-pointer items-start">
|
||||
<input
|
||||
type="radio"
|
||||
id="developer"
|
||||
name="appMode"
|
||||
value="developer"
|
||||
checked={selectedAppMode === 'developer'}
|
||||
onChange={handleAppModeChange}
|
||||
className="cursor-pointer mt-1"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
Developer
|
||||
<div className="opacity-50 text-xs">Code execution is fully anabled.</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{/* <button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Save
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppModeModal;
|
@ -0,0 +1,35 @@
|
||||
import { setShowAppModeModal } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { IconShieldLock } from '@tabler/icons';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { uuid } from 'utils/common/index';
|
||||
import AppModeModal from './AppModeModal/index';
|
||||
|
||||
const SecuritySettingsIcon = ({ collection }) => {
|
||||
const showAppModeModel = collection?.showAppModeModal;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const viewSecuritySettings = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
uid: uuid(),
|
||||
collectionUid: collection.uid,
|
||||
type: 'security-settings'
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconShieldLock className="cursor-pointer" size={20} strokeWidth={1.5} onClick={viewSecuritySettings} />
|
||||
{showAppModeModel ? (
|
||||
<AppModeModal
|
||||
collection={collection}
|
||||
onClose={() => dispatch(setShowAppModeModal({ showAppModeModal: false, collectionUid: collection?.uid }))}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecuritySettingsIcon;
|
@ -0,0 +1,46 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
max-width: 800px;
|
||||
|
||||
div.tabs {
|
||||
div.tab {
|
||||
padding: 6px 0px;
|
||||
border: none;
|
||||
border-bottom: solid 2px transparent;
|
||||
margin-right: 1.25rem;
|
||||
color: var(--color-tab-inactive);
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:focus-within,
|
||||
&:focus-visible,
|
||||
&:target {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
table {
|
||||
thead,
|
||||
td {
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
|
||||
li {
|
||||
background-color: ${(props) => props.theme.bg} !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
54
packages/bruno-app/src/components/SecuritySettings/index.js
Normal file
54
packages/bruno-app/src/components/SecuritySettings/index.js
Normal file
@ -0,0 +1,54 @@
|
||||
import classnames from 'classnames';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateSecuritySettingsSelectedTab } from 'providers/ReduxStore/slices/collections/index';
|
||||
import JSRuntime from './JSRuntime/index';
|
||||
import AppMode from './AppMode/index';
|
||||
|
||||
const SecuritySettings = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const activeTab = collection.securitySettingsSelectedTab || 'appMode';
|
||||
const selectTab = (tab) => {
|
||||
dispatch(
|
||||
updateSecuritySettingsSelectedTab({
|
||||
collectionUid: collection.uid,
|
||||
tab
|
||||
})
|
||||
);
|
||||
};
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'appMode': {
|
||||
return <AppMode collection={collection} />;
|
||||
}
|
||||
case 'jsRuntime': {
|
||||
return <JSRuntime collection={collection} />;
|
||||
}
|
||||
default: {
|
||||
return <div className="mt-4">404 | Not found</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
active: tabName === activeTab
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('appMode')} role="tab" onClick={() => selectTab('appMode')}>
|
||||
App Mode
|
||||
</div>
|
||||
<div className={getTabClassname('jsRuntime')} role="tab" onClick={() => selectTab('jsRuntime')}>
|
||||
JS Runtime
|
||||
</div>
|
||||
</div>
|
||||
<section className="mt-4 h-full">{getTabPanel(activeTab)}</section>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecuritySettings;
|
@ -33,7 +33,8 @@ import {
|
||||
requestCancelled,
|
||||
resetRunResults,
|
||||
responseReceived,
|
||||
updateLastAction
|
||||
updateLastAction,
|
||||
setCollectionSecurityConfig
|
||||
} from './index';
|
||||
|
||||
import { each } from 'lodash';
|
||||
@ -1039,11 +1040,13 @@ export const openCollectionEvent = (uid, pathname, brunoConfig) => (dispatch, ge
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
collectionSchema
|
||||
.validate(collection)
|
||||
.then(() => dispatch(_createCollection(collection)))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
ipcRenderer.invoke('renderer:get-collection-security-config', pathname).then((securityConfig) => {
|
||||
collectionSchema
|
||||
.validate(collection)
|
||||
.then(() => dispatch(_createCollection({ ...collection, securityConfig })))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -1108,3 +1111,19 @@ export const importCollection = (collection, collectionLocation) => (dispatch, g
|
||||
ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const saveCollectionSecurityConfig = (collectionUid, securityConfig) => (dispatch, getState) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
const state = getState();
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
|
||||
ipcRenderer
|
||||
.invoke('renderer:save-collection-security-config', collection?.pathname, securityConfig)
|
||||
.then(async () => {
|
||||
await dispatch(setCollectionSecurityConfig({ collectionUid, securityConfig }));
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
@ -33,6 +33,9 @@ export const collectionsSlice = createSlice({
|
||||
const collection = action.payload;
|
||||
|
||||
collection.settingsSelectedTab = 'headers';
|
||||
collection.securitySettingsSelectedTab = 'appMode';
|
||||
|
||||
collection.showAppModeModal = !collection?.securityConfig?.appMode;
|
||||
|
||||
collection.folderLevelSettingsSelectedTab = {};
|
||||
|
||||
@ -51,6 +54,16 @@ export const collectionsSlice = createSlice({
|
||||
state.collections.push(collection);
|
||||
}
|
||||
},
|
||||
setShowAppModeModal: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
collection.showAppModeModal = action.payload.showAppModeModal;
|
||||
},
|
||||
setCollectionSecurityConfig: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
if (collection) {
|
||||
collection.securityConfig = action.payload.securityConfig;
|
||||
}
|
||||
},
|
||||
brunoConfigUpdateEvent: (state, action) => {
|
||||
const { collectionUid, brunoConfig } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
@ -1616,12 +1629,22 @@ export const collectionsSlice = createSlice({
|
||||
item.draft.request.docs = action.payload.docs;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateSecuritySettingsSelectedTab: (state, action) => {
|
||||
const { collectionUid, tab } = action.payload;
|
||||
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
|
||||
if (collection) {
|
||||
collection.securitySettingsSelectedTab = tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
createCollection,
|
||||
setCollectionSecurityConfig,
|
||||
brunoConfigUpdateEvent,
|
||||
renameCollection,
|
||||
removeCollection,
|
||||
@ -1705,7 +1728,9 @@ export const {
|
||||
runRequestEvent,
|
||||
runFolderEvent,
|
||||
resetCollectionRunner,
|
||||
updateRequestDocs
|
||||
updateRequestDocs,
|
||||
updateSecuritySettingsSelectedTab,
|
||||
setShowAppModeModal
|
||||
} = collectionsSlice.actions;
|
||||
|
||||
export default collectionsSlice.reducer;
|
||||
|
@ -24,7 +24,9 @@ export const tabsSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
if (['variables', 'collection-settings', 'collection-runner'].includes(action.payload.type)) {
|
||||
if (
|
||||
['variables', 'collection-settings', 'collection-runner', 'security-settings'].includes(action.payload.type)
|
||||
) {
|
||||
const tab = tabTypeAlreadyExists(state.tabs, action.payload.collectionUid, action.payload.type);
|
||||
if (tab) {
|
||||
state.activeTabUid = tab.uid;
|
||||
|
@ -37,7 +37,7 @@ const runSingleRequest = async function (
|
||||
|
||||
request = prepareRequest(bruJson.request, collectionRoot);
|
||||
|
||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(brunoConfig, 'security', {}) };
|
||||
|
||||
// make axios work in node using form data
|
||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||
@ -57,7 +57,7 @@ const runSingleRequest = async function (
|
||||
// run pre-request vars
|
||||
const preRequestVars = get(bruJson, 'request.vars.req');
|
||||
if (preRequestVars?.length) {
|
||||
const varsRuntime = new VarsRuntime();
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
varsRuntime.runPreRequestVars(
|
||||
preRequestVars,
|
||||
request,
|
||||
@ -74,7 +74,7 @@ const runSingleRequest = async function (
|
||||
get(bruJson, 'request.script.req')
|
||||
]).join(os.EOL);
|
||||
if (requestScriptFile?.length) {
|
||||
const scriptRuntime = new ScriptRuntime();
|
||||
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
const result = await scriptRuntime.runRequestScript(
|
||||
decomment(requestScriptFile),
|
||||
request,
|
||||
@ -276,7 +276,7 @@ const runSingleRequest = async function (
|
||||
// run post-response vars
|
||||
const postResponseVars = get(bruJson, 'request.vars.res');
|
||||
if (postResponseVars?.length) {
|
||||
const varsRuntime = new VarsRuntime();
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
varsRuntime.runPostResponseVars(
|
||||
postResponseVars,
|
||||
request,
|
||||
@ -294,7 +294,7 @@ const runSingleRequest = async function (
|
||||
get(bruJson, 'request.script.res')
|
||||
]).join(os.EOL);
|
||||
if (responseScriptFile?.length) {
|
||||
const scriptRuntime = new ScriptRuntime();
|
||||
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
const result = await scriptRuntime.runResponseScript(
|
||||
decomment(responseScriptFile),
|
||||
request,
|
||||
@ -315,7 +315,7 @@ const runSingleRequest = async function (
|
||||
let assertionResults = [];
|
||||
const assertions = get(bruJson, 'request.assertions');
|
||||
if (assertions) {
|
||||
const assertRuntime = new AssertRuntime();
|
||||
const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
assertionResults = assertRuntime.runAssertions(
|
||||
assertions,
|
||||
request,
|
||||
@ -339,7 +339,7 @@ const runSingleRequest = async function (
|
||||
let testResults = [];
|
||||
const testFile = compact([get(collectionRoot, 'request.tests'), get(bruJson, 'request.tests')]).join(os.EOL);
|
||||
if (typeof testFile === 'string') {
|
||||
const testRuntime = new TestRuntime();
|
||||
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
const result = await testRuntime.runTests(
|
||||
decomment(testFile),
|
||||
request,
|
||||
|
@ -8,7 +8,7 @@
|
||||
"author": "Anoop M D <anoop.md1421@gmail.com> (https://helloanoop.com/)",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"dev": "electron .",
|
||||
"dev": "electron --no-node-snapshot .",
|
||||
"dist:mac": "electron-builder --mac --config electron-builder-config.js",
|
||||
"dist:win": "electron-builder --win --config electron-builder-config.js",
|
||||
"dist:linux": "electron-builder --linux AppImage --config electron-builder-config.js",
|
||||
@ -31,6 +31,7 @@
|
||||
"about-window": "^1.15.2",
|
||||
"aws4-axios": "^3.3.0",
|
||||
"axios": "^1.5.1",
|
||||
"browserify": "^17.0.0",
|
||||
"chai": "^4.3.7",
|
||||
"chokidar": "^3.5.3",
|
||||
"content-disposition": "^0.5.4",
|
||||
@ -46,6 +47,7 @@
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"is-number": "^7.0.0",
|
||||
"is-valid-path": "^0.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
|
@ -20,8 +20,10 @@ const { generateUidBasedOnHash, stringifyJson, safeParseJSON, safeStringifyJSON
|
||||
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
|
||||
const { deleteCookiesForDomain, getDomainsWithCookies } = require('../utils/cookies');
|
||||
const EnvironmentSecretsStore = require('../store/env-secrets');
|
||||
const CollectionSecurityStore = require('../store/collection-security');
|
||||
|
||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||
const collectionSecurityStore = new CollectionSecurityStore();
|
||||
|
||||
const envHasSecrets = (environment = {}) => {
|
||||
const secrets = _.filter(environment.variables, (v) => v.secret);
|
||||
@ -665,6 +667,22 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:save-collection-security-config', async (event, collectionPath, securityConfig) => {
|
||||
try {
|
||||
collectionSecurityStore.storeSecurityConfigForCollection(collectionPath, securityConfig);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('renderer:get-collection-security-config', async (event, collectionPath) => {
|
||||
try {
|
||||
return collectionSecurityStore.getSecurityConfigForCollection(collectionPath);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
||||
|
@ -315,7 +315,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
// run pre-request vars
|
||||
const preRequestVars = get(request, 'vars.req', []);
|
||||
if (preRequestVars?.length) {
|
||||
const varsRuntime = new VarsRuntime();
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
varsRuntime.runPreRequestVars(
|
||||
preRequestVars,
|
||||
request,
|
||||
@ -330,7 +330,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
let scriptResult;
|
||||
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL);
|
||||
if (requestScript?.length) {
|
||||
const scriptRuntime = new ScriptRuntime();
|
||||
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
scriptResult = await scriptRuntime.runRequestScript(
|
||||
decomment(requestScript),
|
||||
request,
|
||||
@ -382,7 +382,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
// run post-response vars
|
||||
const postResponseVars = get(request, 'vars.res', []);
|
||||
if (postResponseVars?.length) {
|
||||
const varsRuntime = new VarsRuntime();
|
||||
const varsRuntime = new VarsRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
const result = varsRuntime.runPostResponseVars(
|
||||
postResponseVars,
|
||||
request,
|
||||
@ -416,7 +416,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
|
||||
let scriptResult;
|
||||
if (responseScript?.length) {
|
||||
const scriptRuntime = new ScriptRuntime();
|
||||
const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
scriptResult = await scriptRuntime.runResponseScript(
|
||||
decomment(responseScript),
|
||||
request,
|
||||
@ -459,7 +459,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
const envVars = getEnvVars(environment);
|
||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) };
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
@ -575,7 +575,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
// run assertions
|
||||
const assertions = get(request, 'assertions');
|
||||
if (assertions) {
|
||||
const assertRuntime = new AssertRuntime();
|
||||
const assertRuntime = new AssertRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
const results = assertRuntime.runAssertions(
|
||||
assertions,
|
||||
request,
|
||||
@ -603,7 +603,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
]).join(os.EOL);
|
||||
|
||||
if (typeof testFile === 'string') {
|
||||
const testRuntime = new TestRuntime();
|
||||
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime, mode: scriptingConfig?.appMode });
|
||||
const testResults = await testRuntime.runTests(
|
||||
decomment(testFile),
|
||||
request,
|
||||
@ -660,7 +660,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
const envVars = getEnvVars(environment);
|
||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) };
|
||||
|
||||
await runPreRequest(
|
||||
request,
|
||||
@ -765,7 +765,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
const runtimeVariables = collection.runtimeVariables;
|
||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||
const brunoConfig = getBrunoConfig(collection.uid);
|
||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) };
|
||||
|
||||
await runPreRequest(
|
||||
request,
|
||||
@ -831,7 +831,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
const folderUid = folder ? folder.uid : null;
|
||||
const cancelTokenUid = uuid();
|
||||
const brunoConfig = getBrunoConfig(collectionUid);
|
||||
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) };
|
||||
const collectionRoot = get(collection, 'root', {});
|
||||
|
||||
const abortController = new AbortController();
|
||||
@ -1028,7 +1028,10 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
// run assertions
|
||||
const assertions = get(item, 'request.assertions');
|
||||
if (assertions) {
|
||||
const assertRuntime = new AssertRuntime();
|
||||
const assertRuntime = new AssertRuntime({
|
||||
runtime: scriptingConfig?.runtime,
|
||||
mode: scriptingConfig?.appMode
|
||||
});
|
||||
const results = assertRuntime.runAssertions(
|
||||
assertions,
|
||||
request,
|
||||
@ -1055,7 +1058,10 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
]).join(os.EOL);
|
||||
|
||||
if (typeof testFile === 'string') {
|
||||
const testRuntime = new TestRuntime();
|
||||
const testRuntime = new TestRuntime({
|
||||
runtime: scriptingConfig?.runtime,
|
||||
mode: scriptingConfig?.appMode
|
||||
});
|
||||
const testResults = await testRuntime.runTests(
|
||||
decomment(testFile),
|
||||
request,
|
||||
|
37
packages/bruno-electron/src/store/collection-security.js
Normal file
37
packages/bruno-electron/src/store/collection-security.js
Normal file
@ -0,0 +1,37 @@
|
||||
const _ = require('lodash');
|
||||
const Store = require('electron-store');
|
||||
|
||||
class CollectionSecurityStore {
|
||||
constructor() {
|
||||
this.store = new Store({
|
||||
name: 'collection-security',
|
||||
clearInvalidConfig: true
|
||||
});
|
||||
}
|
||||
|
||||
storeSecurityConfigForCollection(collectionPathname, securityConfig) {
|
||||
const collections = this.store.get('collections') || [];
|
||||
const collection = _.find(collections, (c) => c.path === collectionPathname);
|
||||
|
||||
if (!collection) {
|
||||
collections.push({
|
||||
path: collectionPathname,
|
||||
securityConfig
|
||||
});
|
||||
|
||||
this.store.set('collections', collections);
|
||||
return;
|
||||
}
|
||||
|
||||
collection.securityConfig = securityConfig || {};
|
||||
this.store.set('collections', collections);
|
||||
}
|
||||
|
||||
getSecurityConfigForCollection(collectionPathname) {
|
||||
const collections = this.store.get('collections') || [];
|
||||
const collection = _.find(collections, (c) => c.path === collectionPathname);
|
||||
return collection?.securityConfig || {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CollectionSecurityStore;
|
@ -11,25 +11,46 @@
|
||||
"@n8n/vm2": "^3.9.23"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest --testPathIgnorePatterns test.js"
|
||||
"test": "jest --testPathIgnorePatterns test.js",
|
||||
"rebuild:electron": "cd ./node_modules/isolated-vm && npx electron-rebuild",
|
||||
"postinstall": "npm run install:isolated-vm && npm run prebuild:isolated-vm:dev && npm run build:isolated-vm:inbuilt-modules",
|
||||
"install:isolated-vm": "cd ./node_modules/isolated-vm && npm install",
|
||||
"prebuild:isolated-vm:dev": "node ./scripts/prebuild-isolated-vm-for-dev.js",
|
||||
"prebuild:isolated-vm:prod": "node ./scripts/prebuild-isolated-vm-for-prod-builds.js",
|
||||
"build:isolated-vm:inbuilt-modules": "node ./src/sandbox/isolatedvm/utils/bundleLibraries.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@rollup/plugin-commonjs": "^23.0.2",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/query": "0.1.0",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"atob": "^2.1.2",
|
||||
"axios": "^1.5.1",
|
||||
"browserify": "^17.0.0",
|
||||
"btoa": "^1.2.1",
|
||||
"chai": "^4.3.7",
|
||||
"chai-string": "^1.5.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"isolated-vm": "4.6.0",
|
||||
"json-query": "^2.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "3.3.4",
|
||||
"node-fetch": "2.*",
|
||||
"node-vault": "^0.10.2",
|
||||
"rollup": "3.2.5",
|
||||
"rollup-plugin-node-builtins": "^2.1.2",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"stream": "^0.0.2",
|
||||
"terser": "^5.31.1",
|
||||
"uglify-js": "^3.18.0",
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
11
packages/bruno-js/scripts/prebuild-isolated-vm-for-dev.js
Normal file
11
packages/bruno-js/scripts/prebuild-isolated-vm-for-dev.js
Normal file
@ -0,0 +1,11 @@
|
||||
const { execSync } = require('child_process');
|
||||
const os = require('os');
|
||||
|
||||
const platform = os.platform();
|
||||
const arch = os.arch();
|
||||
const target = '21.1.1';
|
||||
|
||||
const command = `cd ./node_modules/isolated-vm && rm -rf prebuilds && mkdir prebuilds && npm run prebuild -- --platform=${platform} --arch=${arch} --target=${target} --runtime=electron`;
|
||||
|
||||
console.log(`Running command: ${command}`);
|
||||
execSync(command, { stdio: 'inherit' });
|
@ -0,0 +1,21 @@
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const darwin_x64 = `cd ./node_modules/isolated-vm && npm run prebuild -- --platform=darwin --arch=x64 --target=21.1.1 --runtime=electron`;
|
||||
const darwin_arm64 = `cd ./node_modules/isolated-vm && npm run prebuild -- --platform=darwin --arch=arm64 --target=21.1.1 --runtime=electron`;
|
||||
const linux_x64 = `cd ./node_modules/isolated-vm && npm run prebuild -- --platform=linux --arch=x64 --target=21.1.1 --runtime=electron`;
|
||||
const linux_arm64 = `cd ./node_modules/isolated-vm && npm run prebuild -- --platform=linux --arch=arm64 --target=21.1.1 --runtime=electron`;
|
||||
const win32_x64 = `cd ./node_modules/isolated-vm && npm run prebuild -- --platform=win32 --arch=x64 --target=21.1.1 --runtime=electron`;
|
||||
const win32_arm64 = `cd ./node_modules/isolated-vm && npm run prebuild -- --platform=win32 --arch=arm64 --target=21.1.1 --runtime=electron`;
|
||||
|
||||
console.log(`Running command: ${darwin_x64}`);
|
||||
execSync(darwin_x64, { stdio: 'inherit' });
|
||||
console.log(`Running command: ${darwin_arm64}`);
|
||||
execSync(darwin_arm64, { stdio: 'inherit' });
|
||||
console.log(`Running command: ${linux_x64}`);
|
||||
execSync(linux_x64, { stdio: 'inherit' });
|
||||
console.log(`Running command: ${linux_arm64}`);
|
||||
execSync(linux_arm64, { stdio: 'inherit' });
|
||||
console.log(`Running command: ${win32_x64}`);
|
||||
execSync(win32_x64, { stdio: 'inherit' });
|
||||
console.log(`Running command: ${win32_arm64}`);
|
||||
execSync(win32_arm64, { stdio: 'inherit' });
|
@ -5,6 +5,7 @@ const Bru = require('../bru');
|
||||
const BrunoRequest = require('../bruno-request');
|
||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||
const { interpolateString } = require('../interpolate-string');
|
||||
const { isolatedVMStrictInstance } = require('../sandbox/isolatedvm');
|
||||
|
||||
const { expect } = chai;
|
||||
chai.use(require('chai-string'));
|
||||
@ -161,7 +162,58 @@ const isUnaryOperator = (operator) => {
|
||||
return unaryOperators.includes(operator);
|
||||
};
|
||||
|
||||
const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
||||
const toNumber = (value) => {
|
||||
const num = Number(value);
|
||||
return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value);
|
||||
};
|
||||
|
||||
const evaluateJsTemplateLiteralBasedOnRuntime = (v, context, runtime, mode) => {
|
||||
let value;
|
||||
if (mode === 'restricted') {
|
||||
let _value = _.get(context, v, v);
|
||||
if (_value && typeof _value == 'object') {
|
||||
value = JSON.stringify(_value);
|
||||
} else if (Number.isNaN(Number(_value))) {
|
||||
value = _value;
|
||||
} else {
|
||||
value = toNumber(_value);
|
||||
}
|
||||
} else if (mode === 'safe') {
|
||||
value = isolatedVMStrictInstance.execute({
|
||||
script: v,
|
||||
context,
|
||||
scriptType: 'template-literal'
|
||||
});
|
||||
} else {
|
||||
value = evaluateJsTemplateLiteral(v, context);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const evaluateJsExpressionBasedOnRuntime = (v, context, runtime, mode) => {
|
||||
let value;
|
||||
if (mode === 'restricted') {
|
||||
let _value = _.get(context, v, v);
|
||||
if (_value && typeof _value == 'object') {
|
||||
value = JSON.stringify(_value);
|
||||
} else if (Number.isNaN(Number(_value))) {
|
||||
value = _value;
|
||||
} else {
|
||||
value = toNumber(_value);
|
||||
}
|
||||
} else if (mode === 'safe') {
|
||||
value = isolatedVMStrictInstance.execute({
|
||||
script: v,
|
||||
context,
|
||||
scriptType: 'expression'
|
||||
});
|
||||
} else {
|
||||
value = evaluateJsExpression(v, context);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const evaluateRhsOperand = (rhsOperand, operator, context, runtime, mode) => {
|
||||
if (isUnaryOperator(operator)) {
|
||||
return;
|
||||
}
|
||||
@ -181,13 +233,27 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
||||
|
||||
return rhsOperand
|
||||
.split(',')
|
||||
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
||||
.map((v) =>
|
||||
evaluateJsTemplateLiteralBasedOnRuntime(
|
||||
interpolateString(v.trim(), interpolationContext),
|
||||
context,
|
||||
runtime,
|
||||
mode
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (operator === 'between') {
|
||||
const [lhs, rhs] = rhsOperand
|
||||
.split(',')
|
||||
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
||||
.map((v) =>
|
||||
evaluateJsTemplateLiteralBasedOnRuntime(
|
||||
interpolateString(v.trim(), interpolationContext),
|
||||
context,
|
||||
runtime,
|
||||
mode
|
||||
)
|
||||
);
|
||||
return [lhs, rhs];
|
||||
}
|
||||
|
||||
@ -200,10 +266,20 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
||||
return interpolateString(rhsOperand, interpolationContext);
|
||||
}
|
||||
|
||||
return evaluateJsTemplateLiteral(interpolateString(rhsOperand, interpolationContext), context);
|
||||
return evaluateJsTemplateLiteralBasedOnRuntime(
|
||||
interpolateString(rhsOperand, interpolationContext),
|
||||
context,
|
||||
runtime,
|
||||
mode
|
||||
);
|
||||
};
|
||||
|
||||
class AssertRuntime {
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
this.mode = props?.mode || 'developer';
|
||||
}
|
||||
|
||||
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
|
||||
@ -238,8 +314,8 @@ class AssertRuntime {
|
||||
const { operator, value: rhsOperand } = parseAssertionOperator(rhsExpr);
|
||||
|
||||
try {
|
||||
const lhs = evaluateJsExpression(lhsExpr, context);
|
||||
const rhs = evaluateRhsOperand(rhsOperand, operator, context);
|
||||
const lhs = evaluateJsExpressionBasedOnRuntime(lhsExpr, context, this.runtime, this.mode);
|
||||
const rhs = evaluateRhsOperand(rhsOperand, operator, context, this.runtime, this.mode);
|
||||
|
||||
switch (operator) {
|
||||
case 'eq':
|
||||
|
@ -28,9 +28,13 @@ const fetch = require('node-fetch');
|
||||
const chai = require('chai');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const NodeVault = require('node-vault');
|
||||
const { isolatedVMAsyncInstance, executeInIsolatedVMAsync } = require('../sandbox/isolatedvm');
|
||||
|
||||
class ScriptRuntime {
|
||||
constructor() {}
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
this.mode = props?.mode || 'developer';
|
||||
}
|
||||
|
||||
// This approach is getting out of hand
|
||||
// Need to refactor this to use a single arg (object) instead of 7
|
||||
@ -44,6 +48,14 @@ class ScriptRuntime {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) {
|
||||
if (this.mode === 'restricted') {
|
||||
return {
|
||||
request,
|
||||
envVariables: cleanJson(envVariables),
|
||||
collectionVariables: cleanJson(collectionVariables),
|
||||
nextRequestName: undefined
|
||||
};
|
||||
}
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
@ -86,43 +98,68 @@ class ScriptRuntime {
|
||||
};
|
||||
}
|
||||
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
atob,
|
||||
btoa,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
chai,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
if (this.mode == 'safe') {
|
||||
// Reuses the same instance of IsolatedVMAsync
|
||||
// TODO: Test for performance
|
||||
// await isolatedVMAsyncInstance.execute({
|
||||
// script,
|
||||
// context,
|
||||
// modules: {}, // todo: module support?
|
||||
// scriptType: 'jsScript'
|
||||
// });
|
||||
await executeInIsolatedVMAsync({
|
||||
script: script,
|
||||
context: context,
|
||||
modules: {},
|
||||
scriptType: 'jsScript'
|
||||
});
|
||||
} else {
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
atob,
|
||||
btoa,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
chai,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
|
||||
await asyncVM();
|
||||
});
|
||||
const asyncVM = vm.run(
|
||||
`module.exports = async () => {
|
||||
console?.debug && console.debug('vm2:pre-request:execution-start');
|
||||
${script}
|
||||
console?.debug && console.debug('vm2:pre-request:execution-end:');
|
||||
}`,
|
||||
path.join(collectionPath, 'vm.js')
|
||||
);
|
||||
await asyncVM();
|
||||
}
|
||||
|
||||
return {
|
||||
request,
|
||||
envVariables: cleanJson(envVariables),
|
||||
@ -142,6 +179,14 @@ class ScriptRuntime {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) {
|
||||
if (this.mode === 'restricted') {
|
||||
return {
|
||||
request,
|
||||
envVariables: cleanJson(envVariables),
|
||||
collectionVariables: cleanJson(collectionVariables),
|
||||
nextRequestName: undefined
|
||||
};
|
||||
}
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
@ -176,47 +221,73 @@ class ScriptRuntime {
|
||||
log: customLogger('log'),
|
||||
info: customLogger('info'),
|
||||
warn: customLogger('warn'),
|
||||
error: customLogger('error')
|
||||
error: customLogger('error'),
|
||||
debug: customLogger('debug')
|
||||
};
|
||||
}
|
||||
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
atob,
|
||||
btoa,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
if (this.mode == 'safe') {
|
||||
// Reuses the same instance of IsolatedVMAsync
|
||||
// TODO: Test for performance
|
||||
// await isolatedVMAsyncInstance.execute({
|
||||
// script,
|
||||
// context,
|
||||
// modules: {}, // todo: module support?
|
||||
// scriptType: 'jsScript'
|
||||
// });
|
||||
await executeInIsolatedVMAsync({
|
||||
script: script,
|
||||
context: context,
|
||||
modules: {},
|
||||
scriptType: 'jsScript'
|
||||
});
|
||||
} else {
|
||||
// DEVELOPER MODE
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
atob,
|
||||
btoa,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
|
||||
await asyncVM();
|
||||
const asyncVM = vm.run(
|
||||
`module.exports = async () => {
|
||||
console?.debug && console.debug('vm2:post-response:execution-start:');
|
||||
${script}
|
||||
console?.debug && console.debug('vm2:post-response:execution-end:');
|
||||
}`,
|
||||
path.join(collectionPath, 'vm.js')
|
||||
);
|
||||
await asyncVM();
|
||||
}
|
||||
|
||||
return {
|
||||
response,
|
||||
|
@ -30,9 +30,13 @@ const axios = require('axios');
|
||||
const fetch = require('node-fetch');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const NodeVault = require('node-vault');
|
||||
const { executeInIsolatedVMAsync, isolatedVMAsyncInstance } = require('../sandbox/isolatedvm');
|
||||
|
||||
class TestRuntime {
|
||||
constructor() {}
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
this.mode = props?.mode || 'developer';
|
||||
}
|
||||
|
||||
async runTests(
|
||||
testsFile,
|
||||
@ -45,6 +49,14 @@ class TestRuntime {
|
||||
processEnvVars,
|
||||
scriptingConfig
|
||||
) {
|
||||
if (this.mode === 'restricted') {
|
||||
return {
|
||||
request,
|
||||
envVariables: cleanJson(envVariables),
|
||||
collectionVariables: cleanJson(collectionVariables),
|
||||
results: []
|
||||
};
|
||||
}
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, requestVariables);
|
||||
const req = new BrunoRequest(request);
|
||||
@ -105,44 +117,68 @@ class TestRuntime {
|
||||
};
|
||||
}
|
||||
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
btoa,
|
||||
atob,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
chai,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
if (this.mode == 'safe') {
|
||||
// Reuses the same instance of IsolatedVMAsync
|
||||
// TODO: Test for performance
|
||||
// await isolatedVMAsyncInstance.execute({
|
||||
// script: testsFile,
|
||||
// context: context,
|
||||
// modules: {},
|
||||
// scriptType: 'jsScript'
|
||||
// });
|
||||
await executeInIsolatedVMAsync({
|
||||
script: testsFile,
|
||||
context: context,
|
||||
modules: {},
|
||||
scriptType: 'jsScript'
|
||||
});
|
||||
} else {
|
||||
// DEVELOPER MODE
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
btoa,
|
||||
atob,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
chai,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
|
||||
await asyncVM();
|
||||
});
|
||||
const asyncVM = vm.run(
|
||||
`module.exports = async () => {
|
||||
console?.debug && console.debug('vm2:tests:execution-start');
|
||||
${testsFile}
|
||||
console?.debug && console.debug('vm2:tests:execution-end:');
|
||||
}`,
|
||||
path.join(collectionPath, 'vm.js')
|
||||
);
|
||||
await asyncVM();
|
||||
}
|
||||
|
||||
return {
|
||||
request,
|
||||
|
@ -3,7 +3,65 @@ const Bru = require('../bru');
|
||||
const BrunoRequest = require('../bruno-request');
|
||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||
|
||||
const { isolatedVMStrictInstance } = require('../sandbox/isolatedvm');
|
||||
|
||||
const toNumber = (value) => {
|
||||
const num = Number(value);
|
||||
return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value);
|
||||
};
|
||||
|
||||
const evaluateJsTemplateLiteralBasedOnRuntime = (v, context, runtime, mode) => {
|
||||
let value;
|
||||
if (mode === 'restricted') {
|
||||
let _value = _.get(context, v, v);
|
||||
if (_value && typeof _value == 'object') {
|
||||
value = JSON.stringify(_value);
|
||||
} else if (Number.isNaN(Number(_value))) {
|
||||
value = _value;
|
||||
} else {
|
||||
value = toNumber(_value);
|
||||
}
|
||||
} else if (mode === 'safe') {
|
||||
value = isolatedVMStrictInstance.execute({
|
||||
script: v,
|
||||
context,
|
||||
scriptType: 'template-literal'
|
||||
});
|
||||
} else {
|
||||
value = evaluateJsTemplateLiteral(v, context);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const evaluateJsExpressionBasedOnRuntime = (v, context, runtime, mode) => {
|
||||
let value;
|
||||
if (mode === 'restricted') {
|
||||
let _value = _.get(context, v, v);
|
||||
if (_value && typeof _value == 'object') {
|
||||
value = JSON.stringify(_value);
|
||||
} else if (Number.isNaN(Number(_value))) {
|
||||
value = _value;
|
||||
} else {
|
||||
value = toNumber(_value);
|
||||
}
|
||||
} else if (mode === 'safe') {
|
||||
value = isolatedVMStrictInstance.execute({
|
||||
script: v,
|
||||
context,
|
||||
scriptType: 'expression'
|
||||
});
|
||||
} else {
|
||||
value = evaluateJsExpression(v, context);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
class VarsRuntime {
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
this.mode = props?.mode || 'developer';
|
||||
}
|
||||
|
||||
runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) {
|
||||
if (!request?.requestVariables) {
|
||||
request.requestVariables = {};
|
||||
@ -28,7 +86,7 @@ class VarsRuntime {
|
||||
};
|
||||
|
||||
_.each(enabledVars, (v) => {
|
||||
const value = evaluateJsTemplateLiteral(v.value, context);
|
||||
const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime, this.mode);
|
||||
request?.requestVariables && (request.requestVariables[v.name] = value);
|
||||
});
|
||||
}
|
||||
@ -59,7 +117,7 @@ class VarsRuntime {
|
||||
const errors = new Map();
|
||||
_.each(enabledVars, (v) => {
|
||||
try {
|
||||
const value = evaluateJsExpression(v.value, context);
|
||||
const value = evaluateJsExpressionBasedOnRuntime(v.value, context, this.runtime, this.mode);
|
||||
bru.setVar(v.name, value);
|
||||
} catch (error) {
|
||||
errors.set(v.name, error);
|
||||
|
333
packages/bruno-js/src/sandbox/isolatedvm/index.js
Normal file
333
packages/bruno-js/src/sandbox/isolatedvm/index.js
Normal file
@ -0,0 +1,333 @@
|
||||
const ivm = require('isolated-vm');
|
||||
const addBruShimToContext = require('./shims/bru');
|
||||
const addBrunoRequestShimToContext = require('./shims/brunoRequest');
|
||||
const addConsoleShimToContext = require('./shims/console');
|
||||
const addBrunoResponseShimToContext = require('./shims/brunoResponse');
|
||||
const addTestShimToContext = require('./shims/test');
|
||||
const fs = require('fs');
|
||||
const addLibraryShimsToContext = require('./shims/lib');
|
||||
|
||||
// execute `npm run build:isolated-vm:inbuilt-modules` if the below file doesn't exist
|
||||
const getBundledCode = require('../../bundle-browser-rollup');
|
||||
|
||||
const toNumber = (value) => {
|
||||
const num = Number(value);
|
||||
return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value);
|
||||
};
|
||||
|
||||
class IsolatedVMStrict {
|
||||
constructor() {
|
||||
this.result = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.isolate = new ivm.Isolate();
|
||||
this.context = this.isolate.createContextSync();
|
||||
this.context.global.setSync('global', this.context.global.derefInto());
|
||||
this.context.evalSync(`
|
||||
let bru = {};
|
||||
let req = {};
|
||||
let res = {};
|
||||
`);
|
||||
this.context.global.setSync('setResult', (arg) => {
|
||||
this.result = arg;
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.isolate.dispose();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.context.evalSync(`
|
||||
bru = {};
|
||||
req = {};
|
||||
res = {};
|
||||
`);
|
||||
}
|
||||
|
||||
execute({ script: externalScript, context: externalContext, scriptType = 'script' }) {
|
||||
if (!isNaN(Number(externalScript))) {
|
||||
return toNumber(externalScript);
|
||||
}
|
||||
try {
|
||||
const { bru, req, res } = externalContext;
|
||||
|
||||
bru && addBruShimToContext(this.context, bru);
|
||||
req && addBrunoRequestShimToContext(this.context, req);
|
||||
res && addBrunoResponseShimToContext(this.context, res);
|
||||
|
||||
const templateLiteralText = `
|
||||
global.value = \`${externalScript}\`;
|
||||
setResult(global.value);
|
||||
`;
|
||||
|
||||
const jsExpressionText = `
|
||||
global.value = ${externalScript};
|
||||
setResult(global.value);
|
||||
`;
|
||||
|
||||
let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText;
|
||||
|
||||
const script = this.isolate.compileScriptSync(scriptText);
|
||||
script.runSync(this.context);
|
||||
this.reset();
|
||||
return this.result;
|
||||
} catch (error) {
|
||||
console.error('Error executing the script!', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isolatedVMStrictInstance = new IsolatedVMStrict();
|
||||
|
||||
const executeInIsolatedVMStrict = ({ script: externalScript, context: externalContext, scriptType = 'script' }) => {
|
||||
if (!isNaN(Number(externalScript))) {
|
||||
return Number(externalScript);
|
||||
}
|
||||
let result;
|
||||
const isolate = new ivm.Isolate();
|
||||
try {
|
||||
const context = isolate.createContextSync();
|
||||
context.global.setSync('global', context.global.derefInto());
|
||||
|
||||
const { bru, req, res } = externalContext;
|
||||
|
||||
context.evalSync(`
|
||||
let bru = {};
|
||||
let req = {};
|
||||
let res = {};
|
||||
`);
|
||||
|
||||
bru && addBruShimToContext(context, bru);
|
||||
req && addBrunoRequestShimToContext(context, req);
|
||||
res && addBrunoResponseShimToContext(context, res);
|
||||
|
||||
context.global.setSync('setResult', function (arg) {
|
||||
result = arg;
|
||||
});
|
||||
|
||||
const templateLiteralText = `
|
||||
let value = \`${externalScript}\`;
|
||||
setResult(value);
|
||||
`;
|
||||
|
||||
const jsExpressionText = `
|
||||
let value = ${externalScript};
|
||||
setResult(value);
|
||||
`;
|
||||
|
||||
let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText;
|
||||
|
||||
const script = isolate.compileScriptSync(scriptText);
|
||||
script.runSync(context);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error executing the script!', error);
|
||||
}
|
||||
isolate.dispose();
|
||||
};
|
||||
|
||||
class IsolatedVMAsync {
|
||||
constructor() {
|
||||
this.result;
|
||||
this.initPromise = this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.isolate = new ivm.Isolate();
|
||||
this.context = this.isolate.createContextSync();
|
||||
this.context.global.setSync('global', this.context.global.derefInto());
|
||||
this.context.evalSync(`
|
||||
let bru = {};
|
||||
let req = {};
|
||||
let res = {};
|
||||
let console = {};
|
||||
global.requireObject = {};
|
||||
global.require = (module) => {
|
||||
return global.requireObject[module];
|
||||
}
|
||||
`);
|
||||
|
||||
try {
|
||||
this.bundledCode = getBundledCode?.toString() || '';
|
||||
await this.context.eval(`(${this.bundledCode})()`);
|
||||
} catch (err) {
|
||||
console.debug('Error bundling libraries', err);
|
||||
}
|
||||
|
||||
await addLibraryShimsToContext(this.context);
|
||||
|
||||
this.context.global.setSync('setResult', (arg) => {
|
||||
this.result = arg;
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.isolate.dispose();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.context.evalSync(`
|
||||
bru = {};
|
||||
req = {};
|
||||
res = {};
|
||||
console = {};
|
||||
`);
|
||||
}
|
||||
|
||||
async execute({ script: externalScript, context: externalContext, scriptType = 'script' }) {
|
||||
await this.initPromise;
|
||||
if (!isNaN(Number(externalScript))) {
|
||||
return toNumber(externalScript);
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext;
|
||||
|
||||
bru && addBruShimToContext(this.context, bru);
|
||||
req && addBrunoRequestShimToContext(this.context, req);
|
||||
res && addBrunoResponseShimToContext(this.context, res);
|
||||
consoleFn && addConsoleShimToContext(this.context, consoleFn);
|
||||
|
||||
test && __brunoTestResults && (await addTestShimToContext(this.context, __brunoTestResults));
|
||||
|
||||
const jsScriptText = `
|
||||
new Promise(async (resolve, reject) => {
|
||||
console?.debug && console.debug('isolated-vm:execution-start:');
|
||||
try {
|
||||
${externalScript}
|
||||
} catch (error) {
|
||||
console?.debug && console.debug('isolated-vm:execution-end:with-error', error?.message);
|
||||
}
|
||||
console?.debug && console.debug('isolated-vm:execution-end:');
|
||||
resolve();
|
||||
});
|
||||
`;
|
||||
const templateLiteralText = `
|
||||
global.value = \`${externalScript}\`;
|
||||
setResult(global.value);
|
||||
`;
|
||||
const jsExpressionText = `
|
||||
global.value = ${externalScript};
|
||||
setResult(global.value);
|
||||
`;
|
||||
let scriptText =
|
||||
scriptType === 'template-literal'
|
||||
? templateLiteralText
|
||||
: scriptType === 'expression'
|
||||
? jsExpressionText
|
||||
: jsScriptText;
|
||||
const script = await this.isolate.compileScript(scriptText);
|
||||
await script.run(this.context);
|
||||
this.reset();
|
||||
return this.result;
|
||||
} catch (error) {
|
||||
console.error('Error executing the script!', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isolatedVMAsyncInstance = new IsolatedVMAsync();
|
||||
|
||||
const executeInIsolatedVMAsync = async ({
|
||||
script: externalScript,
|
||||
context: externalContext,
|
||||
modules = {},
|
||||
scriptType = 'script'
|
||||
}) => {
|
||||
if (!isNaN(Number(externalScript))) {
|
||||
return toNumber(externalScript);
|
||||
}
|
||||
let result;
|
||||
const isolate = new ivm.Isolate();
|
||||
try {
|
||||
const context = await isolate.createContext();
|
||||
await context.global.set('global', context.global.derefInto());
|
||||
|
||||
context.evalSync(`
|
||||
let bru = {};
|
||||
let req = {};
|
||||
let res = {};
|
||||
let console = {};
|
||||
global.requireObject = {};
|
||||
`);
|
||||
|
||||
context.global.setSync('log', function (...args) {
|
||||
console.debug(...args);
|
||||
});
|
||||
|
||||
try {
|
||||
const bundledCode = getBundledCode?.toString() || '';
|
||||
await context.eval(`(${bundledCode})()`);
|
||||
} catch (err) {
|
||||
console.debug('Error bundling libraries', err);
|
||||
}
|
||||
|
||||
const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext;
|
||||
|
||||
bru && addBruShimToContext(context, bru);
|
||||
req && addBrunoRequestShimToContext(context, req);
|
||||
res && addBrunoResponseShimToContext(context, res);
|
||||
consoleFn && addConsoleShimToContext(context, consoleFn);
|
||||
|
||||
await context.eval(
|
||||
`
|
||||
global.require = (module) => {
|
||||
return global.requireObject[module];
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
test && __brunoTestResults && (await addTestShimToContext(context, __brunoTestResults));
|
||||
|
||||
context.global.setSync('setResult', function (arg) {
|
||||
result = arg;
|
||||
});
|
||||
|
||||
const jsScriptText = `
|
||||
new Promise(async (resolve, reject) => {
|
||||
console?.debug && console.debug('isolated-vm:execution-start:');
|
||||
try {
|
||||
${externalScript}
|
||||
} catch (error) {
|
||||
console?.debug && console.debug('isolated-vm:execution-end:with-error', error?.message);
|
||||
}
|
||||
console?.debug && console.debug('isolated-vm:execution-end:');
|
||||
resolve();
|
||||
});
|
||||
`;
|
||||
|
||||
const templateLiteralText = `
|
||||
let value = \`${externalScript}\`;
|
||||
setResult(value);
|
||||
`;
|
||||
|
||||
const jsExpressionText = `
|
||||
let value = ${externalScript};
|
||||
setResult(value);
|
||||
`;
|
||||
|
||||
let scriptText =
|
||||
scriptType === 'template-literal'
|
||||
? templateLiteralText
|
||||
: scriptType === 'expression'
|
||||
? jsExpressionText
|
||||
: jsScriptText;
|
||||
|
||||
const script = await isolate.compileScript(scriptText);
|
||||
await script.run(context);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error executing the script!', error);
|
||||
}
|
||||
isolate.dispose();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
executeInIsolatedVMStrict,
|
||||
isolatedVMStrictInstance,
|
||||
executeInIsolatedVMAsync,
|
||||
isolatedVMAsyncInstance
|
||||
};
|
59
packages/bruno-js/src/sandbox/isolatedvm/shims/bru.js
Normal file
59
packages/bruno-js/src/sandbox/isolatedvm/shims/bru.js
Normal file
@ -0,0 +1,59 @@
|
||||
const addBruShimToContext = (context, bru) => {
|
||||
context.global.setSync('cwd', function () {
|
||||
return bru.cwd();
|
||||
});
|
||||
|
||||
context.global.setSync('getEnvName', function () {
|
||||
return bru.getEnvName();
|
||||
});
|
||||
|
||||
context.global.setSync('getProcessEnv', function (key) {
|
||||
return bru.getProcessEnv(key);
|
||||
});
|
||||
|
||||
context.global.setSync('getEnvVar', function (key) {
|
||||
return bru.getEnvVar(key);
|
||||
});
|
||||
|
||||
context.global.setSync('setEnvVar', function (key, value) {
|
||||
bru.setEnvVar(key, value);
|
||||
});
|
||||
|
||||
context.global.setSync('setVar', function (key, value) {
|
||||
bru.setVar(key, value);
|
||||
});
|
||||
|
||||
context.global.setSync('getVar', function (key) {
|
||||
return bru.getVar(key);
|
||||
});
|
||||
|
||||
context.global.setSync('setNextRequest', function (nextRequest) {
|
||||
bru.setNextRequest(nextRequest);
|
||||
});
|
||||
|
||||
context.global.setSync('visualize', function (htmlString) {
|
||||
bru.visualize(htmlString);
|
||||
});
|
||||
|
||||
context.global.setSync('getSecretVar', function (key) {
|
||||
return bru.getSecretVar(key);
|
||||
});
|
||||
|
||||
context.evalSync(`
|
||||
bru = {
|
||||
...bru || {},
|
||||
cwd: global.cwd,
|
||||
getEnvName: global.getEnvName,
|
||||
getProcessEnv: global.getProcessEnv,
|
||||
getEnvVar: global.getEnvVar,
|
||||
setEnvVar: global.setEnvVar,
|
||||
setVar: global.setVar,
|
||||
getVar: global.getVar,
|
||||
setNextRequest: global.setNextRequest,
|
||||
visualize: global.visualize,
|
||||
getSecretVar: global.getSecretVar
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = addBruShimToContext;
|
@ -0,0 +1,79 @@
|
||||
const addBrunoRequestShimToContext = (context, req) => {
|
||||
context.global.setSync('getUrl', function () {
|
||||
return req.getUrl();
|
||||
});
|
||||
|
||||
context.global.setSync('setUrl', function (url) {
|
||||
req.setUrl(url);
|
||||
});
|
||||
|
||||
context.global.setSync('getMethod', function () {
|
||||
return req.getMethod();
|
||||
});
|
||||
|
||||
context.global.setSync('getAuthMode', function () {
|
||||
return req.getAuthMode();
|
||||
});
|
||||
|
||||
context.global.setSync('setMethod', function (method) {
|
||||
req.setMethod(method);
|
||||
});
|
||||
|
||||
context.global.setSync('getHeaders', function () {
|
||||
return req.getHeaders();
|
||||
});
|
||||
|
||||
context.global.setSync('setHeaders', function (headers) {
|
||||
req.setHeaders(headers);
|
||||
});
|
||||
|
||||
context.global.setSync('getHeader', function (name) {
|
||||
return req.getHeader(name);
|
||||
});
|
||||
|
||||
context.global.setSync('setHeader', function (name, value) {
|
||||
req.setHeader(name, value);
|
||||
});
|
||||
|
||||
context.global.setSync('getBody', function () {
|
||||
return req.getBody();
|
||||
});
|
||||
|
||||
context.global.setSync('setBody', function (data) {
|
||||
req.setBody(data);
|
||||
});
|
||||
|
||||
context.global.setSync('setMaxRedirects', function (maxRedirects) {
|
||||
req.setMaxRedirects(maxRedirects);
|
||||
});
|
||||
|
||||
context.global.setSync('getTimeout', function () {
|
||||
return req.getTimeout();
|
||||
});
|
||||
|
||||
context.global.setSync('setTimeout', function (timeout) {
|
||||
req.setTimeout(timeout);
|
||||
});
|
||||
|
||||
context.evalSync(`
|
||||
req = {
|
||||
...req || {},
|
||||
getUrl: global.getUrl,
|
||||
setUrl: global.setUrl,
|
||||
getMethod: global.getMethod,
|
||||
getAuthMode: global.getAuthMode,
|
||||
setMethod: global.setMethod,
|
||||
getHeaders: global.getHeaders,
|
||||
setHeaders: global.setHeaders,
|
||||
getHeader: global.getHeader,
|
||||
setHeader: global.setHeader,
|
||||
getBody: global.getBody,
|
||||
setBody: global.setBody,
|
||||
setMaxRedirects: global.setMaxRedirects,
|
||||
getTimeout: global.getTimeout,
|
||||
setTimeout: global.setTimeout
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = addBrunoRequestShimToContext;
|
@ -0,0 +1,46 @@
|
||||
const ivm = require('isolated-vm');
|
||||
|
||||
const addBrunoResponseShimToContext = (context, res) => {
|
||||
context.global.setSync('status', new ivm.ExternalCopy(res?.status).copyInto());
|
||||
context.global.setSync('headers', new ivm.ExternalCopy(res?.headers).copyInto());
|
||||
context.global.setSync('body', new ivm.ExternalCopy(res?.body).copyInto());
|
||||
context.global.setSync('responseTime', new ivm.ExternalCopy(res?.responseTime).copyInto());
|
||||
|
||||
context.global.setSync('getStatus', function () {
|
||||
return res?.getStatus();
|
||||
});
|
||||
|
||||
context.global.setSync('getHeader', function (name) {
|
||||
return res?.getHeader(name);
|
||||
});
|
||||
|
||||
context.global.setSync('getHeaders', function () {
|
||||
return res?.getHeaders();
|
||||
});
|
||||
|
||||
context.global.setSync('getBody', function () {
|
||||
return res?.getBody();
|
||||
});
|
||||
|
||||
context.global.setSync('getResponseTime', function () {
|
||||
return res?.getResponseTime();
|
||||
});
|
||||
|
||||
context.evalSync(`
|
||||
res = {
|
||||
...res || {},
|
||||
status: global.status,
|
||||
statusText: global.statusText,
|
||||
headers: global.headers,
|
||||
body: global.body,
|
||||
responseTime: global.responseTime,
|
||||
getStatus: global.getStatus,
|
||||
getHeader: global.getHeader,
|
||||
getHeaders: global.getHeaders,
|
||||
getBody: global.getBody,
|
||||
getResponseTime: global.getResponseTime
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = addBrunoResponseShimToContext;
|
39
packages/bruno-js/src/sandbox/isolatedvm/shims/console.js
Normal file
39
packages/bruno-js/src/sandbox/isolatedvm/shims/console.js
Normal file
@ -0,0 +1,39 @@
|
||||
const addConsoleShimToContext = (context, console) => {
|
||||
context.global.setSync('log', function (...args) {
|
||||
console?.log && console.log(...args);
|
||||
return args;
|
||||
});
|
||||
|
||||
context.global.setSync('debug', function (...args) {
|
||||
console?.debug && console.debug(...args);
|
||||
return args;
|
||||
});
|
||||
|
||||
context.global.setSync('info', function (...args) {
|
||||
console?.info && console.info(...args);
|
||||
return args;
|
||||
});
|
||||
|
||||
context.global.setSync('warn', function (...args) {
|
||||
console?.warn && console.warn(...args);
|
||||
return args;
|
||||
});
|
||||
|
||||
context.global.setSync('error', function (...args) {
|
||||
console?.error && console.error(...args);
|
||||
return args;
|
||||
});
|
||||
|
||||
context.evalSync(`
|
||||
console = {
|
||||
...console || {},
|
||||
log: global.log,
|
||||
debug: global.debug,
|
||||
info: global.info,
|
||||
warn: global.warn,
|
||||
error: global.error
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = addConsoleShimToContext;
|
57
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/axios-min.js
vendored
Normal file
57
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/axios-min.js
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
const axios = require('axios');
|
||||
const ivm = require('isolated-vm');
|
||||
const { cleanJson } = require('../../../../utils');
|
||||
|
||||
const addAxiosShimToContext = async (context) => {
|
||||
await context.evalClosure(
|
||||
`
|
||||
globalThis.axios = (...args) => $0.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
${['get', 'post', 'put', 'patch', 'delete']
|
||||
?.map(
|
||||
(method, idx) =>
|
||||
`globalThis.axios.${method} = (...args) => $${
|
||||
idx + 1
|
||||
}.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)))`
|
||||
)
|
||||
.join('\n')}
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
axios: globalThis.axios,
|
||||
}
|
||||
`,
|
||||
[
|
||||
async (...argStrings) => {
|
||||
console.log(argStrings);
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
},
|
||||
...['get', 'post', 'put', 'patch', 'delete']?.map((method) => async (...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios[method](...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
})
|
||||
],
|
||||
{ arguments: { reference: true } }
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addAxiosShimToContext;
|
115
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/axios.js
Normal file
115
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/axios.js
Normal file
@ -0,0 +1,115 @@
|
||||
const axios = require('axios');
|
||||
const ivm = require('isolated-vm');
|
||||
const { cleanJson } = require('../../../../utils');
|
||||
|
||||
const addAxiosShimToContext = async (context) => {
|
||||
await context.evalClosure(
|
||||
`
|
||||
globalThis.axios = (...args) => $0.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.axios.get = (...args) => $1.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.axios.post = (...args) => $2.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.axios.put = (...args) => $3.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.axios.delete = (...args) => $4.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.axios.patch = (...args) => $5.applySyncPromise(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
axios: globalThis.axios,
|
||||
}
|
||||
`,
|
||||
[
|
||||
async (...argStrings) => {
|
||||
console.log(argStrings);
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
},
|
||||
async (...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios
|
||||
.get(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
},
|
||||
async (...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios
|
||||
.post(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
},
|
||||
async (...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios
|
||||
.put(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
},
|
||||
async (...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios
|
||||
.delete(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
},
|
||||
async (...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
const res = await axios
|
||||
.patch(...args)
|
||||
.then((response) => {
|
||||
return cleanJson(response?.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
message: err.message
|
||||
// response: cleanJson(err.response)
|
||||
};
|
||||
});
|
||||
return new ivm.ExternalCopy(res).copyInto({ release: true });
|
||||
}
|
||||
],
|
||||
{ arguments: { reference: true } }
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addAxiosShimToContext;
|
11
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/index.js
Normal file
11
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
const addAxiosShimToContext = require('./axios');
|
||||
const addNanoidShimToContext = require('./nanoid');
|
||||
const addUuidShimToContext = require('./uuid');
|
||||
|
||||
const addLibraryShimsToContext = async (context) => {
|
||||
await addAxiosShimToContext(context);
|
||||
await addNanoidShimToContext(context);
|
||||
await addUuidShimToContext(context);
|
||||
};
|
||||
|
||||
module.exports = addLibraryShimsToContext;
|
23
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/nanoid.js
Normal file
23
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/nanoid.js
Normal file
@ -0,0 +1,23 @@
|
||||
const ivm = require('isolated-vm');
|
||||
const { nanoid } = require('nanoid');
|
||||
|
||||
const addNanoidShimToContext = async (context) => {
|
||||
await context.evalClosure(
|
||||
`
|
||||
globalThis.nanoid = {};
|
||||
globalThis.nanoid.nanoid = () => $0.applySync(undefined);
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
nanoid: globalThis.nanoid
|
||||
}
|
||||
`,
|
||||
[
|
||||
() => {
|
||||
return new ivm.ExternalCopy(nanoid()).copyInto({ release: true });
|
||||
}
|
||||
],
|
||||
{ arguments: { reference: true } }
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addNanoidShimToContext;
|
39
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/uuid-min.js
vendored
Normal file
39
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/uuid-min.js
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
const ivm = require('isolated-vm');
|
||||
const uuid = require('uuid');
|
||||
const { MAX, NIL } = uuid;
|
||||
|
||||
const addUuidShimToContext = async (context) => {
|
||||
await context.evalClosure(
|
||||
`
|
||||
globalThis.uuid = {};
|
||||
globalThis.uuid.MAX = $0;
|
||||
globalThis.uuid.NIL = $1;
|
||||
${['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']
|
||||
?.map(
|
||||
(fn, idx) =>
|
||||
`globalThis.uuid.${fn} = (...args) => $${
|
||||
idx + 2
|
||||
}.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));`
|
||||
)
|
||||
.join('\n')}
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
uuid: globalThis.uuid,
|
||||
}
|
||||
`,
|
||||
[
|
||||
new ivm.ExternalCopy(MAX).copyInto({ release: true }),
|
||||
new ivm.ExternalCopy(NIL).copyInto({ release: true }),
|
||||
...['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']?.map(
|
||||
(fn) =>
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(uuid[fn](...args)).copyInto({ release: true });
|
||||
}
|
||||
)
|
||||
],
|
||||
{ arguments: { reference: true } }
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addUuidShimToContext;
|
83
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/uuid.js
Normal file
83
packages/bruno-js/src/sandbox/isolatedvm/shims/lib/uuid.js
Normal file
@ -0,0 +1,83 @@
|
||||
const ivm = require('isolated-vm');
|
||||
const { MAX, NIL, parse, stringify, v1, v1ToV6, v3, v4, v5, v6, v6ToV1, v7, validate, version } = require('uuid');
|
||||
|
||||
const addUuidShimToContext = async (context) => {
|
||||
await context.evalClosure(
|
||||
`
|
||||
globalThis.uuid = {};
|
||||
globalThis.uuid.MAX = $0;
|
||||
globalThis.uuid.NIL = $1;
|
||||
globalThis.uuid.version = (...args) => $2.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.parse = (...args) => $3.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.stringify = (...args) => $4.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v1 = (...args) => $5.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v1ToV6 = (...args) => $6.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v3 = (...args) => $7.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v4 = (...args) => $8.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v5 = (...args) => $9.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v6 = (...args) => $10.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v6ToV1 = (...args) => $11.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.v7 = (...args) => $12.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.uuid.validate = (...args) => $13.applySync(undefined, args?.map(arg=>JSON.stringify(arg)));
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
uuid: globalThis.uuid,
|
||||
}
|
||||
`,
|
||||
[
|
||||
new ivm.ExternalCopy(MAX).copyInto({ release: true }),
|
||||
new ivm.ExternalCopy(NIL).copyInto({ release: true }),
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(version(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(parse(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(stringify(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v1(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v1ToV6(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v3(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v4(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v5(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v6(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v6ToV1(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(v7(...args)).copyInto({ release: true });
|
||||
},
|
||||
(...argStrings) => {
|
||||
let args = argStrings?.map((arg) => JSON.parse(arg));
|
||||
return new ivm.ExternalCopy(validate(...args)).copyInto({ release: true });
|
||||
}
|
||||
],
|
||||
{ arguments: { reference: true } }
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addUuidShimToContext;
|
57
packages/bruno-js/src/sandbox/isolatedvm/shims/test.js
Normal file
57
packages/bruno-js/src/sandbox/isolatedvm/shims/test.js
Normal file
@ -0,0 +1,57 @@
|
||||
const addTestShimToContext = async (context, __brunoTestResults) => {
|
||||
context.global.setSync('addResult', function (v) {
|
||||
__brunoTestResults.addResult(v);
|
||||
});
|
||||
|
||||
context.global.setSync('getResults', function () {
|
||||
return __brunoTestResults.getResults();
|
||||
});
|
||||
|
||||
context.evalSync(`
|
||||
global.expect = require('chai').expect;
|
||||
global.assert = require('chai').assert;
|
||||
|
||||
global.__brunoTestResults = {
|
||||
addResult: global.addResult,
|
||||
getResults: global.getResults,
|
||||
}
|
||||
|
||||
|
||||
global.DummyChaiAssertionError = class DummyChaiAssertionError extends Error {
|
||||
constructor(message, props, ssf) {
|
||||
super(message);
|
||||
this.name = "AssertionError";
|
||||
Object.assign(this, props);
|
||||
}
|
||||
}
|
||||
|
||||
global.Test = (__brunoTestResults) => async (description, callback) => {
|
||||
try {
|
||||
await callback();
|
||||
__brunoTestResults.addResult({ description, status: "pass" });
|
||||
} catch (error) {
|
||||
if (error instanceof DummyChaiAssertionError) {
|
||||
const { message, actual, expected } = error;
|
||||
__brunoTestResults.addResult({
|
||||
description,
|
||||
status: "fail",
|
||||
error: message,
|
||||
actual,
|
||||
expected,
|
||||
});
|
||||
} else {
|
||||
__brunoTestResults.addResult({
|
||||
description,
|
||||
status: "fail",
|
||||
error: error.message || "An unexpected error occurred.",
|
||||
});
|
||||
}
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
global.test = Test(__brunoTestResults);
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = addTestShimToContext;
|
@ -0,0 +1,90 @@
|
||||
const rollup = require('rollup');
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const fs = require('fs');
|
||||
const { terser } = require('rollup-plugin-terser');
|
||||
|
||||
const bundleLibraries = async () => {
|
||||
const codeScript = `
|
||||
import isNumber from "is-number";
|
||||
global.isNumber = isNumber;
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { expect, assert } from 'chai';
|
||||
import { Buffer } from "buffer";
|
||||
import moment from "moment";
|
||||
import btoa from "btoa";
|
||||
import atob from "atob";
|
||||
global.expect = expect;
|
||||
global.assert = assert;
|
||||
global.faker = faker;
|
||||
global.moment = moment;
|
||||
global.btoa = btoa;
|
||||
global.atob = atob;
|
||||
global.Buffer = Buffer;
|
||||
global.requireObject = {
|
||||
'chai': { expect, assert },
|
||||
'faker': faker,
|
||||
'@faker-js/faker': { faker },
|
||||
'moment': moment,
|
||||
'buffer': { Buffer },
|
||||
'btoa': btoa,
|
||||
'atob': atob,
|
||||
};
|
||||
`;
|
||||
|
||||
const config = {
|
||||
input: {
|
||||
input: 'inline-code',
|
||||
plugins: [
|
||||
{
|
||||
name: 'inline-code-plugin',
|
||||
resolveId(id) {
|
||||
if (id === 'inline-code') {
|
||||
return id;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
load(id) {
|
||||
if (id === 'inline-code') {
|
||||
return codeScript;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
nodeResolve({
|
||||
preferBuiltins: false,
|
||||
browser: false
|
||||
}),
|
||||
commonjs(),
|
||||
terser()
|
||||
]
|
||||
},
|
||||
output: {
|
||||
file: './src/bundle-browser-rollup.js',
|
||||
format: 'iife',
|
||||
name: 'MyBundle'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const bundle = await rollup.rollup(config.input);
|
||||
const { output } = await bundle.generate(config.output);
|
||||
fs.writeFileSync(
|
||||
'./src/bundle-browser-rollup.js',
|
||||
`
|
||||
const getBundledCode = () => {
|
||||
return function(){
|
||||
${output?.map((o) => o.code).join('\n')}
|
||||
}()
|
||||
}
|
||||
module.exports = getBundledCode;
|
||||
`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error while bundling:', error);
|
||||
}
|
||||
};
|
||||
|
||||
bundleLibraries();
|
||||
|
||||
module.exports = bundleLibraries;
|
@ -35,7 +35,7 @@ describe('runtime', () => {
|
||||
})
|
||||
`;
|
||||
|
||||
const runtime = new TestRuntime();
|
||||
const runtime = new TestRuntime({ runtime: 'vm2' });
|
||||
const result = await runtime.runTests(
|
||||
testFile,
|
||||
{ ...baseRequest },
|
||||
@ -71,7 +71,7 @@ describe('runtime', () => {
|
||||
})
|
||||
`;
|
||||
|
||||
const runtime = new TestRuntime();
|
||||
const runtime = new TestRuntime({ runtime: 'vm2' });
|
||||
const result = await runtime.runTests(
|
||||
testFile,
|
||||
{ ...baseRequest },
|
||||
@ -114,7 +114,7 @@ describe('runtime', () => {
|
||||
bru.setVar('validation', validate(new Date().toISOString()))
|
||||
`;
|
||||
|
||||
const runtime = new ScriptRuntime();
|
||||
const runtime = new ScriptRuntime({ runtime: 'vm2', mode: 'developer' });
|
||||
const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env);
|
||||
expect(result.runtimeVariables.validation).toBeTruthy();
|
||||
});
|
||||
@ -160,7 +160,7 @@ describe('runtime', () => {
|
||||
bru.setVar('validation', validate(new Date().toISOString()))
|
||||
`;
|
||||
|
||||
const runtime = new ScriptRuntime();
|
||||
const runtime = new ScriptRuntime({ runtime: 'vm2', mode: 'developer' });
|
||||
const result = await runtime.runResponseScript(
|
||||
script,
|
||||
{ ...baseRequest },
|
||||
|
Loading…
Reference in New Issue
Block a user