@stuntman/server 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +3 -3
- package/src/api/api.ts +62 -35
- package/src/api/webgui/rules.pug +2 -2
- package/src/api/webgui/style.css +3 -3
- package/src/api/webgui/traffic.pug +1 -0
- package/src/bin/stuntman.ts +2 -2
- package/src/ipUtils.ts +1 -1
- package/src/mock.ts +137 -160
- package/src/ruleExecutor.ts +2 -1
- package/src/rules/index.ts +5 -5
- package/src/storage.ts +2 -2
- package/dist/api/api.d.ts +0 -22
- package/dist/api/api.js +0 -182
- package/dist/api/utils.d.ts +0 -4
- package/dist/api/utils.js +0 -60
- package/dist/api/validators.d.ts +0 -3
- package/dist/api/validators.js +0 -124
- package/dist/api/webgui/rules.pug +0 -147
- package/dist/api/webgui/style.css +0 -28
- package/dist/api/webgui/traffic.pug +0 -37
- package/dist/bin/stuntman.d.ts +0 -2
- package/dist/bin/stuntman.js +0 -7
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/dist/ipUtils.d.ts +0 -17
- package/dist/ipUtils.js +0 -101
- package/dist/mock.d.ts +0 -30
- package/dist/mock.js +0 -327
- package/dist/requestContext.d.ts +0 -9
- package/dist/requestContext.js +0 -18
- package/dist/ruleExecutor.d.ts +0 -22
- package/dist/ruleExecutor.js +0 -187
- package/dist/rules/catchAll.d.ts +0 -2
- package/dist/rules/catchAll.js +0 -15
- package/dist/rules/echo.d.ts +0 -2
- package/dist/rules/echo.js +0 -15
- package/dist/rules/index.d.ts +0 -3
- package/dist/rules/index.js +0 -70
- package/dist/storage.d.ts +0 -4
- package/dist/storage.js +0 -42
package/dist/api/utils.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.liveRuleToRule = exports.escapedSerialize = exports.deserializeRule = void 0;
|
|
7
|
-
const serialize_javascript_1 = __importDefault(require("serialize-javascript"));
|
|
8
|
-
const shared_1 = require("@stuntman/shared");
|
|
9
|
-
const validators_1 = require("./validators");
|
|
10
|
-
// TODO
|
|
11
|
-
const deserializeRule = (serializedRule) => {
|
|
12
|
-
shared_1.logger.debug(serializedRule, 'attempt to deserialize rule');
|
|
13
|
-
(0, validators_1.validateSerializedRuleProperties)(serializedRule);
|
|
14
|
-
const rule = {
|
|
15
|
-
id: serializedRule.id,
|
|
16
|
-
matches: (req) => new Function('____arg0', serializedRule.matches.remoteFn)(req),
|
|
17
|
-
ttlSeconds: serializedRule.ttlSeconds,
|
|
18
|
-
actions: { mockResponse: { status: 200 } },
|
|
19
|
-
...(serializedRule.disableAfterUse !== undefined && { disableAfterUse: serializedRule.disableAfterUse }),
|
|
20
|
-
...(serializedRule.removeAfterUse !== undefined && { removeAfterUse: serializedRule.removeAfterUse }),
|
|
21
|
-
...(serializedRule.labels !== undefined && { labels: serializedRule.labels }),
|
|
22
|
-
...(serializedRule.priority !== undefined && { priority: serializedRule.priority }),
|
|
23
|
-
...(serializedRule.isEnabled !== undefined && { isEnabled: serializedRule.isEnabled }),
|
|
24
|
-
...(serializedRule.storeTraffic !== undefined && { storeTraffic: serializedRule.storeTraffic }),
|
|
25
|
-
};
|
|
26
|
-
// TODO store the original localFn and variables sent from client for web UI editing maybe
|
|
27
|
-
if (serializedRule.actions.mockResponse) {
|
|
28
|
-
rule.actions = {
|
|
29
|
-
mockResponse: 'remoteFn' in serializedRule.actions.mockResponse
|
|
30
|
-
? (req) => new Function('____arg0', serializedRule.actions.mockResponse.remoteFn)(req)
|
|
31
|
-
: serializedRule.actions.mockResponse,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
rule.actions = {
|
|
36
|
-
...(serializedRule.actions.modifyRequest && {
|
|
37
|
-
modifyRequest: ((req) => new Function('____arg0', serializedRule.actions.modifyRequest.remoteFn)(req)),
|
|
38
|
-
}),
|
|
39
|
-
...(serializedRule.actions.modifyResponse && {
|
|
40
|
-
modifyResponse: ((req, res) => new Function('____arg0', '____arg1', serializedRule.actions.modifyResponse.remoteFn)(req, res)),
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
shared_1.logger.debug(rule, 'deserialized rule');
|
|
45
|
-
return rule;
|
|
46
|
-
};
|
|
47
|
-
exports.deserializeRule = deserializeRule;
|
|
48
|
-
const escapedSerialize = (obj) => (0, serialize_javascript_1.default)(obj).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, "\\n'\n+ '");
|
|
49
|
-
exports.escapedSerialize = escapedSerialize;
|
|
50
|
-
const liveRuleToRule = (liveRule) => {
|
|
51
|
-
const ruleClone = { ...liveRule };
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
delete ruleClone.counter;
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
56
|
-
// @ts-ignore
|
|
57
|
-
delete ruleClone.createdTimestamp;
|
|
58
|
-
return ruleClone;
|
|
59
|
-
};
|
|
60
|
-
exports.liveRuleToRule = liveRuleToRule;
|
package/dist/api/validators.d.ts
DELETED
package/dist/api/validators.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateDeserializedRule = exports.validateSerializedRuleProperties = void 0;
|
|
4
|
-
const shared_1 = require("@stuntman/shared");
|
|
5
|
-
const validateSerializedRuleProperties = (rule) => {
|
|
6
|
-
if (!rule) {
|
|
7
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid serialized rule' });
|
|
8
|
-
}
|
|
9
|
-
if (typeof rule.id !== 'string') {
|
|
10
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.id' });
|
|
11
|
-
}
|
|
12
|
-
if (typeof rule.matches !== 'object' || !('remoteFn' in rule.matches) || typeof rule.matches.remoteFn !== 'string') {
|
|
13
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.matches' });
|
|
14
|
-
}
|
|
15
|
-
if (rule.priority && typeof rule.priority !== 'number') {
|
|
16
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.priority' });
|
|
17
|
-
}
|
|
18
|
-
if (typeof rule.actions !== 'object') {
|
|
19
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions - not an object' });
|
|
20
|
-
}
|
|
21
|
-
if (rule.actions.proxyPass !== true &&
|
|
22
|
-
typeof rule.actions.mockResponse !== 'object' &&
|
|
23
|
-
typeof rule.actions.modifyRequest !== 'object' &&
|
|
24
|
-
typeof rule.actions.modifyResponse !== 'object') {
|
|
25
|
-
throw new shared_1.AppError({
|
|
26
|
-
httpCode: shared_1.HttpCode.BAD_REQUEST,
|
|
27
|
-
message: 'invalid rule.actions - missing one of: proxyPass, mockResponse, modifyRequest, modifyResponse',
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
if (typeof rule.actions.mockResponse !== 'undefined') {
|
|
31
|
-
if (typeof rule.actions.mockResponse !== 'object') {
|
|
32
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
|
|
33
|
-
}
|
|
34
|
-
if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
|
|
35
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
|
|
36
|
-
}
|
|
37
|
-
else if ('status' in rule.actions.mockResponse) {
|
|
38
|
-
if (typeof rule.actions.mockResponse.status !== 'number') {
|
|
39
|
-
throw new shared_1.AppError({
|
|
40
|
-
httpCode: shared_1.HttpCode.BAD_REQUEST,
|
|
41
|
-
message: 'invalid rule.actions.mockResponse.status',
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
if (typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
|
|
45
|
-
(!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
|
|
46
|
-
rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))) {
|
|
47
|
-
throw new shared_1.AppError({
|
|
48
|
-
httpCode: shared_1.HttpCode.BAD_REQUEST,
|
|
49
|
-
message: 'invalid rule.actions.mockResponse.rawHeaders',
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
|
|
53
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
|
|
58
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
|
|
59
|
-
}
|
|
60
|
-
if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
|
|
61
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
|
|
62
|
-
}
|
|
63
|
-
if (typeof rule.disableAfterUse !== 'undefined' &&
|
|
64
|
-
typeof rule.disableAfterUse !== 'boolean' &&
|
|
65
|
-
(typeof rule.disableAfterUse !== 'number' || rule.disableAfterUse < 0)) {
|
|
66
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.disableAfterUse' });
|
|
67
|
-
}
|
|
68
|
-
if (typeof rule.removeAfterUse !== 'undefined' &&
|
|
69
|
-
typeof rule.removeAfterUse !== 'boolean' &&
|
|
70
|
-
(typeof rule.removeAfterUse !== 'number' || rule.removeAfterUse < 0)) {
|
|
71
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.removeAfterUse' });
|
|
72
|
-
}
|
|
73
|
-
if (typeof rule.labels !== 'undefined' &&
|
|
74
|
-
(!Array.isArray(rule.labels) || rule.labels.some((label) => typeof label !== 'string'))) {
|
|
75
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.labels' });
|
|
76
|
-
}
|
|
77
|
-
if (rule.actions.mockResponse && rule.actions.modifyResponse) {
|
|
78
|
-
throw new shared_1.AppError({
|
|
79
|
-
httpCode: shared_1.HttpCode.BAD_REQUEST,
|
|
80
|
-
message: 'rule.actions.mockResponse and rule.actions.modifyResponse are mutually exclusive',
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
if (!rule.ttlSeconds || rule.ttlSeconds < shared_1.MIN_RULE_TTL_SECONDS || rule.ttlSeconds > shared_1.MAX_RULE_TTL_SECONDS) {
|
|
84
|
-
throw new shared_1.AppError({
|
|
85
|
-
httpCode: shared_1.HttpCode.BAD_REQUEST,
|
|
86
|
-
message: `rule.ttlSeconds should be within ${shared_1.MIN_RULE_TTL_SECONDS} and ${shared_1.MAX_RULE_TTL_SECONDS}`,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
if (typeof rule.isEnabled !== 'undefined' && typeof rule.isEnabled !== 'boolean') {
|
|
90
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.isEnabled' });
|
|
91
|
-
}
|
|
92
|
-
if (typeof rule.storeTraffic !== 'undefined' && typeof rule.storeTraffic !== 'boolean') {
|
|
93
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.storeTraffic' });
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
exports.validateSerializedRuleProperties = validateSerializedRuleProperties;
|
|
97
|
-
const validateDeserializedRule = (deserializedRule) => {
|
|
98
|
-
// TODO validate other functions ?
|
|
99
|
-
let matchValidationResult;
|
|
100
|
-
try {
|
|
101
|
-
matchValidationResult = deserializedRule.matches({
|
|
102
|
-
id: 'validation',
|
|
103
|
-
method: 'GET',
|
|
104
|
-
rawHeaders: new shared_1.RawHeaders(),
|
|
105
|
-
timestamp: Date.now(),
|
|
106
|
-
url: 'http://dummy.invalid/',
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
shared_1.logger.error({ ruleId: deserializedRule.id }, error);
|
|
111
|
-
throw new shared_1.AppError({
|
|
112
|
-
httpCode: shared_1.HttpCode.UNPROCESSABLE_ENTITY,
|
|
113
|
-
message: 'match function threw an error',
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
if (matchValidationResult !== true &&
|
|
117
|
-
matchValidationResult !== false &&
|
|
118
|
-
matchValidationResult.result !== true &&
|
|
119
|
-
matchValidationResult.result !== false) {
|
|
120
|
-
shared_1.logger.error({ ruleId: deserializedRule.id, matchValidationResult }, 'match function retruned invalid value');
|
|
121
|
-
throw new shared_1.AppError({ httpCode: shared_1.HttpCode.UNPROCESSABLE_ENTITY, message: 'match function returned invalid value' });
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
exports.validateDeserializedRule = validateDeserializedRule;
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
doctype html
|
|
2
|
-
html
|
|
3
|
-
head
|
|
4
|
-
title Stuntman - rule editor
|
|
5
|
-
script(src='https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs/loader.min.js')
|
|
6
|
-
style
|
|
7
|
-
include style.css
|
|
8
|
-
body(style='color: rgb(204, 204, 204); background-color: rgb(50, 50, 50)')
|
|
9
|
-
button#newRule(type='button', onclick='window.newRule()') New rule
|
|
10
|
-
button#saveRule(type='button', onclick='window.saveRule()', disabled) Save rule
|
|
11
|
-
div(style='width: 100%; overflow: hidden')
|
|
12
|
-
div(style='width: 230px; float: left')
|
|
13
|
-
h3 Rules
|
|
14
|
-
ul#ruleKeys.no-bullets
|
|
15
|
-
each ruleId in ruleKeys
|
|
16
|
-
li
|
|
17
|
-
button.rule(
|
|
18
|
-
type='button',
|
|
19
|
-
onclick='window.setRuleModel(this.getAttribute("data-rule-id"))',
|
|
20
|
-
data-rule-id=ruleId
|
|
21
|
-
)= ruleId
|
|
22
|
-
div(style='margin-left: 240px')
|
|
23
|
-
#container(style='height: 400px')
|
|
24
|
-
script.
|
|
25
|
-
const uuidv4 = () => {
|
|
26
|
-
function getRandomSymbol(symbol) {
|
|
27
|
-
var array;
|
|
28
|
-
|
|
29
|
-
if (symbol === 'y') {
|
|
30
|
-
array = ['8', '9', 'a', 'b'];
|
|
31
|
-
return array[Math.floor(Math.random() * array.length)];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
array = new Uint8Array(1);
|
|
35
|
-
window.crypto.getRandomValues(array);
|
|
36
|
-
return (array[0] % 16).toString(16);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, getRandomSymbol);
|
|
40
|
-
};
|
|
41
|
-
require.config({
|
|
42
|
-
paths: {
|
|
43
|
-
vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs',
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
require(['vs/editor/editor.main'], function () {
|
|
47
|
-
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
|
|
48
|
-
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
|
49
|
-
noSemanticValidation: false,
|
|
50
|
-
noSyntaxValidation: false,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// compiler options
|
|
54
|
-
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
|
55
|
-
target: monaco.languages.typescript.ScriptTarget.ES6,
|
|
56
|
-
allowNonTsExtensions: true,
|
|
57
|
-
noLib: true,
|
|
58
|
-
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
|
59
|
-
module: monaco.languages.typescript.ModuleKind.CommonJS,
|
|
60
|
-
noEmit: true,
|
|
61
|
-
checkJs: true,
|
|
62
|
-
allowJs: true,
|
|
63
|
-
isolatedModules: true,
|
|
64
|
-
typeRoots: ['node_modules/@types'],
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
monaco.languages.typescript.typescriptDefaults.addExtraLib(`!{INDEX_DTS}`, 'file:///node_modules/@types/stuntman/index.d.ts');
|
|
68
|
-
const models = {};
|
|
69
|
-
const rules = eval('(!{rules})');
|
|
70
|
-
for (const ruleId of Object.keys(rules)) {
|
|
71
|
-
models[ruleId] = monaco.editor.createModel("import type * as Stuntman from 'stuntman';\n\nvar STUNTMAN_RULE: Stuntman.Rule = " + rules[ruleId] + ';', 'typescript', `file:///${ruleId}.ts`);
|
|
72
|
-
}
|
|
73
|
-
const editor = monaco.editor.create(document.getElementById('container'), {
|
|
74
|
-
theme: 'vs-dark',
|
|
75
|
-
autoIndent: true,
|
|
76
|
-
formatOnPaste: true,
|
|
77
|
-
formatOnType: true,
|
|
78
|
-
automaticLayout: true,
|
|
79
|
-
autoIndent: true,
|
|
80
|
-
tabSize: 2,
|
|
81
|
-
});
|
|
82
|
-
editor.onDidChangeModel((event) => {
|
|
83
|
-
const isInternal = /^file:\/\/\/internal\/.+/.test(event.newModelUrl);
|
|
84
|
-
if (isInternal) {
|
|
85
|
-
document.getElementById('saveRule').setAttribute('disabled', 'true');
|
|
86
|
-
} else {
|
|
87
|
-
document.getElementById('saveRule').removeAttribute('disabled');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
editor.getAction('editor.action.formatDocument').run();
|
|
92
|
-
}, 100);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
window.setRuleModel = (ruleId) => {
|
|
96
|
-
if (history.pushState) {
|
|
97
|
-
const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?ruleId=${encodeURIComponent(ruleId)}`;
|
|
98
|
-
window.history.pushState({ path: newUrl }, '', newUrl);
|
|
99
|
-
}
|
|
100
|
-
editor.setModel(models[ruleId]);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const urlSearchParams = new URLSearchParams(window.location.search);
|
|
104
|
-
if (urlSearchParams.has('ruleId') && urlSearchParams.get('ruleId') in models) {
|
|
105
|
-
editor.setModel(models[urlSearchParams.get('ruleId')]);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
window.saveRule = () => {
|
|
109
|
-
document.getElementById('saveRule').setAttribute('disabled', 'true');
|
|
110
|
-
const modelUri = editor.getModel().uri;
|
|
111
|
-
const result = monaco.languages.typescript.getTypeScriptWorker();
|
|
112
|
-
result.then((worker) => {
|
|
113
|
-
worker(modelUri).then(function (client) {
|
|
114
|
-
client.getEmitOutput(modelUri.toString()).then((output) => {
|
|
115
|
-
const ruleFunctionText = output.outputFiles[0].text.replace(/^export .+$/im, '');
|
|
116
|
-
const newId = new Function(ruleFunctionText + '\n return STUNTMAN_RULE;')().id;
|
|
117
|
-
fetch('/webgui/rules/unsafesave', {
|
|
118
|
-
method: 'POST',
|
|
119
|
-
headers: { 'content-type': 'text/plain' },
|
|
120
|
-
body: ruleFunctionText + '\n return STUNTMAN_RULE;',
|
|
121
|
-
}).then((response) => {
|
|
122
|
-
if (response.ok) {
|
|
123
|
-
const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?ruleId=${encodeURIComponent(newId)}`;
|
|
124
|
-
window.history.pushState({ path: newUrl }, '', newUrl);
|
|
125
|
-
window.location.reload();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
alert('Error when saving rule');
|
|
129
|
-
document.getElementById('saveRule').removeAttribute('disabled');
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
window.newRule = () => {
|
|
137
|
-
const ruleId = uuidv4();
|
|
138
|
-
const emptyRule = `import type * as Stuntman from \'stuntman\';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600, actions: { mockResponse: { status: '200', body: '${ruleId}' }} };`;
|
|
139
|
-
models[ruleId] = monaco.editor.createModel(emptyRule, 'typescript', `file:///${ruleId}.ts`);
|
|
140
|
-
const ruleKeyNode = document.getElementById('ruleKeys').firstChild;
|
|
141
|
-
const ruleKeyNodeClone = ruleKeyNode.cloneNode(true);
|
|
142
|
-
ruleKeyNodeClone.getElementsByTagName('button')[0].setAttribute('data-rule-id', ruleId);
|
|
143
|
-
ruleKeyNodeClone.getElementsByTagName('button')[0].innerText = ruleId;
|
|
144
|
-
document.getElementById('ruleKeys').appendChild(ruleKeyNodeClone);
|
|
145
|
-
window.setRuleModel(ruleId);
|
|
146
|
-
};
|
|
147
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
div#container {
|
|
2
|
-
resize: vertical;
|
|
3
|
-
overflow: auto;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
body {
|
|
7
|
-
font-family: Menlo, Monaco, 'Courier New', monospace;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
button.rule {
|
|
11
|
-
font-family: Menlo, Monaco, 'Courier New', monospace;
|
|
12
|
-
background: none !important;
|
|
13
|
-
border: none;
|
|
14
|
-
padding: 0 !important;
|
|
15
|
-
color: #aaa;
|
|
16
|
-
text-decoration: underline;
|
|
17
|
-
cursor: pointer;
|
|
18
|
-
margin-top: 10px;
|
|
19
|
-
font-size: x-small;
|
|
20
|
-
text-align: left;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
ul.no-bullets {
|
|
24
|
-
list-style-type: none; /* Remove bullets */
|
|
25
|
-
padding: 0; /* Remove padding */
|
|
26
|
-
margin: 0; /* Remove margins */
|
|
27
|
-
text-align: left;
|
|
28
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
doctype html
|
|
2
|
-
html
|
|
3
|
-
head
|
|
4
|
-
title Stuntman - rule editor
|
|
5
|
-
script(src='https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs/loader.min.js')
|
|
6
|
-
style
|
|
7
|
-
include style.css
|
|
8
|
-
body(style='color: rgb(204, 204, 204); background-color: rgb(50, 50, 50)')
|
|
9
|
-
div(style='width: 100%; overflow: hidden')
|
|
10
|
-
div(style='width: 200px; float: left')
|
|
11
|
-
h3 Traffic log
|
|
12
|
-
div(style='margin-left: 220px')
|
|
13
|
-
#container(style='height: 800px')
|
|
14
|
-
script.
|
|
15
|
-
require.config({
|
|
16
|
-
paths: {
|
|
17
|
-
vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.35.0/min/vs',
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
require(['vs/editor/editor.main'], function () {
|
|
21
|
-
const traffic = !{ traffic };
|
|
22
|
-
const model = monaco.editor.createModel(JSON.stringify(traffic, null, 2), 'json');
|
|
23
|
-
const editor = monaco.editor.create(document.getElementById('container'), {
|
|
24
|
-
theme: 'vs-dark',
|
|
25
|
-
autoIndent: true,
|
|
26
|
-
formatOnPaste: true,
|
|
27
|
-
formatOnType: true,
|
|
28
|
-
automaticLayout: true,
|
|
29
|
-
readOnly: true,
|
|
30
|
-
});
|
|
31
|
-
editor.onDidChangeModel((event) => {
|
|
32
|
-
setTimeout(() => {
|
|
33
|
-
editor.getAction('editor.action.formatDocument').run();
|
|
34
|
-
}, 100);
|
|
35
|
-
});
|
|
36
|
-
editor.setModel(model);
|
|
37
|
-
});
|
package/dist/bin/stuntman.d.ts
DELETED
package/dist/bin/stuntman.js
DELETED
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Mock as StuntmanMock } from './mock';
|
package/dist/index.js
DELETED
package/dist/ipUtils.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import dns from 'node:dns';
|
|
3
|
-
export declare class IPUtils {
|
|
4
|
-
protected dnsResolutionCache: import("lru-cache")<string, string>;
|
|
5
|
-
protected mockUuid: string;
|
|
6
|
-
externalDns: dns.Resolver | null;
|
|
7
|
-
constructor(options: {
|
|
8
|
-
mockUuid: string;
|
|
9
|
-
externalDns?: string[];
|
|
10
|
-
});
|
|
11
|
-
isLocalhostIP(ip: string): boolean;
|
|
12
|
-
private resolveIPs;
|
|
13
|
-
resolveIP(hostname: string, options?: {
|
|
14
|
-
useExternalDns?: true;
|
|
15
|
-
}): Promise<string>;
|
|
16
|
-
isIP(hostname: string): boolean;
|
|
17
|
-
}
|
package/dist/ipUtils.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.IPUtils = void 0;
|
|
27
|
-
const os_1 = require("os");
|
|
28
|
-
const node_dns_1 = __importStar(require("node:dns"));
|
|
29
|
-
const storage_1 = require("./storage");
|
|
30
|
-
const shared_1 = require("@stuntman/shared");
|
|
31
|
-
const localhostIPs = ['127.0.0.1'];
|
|
32
|
-
const IP_WITH_OPTIONAL_PORT_REGEX = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(:[0-9]+)?$/i;
|
|
33
|
-
for (const nets of Object.values((0, os_1.networkInterfaces)())) {
|
|
34
|
-
if (!nets) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
for (const net of nets) {
|
|
38
|
-
const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4;
|
|
39
|
-
if (net.family === familyV4Value && !localhostIPs.includes(net.address)) {
|
|
40
|
-
localhostIPs.push(net.address);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
class IPUtils {
|
|
45
|
-
constructor(options) {
|
|
46
|
-
var _a;
|
|
47
|
-
this.externalDns = null;
|
|
48
|
-
this.mockUuid = options.mockUuid;
|
|
49
|
-
if ((_a = options.externalDns) === null || _a === void 0 ? void 0 : _a.length) {
|
|
50
|
-
this.externalDns = new node_dns_1.Resolver();
|
|
51
|
-
this.externalDns.setServers(options.externalDns);
|
|
52
|
-
}
|
|
53
|
-
this.dnsResolutionCache = (0, storage_1.getDnsResolutionCache)(this.mockUuid);
|
|
54
|
-
}
|
|
55
|
-
isLocalhostIP(ip) {
|
|
56
|
-
return localhostIPs.includes(ip);
|
|
57
|
-
}
|
|
58
|
-
async resolveIPs(hostname, options) {
|
|
59
|
-
return new Promise((resolve, reject) => {
|
|
60
|
-
const callback = (error, addresses) => {
|
|
61
|
-
if (error) {
|
|
62
|
-
shared_1.logger.debug({ error, hostname }, 'error resolving hostname');
|
|
63
|
-
reject(error);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (typeof addresses === 'string') {
|
|
67
|
-
shared_1.logger.debug({ ip: [addresses], hostname }, 'resolved hostname');
|
|
68
|
-
resolve([addresses]);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (!addresses || addresses.length === 0) {
|
|
72
|
-
shared_1.logger.debug({ hostname }, 'missing IPs');
|
|
73
|
-
reject(new Error('No addresses found'));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
shared_1.logger.debug({ ip: addresses, hostname }, 'resolved hostname');
|
|
77
|
-
resolve([addresses[0], ...addresses.slice(1)]);
|
|
78
|
-
};
|
|
79
|
-
if (options === null || options === void 0 ? void 0 : options.useExternalDns) {
|
|
80
|
-
if (!this.externalDns) {
|
|
81
|
-
reject(new Error('external dns servers not set'));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
this.externalDns.resolve(hostname, callback);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
node_dns_1.default.lookup(hostname, callback);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
async resolveIP(hostname, options) {
|
|
91
|
-
const cachedIP = this.dnsResolutionCache.get(hostname);
|
|
92
|
-
if (cachedIP) {
|
|
93
|
-
return cachedIP;
|
|
94
|
-
}
|
|
95
|
-
return (await this.resolveIPs(hostname, options))[0];
|
|
96
|
-
}
|
|
97
|
-
isIP(hostname) {
|
|
98
|
-
return IP_WITH_OPTIONAL_PORT_REGEX.test(hostname);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
exports.IPUtils = IPUtils;
|
package/dist/mock.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
import http from 'http';
|
|
4
|
-
import https from 'https';
|
|
5
|
-
import express from 'express';
|
|
6
|
-
import type * as Stuntman from '@stuntman/shared';
|
|
7
|
-
import { IPUtils } from './ipUtils';
|
|
8
|
-
import LRUCache from 'lru-cache';
|
|
9
|
-
import { API } from './api/api';
|
|
10
|
-
export declare class Mock {
|
|
11
|
-
readonly mockUuid: string;
|
|
12
|
-
protected options: Stuntman.ServerConfig;
|
|
13
|
-
protected mockApp: express.Express;
|
|
14
|
-
protected MOCK_DOMAIN_REGEX: RegExp;
|
|
15
|
-
protected URL_PORT_REGEX: RegExp;
|
|
16
|
-
protected server: http.Server | null;
|
|
17
|
-
protected serverHttps: https.Server | null;
|
|
18
|
-
protected trafficStore: LRUCache<string, Stuntman.LogEntry>;
|
|
19
|
-
protected ipUtils: IPUtils | null;
|
|
20
|
-
private _api;
|
|
21
|
-
private requestHandler;
|
|
22
|
-
get apiServer(): API | null;
|
|
23
|
-
get ruleExecutor(): Stuntman.RuleExecutorInterface;
|
|
24
|
-
constructor(options: Stuntman.ServerConfig);
|
|
25
|
-
private proxyRequest;
|
|
26
|
-
start(): void;
|
|
27
|
-
stop(): void;
|
|
28
|
-
protected unproxyRequest(req: express.Request): Stuntman.BaseRequest;
|
|
29
|
-
protected removeProxyPort(req: Stuntman.Request): void;
|
|
30
|
-
}
|