Skip to content

Commit

Permalink
Support custom scripts to determine whether it is up/down based on th…
Browse files Browse the repository at this point in the history
…e response JSON
  • Loading branch information
YinDongFang committed Jan 26, 2025
1 parent 3024c20 commit a656b4b
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Server/db/models/Monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const MonitorSchema = mongoose.Schema(
"distributed_http",
],
},
testScripts: {
type: String,
},
url: {
type: String,
required: true,
Expand Down
158 changes: 156 additions & 2 deletions Server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
"isolated-vm": "^5.0.3",
"joi": "^17.13.1",
"jsonwebtoken": "9.0.2",
"mailersend": "^2.2.0",
Expand Down
12 changes: 10 additions & 2 deletions Server/service/networkService.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { errorMessages, successMessages } from "../utils/messages.js";
import runInSandbox from "../utils/sandbox.js";
const SERVICE_NAME = "NetworkService";

/**
Expand Down Expand Up @@ -135,6 +136,7 @@ class NetworkService {
type: job.data.type,
responseTime,
payload: response?.data,
code: response.status,
};

if (error) {
Expand All @@ -144,8 +146,14 @@ class NetworkService {
httpResponse.message = this.http.STATUS_CODES[code] || "Network Error";
return httpResponse;
}
httpResponse.status = true;
httpResponse.code = response.status;

const testScripts = job.data.testScripts;
if (testScripts) {
const status = await runInSandbox(testScripts, response?.data);
httpResponse.status = !!status;
} else {
httpResponse.status = true;
}
httpResponse.message = this.http.STATUS_CODES[response.status];
return httpResponse;
} catch (error) {
Expand Down
55 changes: 55 additions & 0 deletions Server/utils/sandbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import ivm from 'isolated-vm';

export class TimeoutError extends Error {
constructor(message) { super(message); }
}

export class UserScriptError extends Error {
constructor(message, row, col) {
super(message);
this.row = row;
this.col = col;
}
}

export default async function(code, param) {
// Create a new isolate limited to 128MB
const isolate = new ivm.Isolate({ memoryLimit: 128 });

// Create a new context within this isolate. Each context has its own copy of all the builtin Objects.
// So for instance if one context does Object.prototype.foo = 1 this would not affect any other contexts.
const context = isolate.createContextSync();

// Complete code to be executed by `evalClosure`.
// Add code to get the argument as the `res`, so that `res` can be used in the user code.
// Example:
// return res.code === 200;
// No spaces or line breaks, preserve the error location.
const script =
`const res = $0;
try {
${code}
} catch (e) {
const lines = e.stack.split("\\n");
const [, row, col] = lines[lines.length - 1].match(/\\sat\\s\\<isolated-vm\\>\\:(\\d)\\:(\\d)/);
throw new Error("UserScriptError:" + row + ":" + col + ":" + e.message);
}
`;

// Compiles and runs code as if it were inside a function, similar to the seldom-used new Function(code) constructor.
// The function will return a Promise while the work runs in a separate thread pool.
// After the timeout, the thread will be automatically terminated, and we do not need to handle it.
return context.evalClosure(script, [param], { timeout: 1000, arguments: { copy: true } }).catch(e => {
if (e.message.startsWith('UserScriptError')) {
// User script error, display error location in user script.
const [, row, col, ...messages] = e.message.split(':');
throw new UserScriptError("UserScriptError: " + messages.join(':'), row - 2, col);
} else if (e.message === 'Script execution timed out.') {
// Timeout
throw new TimeoutError(e.message);
} else {
// Other runtime error
throw e;
}
});
}
2 changes: 2 additions & 0 deletions Server/validation/joi.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ const createMonitorBodyValidation = joi.object({
}),
notifications: joi.array().items(joi.object()),
secret: joi.string(),
testScripts: joi.string(),
});

const editMonitorBodyValidation = joi.object({
Expand All @@ -202,6 +203,7 @@ const editMonitorBodyValidation = joi.object({
interval: joi.number(),
notifications: joi.array().items(joi.object()),
secret: joi.string(),
testScripts: joi.string(),
});

const pauseMonitorParamValidation = joi.object({
Expand Down

0 comments on commit a656b4b

Please sign in to comment.