@api3/commons 0.1.0 → 0.2.0
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 +14 -5
- package/dist/eslint/jest.d.ts +1 -0
- package/dist/eslint/jest.js +1 -0
- package/dist/eslint/jest.js.map +1 -1
- package/dist/eslint/react.d.ts +0 -1
- package/dist/eslint/react.js +0 -1
- package/dist/eslint/react.js.map +1 -1
- package/dist/eslint/universal.d.ts +1 -5
- package/dist/eslint/universal.js +2 -19
- package/dist/eslint/universal.js.map +1 -1
- package/dist/processing/index.d.ts +4 -0
- package/dist/processing/index.d.ts.map +1 -0
- package/dist/processing/index.js +20 -0
- package/dist/processing/index.js.map +1 -0
- package/dist/processing/processing.d.ts +39 -0
- package/dist/processing/processing.d.ts.map +1 -0
- package/dist/processing/processing.js +122 -0
- package/dist/processing/processing.js.map +1 -0
- package/dist/processing/schema.d.ts +4 -0
- package/dist/processing/schema.d.ts.map +1 -0
- package/dist/processing/schema.js +6 -0
- package/dist/processing/schema.js.map +1 -0
- package/dist/processing/unsafe-evaluate.d.ts +50 -0
- package/dist/processing/unsafe-evaluate.d.ts.map +1 -0
- package/dist/processing/unsafe-evaluate.js +178 -0
- package/dist/processing/unsafe-evaluate.js.map +1 -0
- package/dist/processing/vm-timers.d.ts +21 -0
- package/dist/processing/vm-timers.d.ts.map +1 -0
- package/dist/processing/vm-timers.js +54 -0
- package/dist/processing/vm-timers.js.map +1 -0
- package/package.json +6 -4
- package/src/eslint/README.md +0 -5
- package/src/eslint/jest.js +1 -0
- package/src/eslint/react.js +0 -1
- package/src/eslint/universal.js +2 -20
- package/src/processing/README.md +49 -0
- package/src/processing/index.ts +3 -0
- package/src/processing/processing.test.ts +272 -0
- package/src/processing/processing.ts +160 -0
- package/src/processing/schema.ts +5 -0
- package/src/processing/unsafe-evaluate.test.ts +103 -0
- package/src/processing/unsafe-evaluate.ts +178 -0
- package/src/processing/vm-timers.ts +58 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -1
- package/src/index.ts +0 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { type Endpoint, RESERVED_PARAMETERS } from '@api3/ois';
|
|
2
|
+
import { type GoAsyncOptions, go } from '@api3/promise-utils';
|
|
3
|
+
|
|
4
|
+
import { type ApiCallParameters, apiCallParametersSchema } from './schema';
|
|
5
|
+
import { unsafeEvaluate, unsafeEvaluateAsync } from './unsafe-evaluate';
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_PROCESSING_TIMEOUT_MS = 10_000;
|
|
8
|
+
|
|
9
|
+
const reservedParameters = RESERVED_PARAMETERS as string[]; // To avoid strict TS checks.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Removes reserved parameters from the parameters object.
|
|
13
|
+
* @param parameters The API call parameters from which reserved parameters will be removed.
|
|
14
|
+
* @returns The parameters object without reserved parameters.
|
|
15
|
+
*/
|
|
16
|
+
export const removeReservedParameters = (parameters: ApiCallParameters): ApiCallParameters => {
|
|
17
|
+
const result: ApiCallParameters = {};
|
|
18
|
+
|
|
19
|
+
for (const key in parameters) {
|
|
20
|
+
if (!reservedParameters.includes(key)) {
|
|
21
|
+
result[key] = parameters[key];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Re-inserts reserved parameters from the initial parameters object into the modified parameters object.
|
|
30
|
+
* @param initialParameters The initial API call parameters that might contain reserved parameters.
|
|
31
|
+
* @param modifiedParameters The modified API call parameters to which reserved parameters will be added.
|
|
32
|
+
* @returns The modified parameters object with re-inserted reserved parameters.
|
|
33
|
+
*/
|
|
34
|
+
export const addReservedParameters = (
|
|
35
|
+
initialParameters: ApiCallParameters,
|
|
36
|
+
modifiedParameters: ApiCallParameters
|
|
37
|
+
): ApiCallParameters => {
|
|
38
|
+
for (const key in initialParameters) {
|
|
39
|
+
if (reservedParameters.includes(key)) {
|
|
40
|
+
modifiedParameters[key] = initialParameters[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return modifiedParameters;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Pre-processes API call parameters based on the provided endpoint's processing specifications.
|
|
49
|
+
*
|
|
50
|
+
* @param endpoint The endpoint containing processing specifications.
|
|
51
|
+
* @param apiCallParameters The parameters to be pre-processed.
|
|
52
|
+
* @param processingOptions Options to control the async processing behavior like retries and timeouts.
|
|
53
|
+
*
|
|
54
|
+
* @returns A promise that resolves to the pre-processed parameters.
|
|
55
|
+
*/
|
|
56
|
+
export const preProcessApiCallParameters = async (
|
|
57
|
+
endpoint: Endpoint,
|
|
58
|
+
apiCallParameters: ApiCallParameters,
|
|
59
|
+
processingOptions: GoAsyncOptions = { retries: 0, totalTimeoutMs: DEFAULT_PROCESSING_TIMEOUT_MS }
|
|
60
|
+
): Promise<ApiCallParameters> => {
|
|
61
|
+
const { preProcessingSpecifications } = endpoint;
|
|
62
|
+
if (!preProcessingSpecifications || preProcessingSpecifications.length === 0) {
|
|
63
|
+
return apiCallParameters;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// We only wrap the code through "go" utils because of the timeout and retry logic.
|
|
67
|
+
const goProcessedParameters = await go(async () => {
|
|
68
|
+
let currentValue: unknown = removeReservedParameters(apiCallParameters);
|
|
69
|
+
|
|
70
|
+
for (const processing of preProcessingSpecifications) {
|
|
71
|
+
// Provide endpoint parameters without reserved parameters immutably between steps. Recompute them for each
|
|
72
|
+
// snippet independently because processing snippets can modify the parameters.
|
|
73
|
+
const endpointParameters = removeReservedParameters(apiCallParameters);
|
|
74
|
+
|
|
75
|
+
switch (processing.environment) {
|
|
76
|
+
case 'Node': {
|
|
77
|
+
currentValue = await unsafeEvaluate(
|
|
78
|
+
processing.value,
|
|
79
|
+
{ input: currentValue, endpointParameters },
|
|
80
|
+
processing.timeoutMs
|
|
81
|
+
);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'Node async': {
|
|
85
|
+
currentValue = await unsafeEvaluateAsync(
|
|
86
|
+
processing.value,
|
|
87
|
+
{ input: currentValue, endpointParameters },
|
|
88
|
+
processing.timeoutMs
|
|
89
|
+
);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return currentValue;
|
|
96
|
+
}, processingOptions);
|
|
97
|
+
if (!goProcessedParameters.success) throw goProcessedParameters.error;
|
|
98
|
+
|
|
99
|
+
// Let this throw if the processed parameters are invalid.
|
|
100
|
+
const parsedParameters = apiCallParametersSchema.parse(goProcessedParameters.data);
|
|
101
|
+
|
|
102
|
+
// Having removed reserved parameters for pre-processing, we need to re-insert them for the API call.
|
|
103
|
+
return addReservedParameters(apiCallParameters, parsedParameters);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Post-processes the API call response based on the provided endpoint's processing specifications.
|
|
108
|
+
*
|
|
109
|
+
* @param apiCallResponse The raw response obtained from the API call.
|
|
110
|
+
* @param endpoint The endpoint containing processing specifications.
|
|
111
|
+
* @param apiCallParameters The parameters used in the API call.
|
|
112
|
+
* @param processingOptions Options to control the async processing behavior like retries and timeouts.
|
|
113
|
+
*
|
|
114
|
+
* @returns A promise that resolves to the post-processed API call response.
|
|
115
|
+
*/
|
|
116
|
+
export const postProcessApiCallResponse = async (
|
|
117
|
+
apiCallResponse: unknown,
|
|
118
|
+
endpoint: Endpoint,
|
|
119
|
+
apiCallParameters: ApiCallParameters,
|
|
120
|
+
processingOptions: GoAsyncOptions = { retries: 0, totalTimeoutMs: DEFAULT_PROCESSING_TIMEOUT_MS }
|
|
121
|
+
) => {
|
|
122
|
+
const { postProcessingSpecifications } = endpoint;
|
|
123
|
+
if (!postProcessingSpecifications || postProcessingSpecifications?.length === 0) {
|
|
124
|
+
return apiCallResponse;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// We only wrap the code through "go" utils because of the timeout and retry logic.
|
|
128
|
+
const goResult = await go(async () => {
|
|
129
|
+
let currentValue: unknown = apiCallResponse;
|
|
130
|
+
|
|
131
|
+
for (const processing of postProcessingSpecifications) {
|
|
132
|
+
// Provide endpoint parameters without reserved parameters immutably between steps. Recompute them for each
|
|
133
|
+
// snippet independently because processing snippets can modify the parameters.
|
|
134
|
+
const endpointParameters = removeReservedParameters(apiCallParameters);
|
|
135
|
+
switch (processing.environment) {
|
|
136
|
+
case 'Node': {
|
|
137
|
+
currentValue = await unsafeEvaluate(
|
|
138
|
+
processing.value,
|
|
139
|
+
{ input: currentValue, endpointParameters },
|
|
140
|
+
processing.timeoutMs
|
|
141
|
+
);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'Node async': {
|
|
145
|
+
currentValue = await unsafeEvaluateAsync(
|
|
146
|
+
processing.value,
|
|
147
|
+
{ input: currentValue, endpointParameters },
|
|
148
|
+
processing.timeoutMs
|
|
149
|
+
);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return currentValue;
|
|
156
|
+
}, processingOptions);
|
|
157
|
+
if (!goResult.success) throw goResult.error;
|
|
158
|
+
|
|
159
|
+
return goResult.data;
|
|
160
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* eslint-disable jest/prefer-strict-equal */ // Because the errors are thrown from the "vm" module (different context), they are not strictly equal.
|
|
2
|
+
import { unsafeEvaluate, unsafeEvaluateAsync } from './unsafe-evaluate';
|
|
3
|
+
|
|
4
|
+
describe('unsafe evaluate - sync', () => {
|
|
5
|
+
it('executes harmless code', () => {
|
|
6
|
+
const result = unsafeEvaluate("const output = {...input, c: 'some-value'}", { input: { a: true, b: 123 } }, 5000);
|
|
7
|
+
|
|
8
|
+
expect(result).toEqual({ a: true, b: 123, c: 'some-value' });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('throws on exception', () => {
|
|
12
|
+
expect(() => unsafeEvaluate("throw new Error('unexpected')", {}, 5000)).toThrow('unexpected');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('unsafe evaluate - async', () => {
|
|
17
|
+
it('executes harmless code', async () => {
|
|
18
|
+
const result = unsafeEvaluateAsync(
|
|
19
|
+
"const output = {...input, c: 'some-value'}; resolve(output);",
|
|
20
|
+
{ input: { a: true, b: 123 } },
|
|
21
|
+
5000
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
await expect(result).resolves.toEqual({ a: true, b: 123, c: 'some-value' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('can use setTimeout and setInterval', async () => {
|
|
28
|
+
const result = unsafeEvaluateAsync(
|
|
29
|
+
`
|
|
30
|
+
const fn = async () => {
|
|
31
|
+
const output = input;
|
|
32
|
+
output.push('start')
|
|
33
|
+
|
|
34
|
+
const tickMs = 35
|
|
35
|
+
const bufferMs = 25
|
|
36
|
+
setInterval(() => output.push('ping interval'), tickMs)
|
|
37
|
+
await new Promise((res) => setTimeout(res, tickMs * 4 + bufferMs));
|
|
38
|
+
|
|
39
|
+
output.push('end')
|
|
40
|
+
resolve(output);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
fn()
|
|
44
|
+
`,
|
|
45
|
+
{ input: [] },
|
|
46
|
+
200
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
await expect(result).resolves.toEqual([
|
|
50
|
+
'start',
|
|
51
|
+
'ping interval',
|
|
52
|
+
'ping interval',
|
|
53
|
+
'ping interval',
|
|
54
|
+
'ping interval',
|
|
55
|
+
'end',
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('applies timeout when using setTimeout', async () => {
|
|
60
|
+
await expect(async () =>
|
|
61
|
+
unsafeEvaluateAsync(
|
|
62
|
+
`
|
|
63
|
+
const fn = () => {
|
|
64
|
+
setTimeout(() => console.log('ping timeout'), 100)
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
fn()
|
|
68
|
+
`,
|
|
69
|
+
{},
|
|
70
|
+
50
|
|
71
|
+
)
|
|
72
|
+
).rejects.toEqual(new Error('Timeout exceeded'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('applies timeout when using setInterval', async () => {
|
|
76
|
+
await expect(async () =>
|
|
77
|
+
unsafeEvaluateAsync(
|
|
78
|
+
`
|
|
79
|
+
const fn = () => {
|
|
80
|
+
const someFn = () => {}
|
|
81
|
+
setInterval(someFn, 10)
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
fn()
|
|
85
|
+
`,
|
|
86
|
+
{},
|
|
87
|
+
50
|
|
88
|
+
)
|
|
89
|
+
).rejects.toEqual(new Error('Timeout exceeded'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('processing can call reject', async () => {
|
|
93
|
+
await expect(async () =>
|
|
94
|
+
unsafeEvaluateAsync(`reject(new Error('Rejected by processing snippet.'))`, {}, 50)
|
|
95
|
+
).rejects.toEqual(new Error('Rejected by processing snippet.'));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('throws on exception', async () => {
|
|
99
|
+
await expect(async () => unsafeEvaluateAsync("throw new Error('unexpected')", {}, 5000)).rejects.toEqual(
|
|
100
|
+
new Error('unexpected')
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import async_hooks from 'node:async_hooks';
|
|
4
|
+
import buffer from 'node:buffer';
|
|
5
|
+
import child_process from 'node:child_process';
|
|
6
|
+
import cluster from 'node:cluster';
|
|
7
|
+
import console from 'node:console';
|
|
8
|
+
import constants from 'node:constants';
|
|
9
|
+
import crypto from 'node:crypto';
|
|
10
|
+
import dgram from 'node:dgram';
|
|
11
|
+
import dns from 'node:dns';
|
|
12
|
+
import events from 'node:events';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import http from 'node:http';
|
|
15
|
+
import http2 from 'node:http2';
|
|
16
|
+
import https from 'node:https';
|
|
17
|
+
import inspector from 'node:inspector';
|
|
18
|
+
import module from 'node:module';
|
|
19
|
+
import net from 'node:net';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import perf_hooks from 'node:perf_hooks';
|
|
23
|
+
import process from 'node:process';
|
|
24
|
+
import readline from 'node:readline';
|
|
25
|
+
import repl from 'node:repl';
|
|
26
|
+
import stream from 'node:stream';
|
|
27
|
+
import string_decoder from 'node:string_decoder';
|
|
28
|
+
import timers from 'node:timers';
|
|
29
|
+
import tls from 'node:tls';
|
|
30
|
+
import trace_events from 'node:trace_events';
|
|
31
|
+
import tty from 'node:tty';
|
|
32
|
+
import url from 'node:url';
|
|
33
|
+
import util from 'node:util';
|
|
34
|
+
import v8 from 'node:v8';
|
|
35
|
+
import vm from 'node:vm';
|
|
36
|
+
import worker_threads from 'node:worker_threads';
|
|
37
|
+
import zlib from 'node:zlib';
|
|
38
|
+
|
|
39
|
+
import { createTimers } from './vm-timers';
|
|
40
|
+
|
|
41
|
+
const builtInNodeModules = {
|
|
42
|
+
assert,
|
|
43
|
+
async_hooks,
|
|
44
|
+
buffer,
|
|
45
|
+
child_process,
|
|
46
|
+
cluster,
|
|
47
|
+
console,
|
|
48
|
+
constants,
|
|
49
|
+
crypto,
|
|
50
|
+
dgram,
|
|
51
|
+
dns,
|
|
52
|
+
events,
|
|
53
|
+
fs,
|
|
54
|
+
http,
|
|
55
|
+
http2,
|
|
56
|
+
https,
|
|
57
|
+
inspector,
|
|
58
|
+
module,
|
|
59
|
+
net,
|
|
60
|
+
os,
|
|
61
|
+
path,
|
|
62
|
+
perf_hooks,
|
|
63
|
+
process,
|
|
64
|
+
readline,
|
|
65
|
+
repl,
|
|
66
|
+
stream,
|
|
67
|
+
string_decoder,
|
|
68
|
+
timers,
|
|
69
|
+
tls,
|
|
70
|
+
trace_events,
|
|
71
|
+
tty,
|
|
72
|
+
url,
|
|
73
|
+
util,
|
|
74
|
+
v8,
|
|
75
|
+
vm,
|
|
76
|
+
worker_threads,
|
|
77
|
+
zlib,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Evaluates the provided code in a new VM context with the specified global variables.
|
|
82
|
+
*
|
|
83
|
+
* **Security Warning:** This function executes the provided code and can have unintended side effects or
|
|
84
|
+
* vulnerabilities if used with untrusted or malicious input. It's imperative to use this function only with code you
|
|
85
|
+
* trust completely. Avoid using this function with user-generated code or third-party code that hasn't been thoroughly
|
|
86
|
+
* reviewed.
|
|
87
|
+
*
|
|
88
|
+
* @param code The JavaScript code to evaluate.
|
|
89
|
+
* @param globalVariables A key-value pair of variables to be made available in the context of the executed code.
|
|
90
|
+
* @param timeout Duration in milliseconds to wait before terminating the execution.
|
|
91
|
+
*
|
|
92
|
+
* @returns The result of the evaluated code.
|
|
93
|
+
*
|
|
94
|
+
* @throws Throws an error if the execution exceeds the provided timeout or if there's a problem with the code.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
*
|
|
98
|
+
*const result = unsafeEvaluate('const output = input + 1;', { input: 1 }, 1000);
|
|
99
|
+
*console.log(result); // Outputs: 2
|
|
100
|
+
*/
|
|
101
|
+
export const unsafeEvaluate = (code: string, globalVariables: Record<string, unknown>, timeout: number) => {
|
|
102
|
+
const vmContext = {
|
|
103
|
+
...globalVariables,
|
|
104
|
+
...builtInNodeModules,
|
|
105
|
+
deferredOutput: undefined as unknown,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
vm.runInNewContext(`${code}; deferredOutput = output;`, vmContext, {
|
|
109
|
+
displayErrors: true,
|
|
110
|
+
timeout,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return vmContext.deferredOutput;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Asynchronously evaluates the provided code in a new VM context with the specified global variables.
|
|
118
|
+
*
|
|
119
|
+
* **Security Warning:** This function executes the provided code and can have unintended side effects or
|
|
120
|
+
* vulnerabilities if used with untrusted or malicious input. It's imperative to use this function only with code you
|
|
121
|
+
* trust completely. Avoid using this function with user-generated code or third-party code that hasn't been thoroughly
|
|
122
|
+
* reviewed.
|
|
123
|
+
*
|
|
124
|
+
* @param code The JavaScript code to evaluate. The code should call the `resolve` method to return the result of the
|
|
125
|
+
* evaluation. You may use async/await syntax in the code.
|
|
126
|
+
* @param globalVariables A key-value pair of variables to be made available in the context of the executed code.
|
|
127
|
+
* @param timeout Duration in milliseconds to wait before terminating the execution.
|
|
128
|
+
*
|
|
129
|
+
* @returns The result of the evaluated code wrapped in a Promise.
|
|
130
|
+
*
|
|
131
|
+
* @throws Throws an error if the execution exceeds the provided timeout or if there's a problem with the code.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
*
|
|
135
|
+
*const result = await unsafeEvaluateAsync(
|
|
136
|
+
* "const output = {...input, c: 'some-value'}; resolve(output);",
|
|
137
|
+
* { input: { a: true, b: 123 } },
|
|
138
|
+
* 5000
|
|
139
|
+
*);
|
|
140
|
+
*console.log(result); // Outputs: { a: true, b: 123, c: 'some-value' }
|
|
141
|
+
*/
|
|
142
|
+
export const unsafeEvaluateAsync = async (code: string, globalVariables: Record<string, unknown>, timeout: number) => {
|
|
143
|
+
let vmReject: (reason: unknown) => void;
|
|
144
|
+
|
|
145
|
+
// Make sure the timeout is applied. When the processing snippet uses setTimeout or setInterval, the timeout option
|
|
146
|
+
// from VM is broken. See: https://github.com/nodejs/node/issues/3020.
|
|
147
|
+
//
|
|
148
|
+
// We need to manually clear all timers and reject the processing manually.
|
|
149
|
+
const timeoutTimer = setTimeout(() => {
|
|
150
|
+
vmReject(new Error('Timeout exceeded'));
|
|
151
|
+
}, timeout);
|
|
152
|
+
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const timers = createTimers();
|
|
155
|
+
const vmResolve = (value: unknown) => {
|
|
156
|
+
timers.clearAll();
|
|
157
|
+
clearTimeout(timeoutTimer);
|
|
158
|
+
resolve(value);
|
|
159
|
+
};
|
|
160
|
+
vmReject = (reason: unknown) => {
|
|
161
|
+
timers.clearAll();
|
|
162
|
+
clearTimeout(timeoutTimer);
|
|
163
|
+
reject(reason);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const vmContext = {
|
|
167
|
+
...globalVariables,
|
|
168
|
+
...builtInNodeModules,
|
|
169
|
+
resolve: vmResolve,
|
|
170
|
+
reject: vmReject,
|
|
171
|
+
setTimeout: timers.customSetTimeout,
|
|
172
|
+
setInterval: timers.customSetInterval,
|
|
173
|
+
clearTimeout: timers.customClearTimeout,
|
|
174
|
+
clearInterval: timers.customClearInterval,
|
|
175
|
+
};
|
|
176
|
+
vm.runInNewContext(code, vmContext, { displayErrors: true, timeout });
|
|
177
|
+
});
|
|
178
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timers (setTimeout, setInterval) do not work in Node.js vm, see: https://github.com/nodejs/help/issues/1875
|
|
3
|
+
*
|
|
4
|
+
* The API is wrapped in a "create" function so that every processing snippet keeps track of its timers and properly
|
|
5
|
+
* cleans them up after use.
|
|
6
|
+
*/
|
|
7
|
+
export const createTimers = () => {
|
|
8
|
+
let timeouts: NodeJS.Timeout[] = [];
|
|
9
|
+
|
|
10
|
+
const customSetTimeout = (fn: () => void, ms: number) => {
|
|
11
|
+
timeouts.push(setTimeout(fn, ms));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const customClearTimeout = (id: NodeJS.Timeout) => {
|
|
15
|
+
timeouts = timeouts.filter((timeoutId) => timeoutId !== id);
|
|
16
|
+
clearTimeout(id);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const clearAllTimeouts = () => {
|
|
20
|
+
for (const element of timeouts) {
|
|
21
|
+
clearTimeout(element);
|
|
22
|
+
}
|
|
23
|
+
timeouts = [];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let intervals: NodeJS.Timeout[] = [];
|
|
27
|
+
|
|
28
|
+
const customSetInterval = (fn: () => void, ms: number) => {
|
|
29
|
+
intervals.push(setInterval(fn, ms));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const customClearInterval = (id: NodeJS.Timeout) => {
|
|
33
|
+
intervals = intervals.filter((intervalId) => intervalId !== id);
|
|
34
|
+
clearInterval(id);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const clearAllIntervals = () => {
|
|
38
|
+
for (const element of intervals) {
|
|
39
|
+
clearInterval(element);
|
|
40
|
+
}
|
|
41
|
+
intervals = [];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const clearAll = () => {
|
|
45
|
+
clearAllTimeouts();
|
|
46
|
+
clearAllIntervals();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
customSetTimeout,
|
|
51
|
+
customClearTimeout,
|
|
52
|
+
clearAllTimeouts,
|
|
53
|
+
customSetInterval,
|
|
54
|
+
customClearInterval,
|
|
55
|
+
clearAllIntervals,
|
|
56
|
+
clearAll,
|
|
57
|
+
};
|
|
58
|
+
};
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
DELETED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,sCAAsC;AACtC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC"}
|
package/src/index.ts
DELETED