dcp-client 4.1.13 → 4.1.17
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/dcp-client.js +2 -1
- package/dist/dcp-client-bundle.js +102 -88
- package/generated/sandbox-definitions.json +1 -1
- package/index.js +3 -3
- package/lib/standaloneWorker.js +11 -1
- package/libexec/evaluator-node.js +27 -22
- package/libexec/sandbox/access-lists.js +3 -2
- package/libexec/sandbox/bootstrap.js +4 -4
- package/libexec/sandbox/bravojs-env.js +1 -1
- package/libexec/sandbox/calculate-capabilities.js +1 -1
- package/libexec/sandbox/event-loop-virtualization.js +140 -123
- package/libexec/sandbox/native-event-loop.js +237 -0
- package/libexec/sandbox/primitive-timers.js +6 -0
- package/libexec/sandbox/sa-ww-simulation.js +6 -23
- package/libexec/sandbox/script-load-wrapper.js +16 -1
- package/libexec/sandbox/wrap-event-listeners.js +38 -0
- package/package.json +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file event-loop-virtualization.js
|
|
3
|
+
*
|
|
4
|
+
* File that creates an event loop using a reactor pattern
|
|
5
|
+
* for handling timers on the event loop. This is made to
|
|
6
|
+
* be similar to the event loops in nodejs and browsers with respect
|
|
7
|
+
* to timers
|
|
8
|
+
*
|
|
9
|
+
* The node and web worker evaluators already have an event loop, along with
|
|
10
|
+
* their own timer functions, so only the v8 evaluator needs to be modified. Before
|
|
11
|
+
* this, it has only the following primitive functions:
|
|
12
|
+
* - ontimer will be invoked by the reactor when there are timers that
|
|
13
|
+
* should need servicing based on the information provided to nextTimer().
|
|
14
|
+
* - nextTimer() sets when the next timer will be fired (in ms)
|
|
15
|
+
*
|
|
16
|
+
* Once this file has run, the following methods will be
|
|
17
|
+
* available on the global object for every evaluator:
|
|
18
|
+
* - setTimeout() execute callback after minimum timeout time in ms
|
|
19
|
+
* - clearTimeout() clear the timeout created by setTimeout
|
|
20
|
+
* - setInterval() recurringly execute callback after minimum timeout time in ms
|
|
21
|
+
* - clearInterval() clear the interval created by setInterval
|
|
22
|
+
* - setImmediate() execute callback after 0ms, immediately when the event loop allows
|
|
23
|
+
* - clearImmediate() clear the Immediate created by setImmediate
|
|
24
|
+
* - queueMicrotask() add a microtask to the microtask queue, bypassing 4ms timeout clamping
|
|
25
|
+
*
|
|
26
|
+
* @author Parker Rowe, parker@kingsds.network
|
|
27
|
+
* Ryan Saweczko, ryansaweczko@kingsds.network
|
|
28
|
+
* @date August 2020
|
|
29
|
+
*
|
|
30
|
+
* @note Unusual function scoping is done to eliminate spurious symbols
|
|
31
|
+
* from being accessible from the global object, to mitigate
|
|
32
|
+
* certain classes of security risks. The global object here is
|
|
33
|
+
* the top of the scope chain (ie global object) for all code run
|
|
34
|
+
* by hosts in this environment.
|
|
35
|
+
*/
|
|
36
|
+
/* globals self, ontimer, nextTimer, evalTimer */
|
|
37
|
+
|
|
38
|
+
self.wrapScriptLoading({ scriptName: 'native-event-loop' }, (ring0PostMessage) => {
|
|
39
|
+
|
|
40
|
+
(function privateScope(ontimer, nextTimer) {
|
|
41
|
+
const timers = [];
|
|
42
|
+
timers.serial = 0; /* If this isn't set, it becomes NaN */
|
|
43
|
+
|
|
44
|
+
function sortTimers() {
|
|
45
|
+
timers.sort(function (a, b) { return a.when - b.when; });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Fire any timers which are ready to run, being careful not to
|
|
49
|
+
* get into a recurring timer death loop without reactor mediation.
|
|
50
|
+
*/
|
|
51
|
+
function fireTimerCallbacks()
|
|
52
|
+
{
|
|
53
|
+
sortTimers();
|
|
54
|
+
let timer;
|
|
55
|
+
while ((timer = timers.shift()))
|
|
56
|
+
{
|
|
57
|
+
let now = Date.now();
|
|
58
|
+
if (timer.when > now)
|
|
59
|
+
{
|
|
60
|
+
timers.push(timer);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
timer.fn.apply(null, timer.args);
|
|
64
|
+
|
|
65
|
+
if (timer.recur)
|
|
66
|
+
{
|
|
67
|
+
timer.when = Date.now() + timer.recur;
|
|
68
|
+
timers.push(timer);
|
|
69
|
+
}
|
|
70
|
+
sortTimers();
|
|
71
|
+
}
|
|
72
|
+
if (timers.length)
|
|
73
|
+
nextTimer(timers[0].when);
|
|
74
|
+
}
|
|
75
|
+
ontimer(fireTimerCallbacks);
|
|
76
|
+
|
|
77
|
+
/** Execute callback after at least timeout ms.
|
|
78
|
+
*
|
|
79
|
+
* @param callback {function} Callback function to fire after a minimum callback time
|
|
80
|
+
* @param timeout {int} integer containing the minimum time to fire callback in ms
|
|
81
|
+
* @param arg array of arguments to be applied to the callback function
|
|
82
|
+
* @returns {object} A value which may be used as the timeoutId parameter of clearTimeout()
|
|
83
|
+
*/
|
|
84
|
+
self.setTimeout = function eventLoop$$Worker$setTimeout(callback, timeout, arg) {
|
|
85
|
+
let timer, args;
|
|
86
|
+
if (typeof callback === 'string') {
|
|
87
|
+
let code = callback;
|
|
88
|
+
callback = function eventLoop$$Worker$setTimeout$wrapper() {
|
|
89
|
+
let indirectEval = eval;
|
|
90
|
+
return indirectEval(code);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// if user supplies arguments, apply them to the callback function
|
|
95
|
+
if (arg) {
|
|
96
|
+
args = Array.prototype.slice.call(arguments); // get a plain array from function arguments
|
|
97
|
+
args = args.slice(2); // slice the first two elements (callback & timeout), leaving an array of user arguments
|
|
98
|
+
let fn = callback;
|
|
99
|
+
callback = () => fn.apply(fn, args); // apply the arguments to the callback function
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
timers.serial = +timers.serial + 1;
|
|
103
|
+
timer = {
|
|
104
|
+
fn: callback,
|
|
105
|
+
when: Date.now() + (+timeout || 0),
|
|
106
|
+
serial: timers.serial,
|
|
107
|
+
valueOf: function () { return this.serial; }
|
|
108
|
+
}
|
|
109
|
+
timers.push(timer);
|
|
110
|
+
if (timer.when <= timers[0].when) {
|
|
111
|
+
sortTimers();
|
|
112
|
+
nextTimer(timers[0].when);
|
|
113
|
+
}
|
|
114
|
+
return timer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Remove a timeout from the list of pending timeouts, regardless of its current
|
|
118
|
+
* status.
|
|
119
|
+
*
|
|
120
|
+
* @param timeoutId {object} The value, returned from setTimeout(), identifying the timer.
|
|
121
|
+
*/
|
|
122
|
+
self.clearTimeout = function eventLoop$$Worker$clearTimeout(timeoutId) {
|
|
123
|
+
if (typeof timeoutId === "object") {
|
|
124
|
+
let i = timers.indexOf(timeoutId);
|
|
125
|
+
if (i != -1) {
|
|
126
|
+
timers.splice(i, 1);
|
|
127
|
+
|
|
128
|
+
/* if there is a timer at the top of the timers list, set that to be the nextTimer to fire
|
|
129
|
+
* otherwise, tell the event loop that there are no more timers to fire, and end the loop accordingly.
|
|
130
|
+
* this fixes a bug where you clear a timeout, but the program still waits that time before ending,
|
|
131
|
+
* despite never calling the callback function
|
|
132
|
+
*
|
|
133
|
+
* for example:
|
|
134
|
+
* const timeout = setTimeout(() => console.log("hi"), 10000);
|
|
135
|
+
* clearTimeout(timeout);
|
|
136
|
+
*
|
|
137
|
+
* used to still wait 10 seconds before closing the program, despite never printing hi to the console
|
|
138
|
+
*/
|
|
139
|
+
if (timers.length) {
|
|
140
|
+
nextTimer(timers[0].when);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
nextTimer(0);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else if (typeof timeoutId === "number") { /* slow path - object has been reinterpreted in terms of valueOf() */
|
|
147
|
+
for (let i = 0; i < timers.length; i++) {
|
|
148
|
+
if (timers[i].serial === timeoutId) {
|
|
149
|
+
timers.splice(i, 1);
|
|
150
|
+
|
|
151
|
+
if (timers.length) {
|
|
152
|
+
nextTimer(timers[0].when);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
nextTimer(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Memoise the original setTimeout for use in interval/immediate
|
|
165
|
+
const innerSetTimeout = setTimeout;
|
|
166
|
+
|
|
167
|
+
/** Execute callback after at least interval ms, regularly, at least interval ms apart.
|
|
168
|
+
*
|
|
169
|
+
* @param callback {function} Callback function to fire after a minimum callback time
|
|
170
|
+
* @param timeout {int} integer containing the minimum time to fire callback in ms
|
|
171
|
+
* @param arg array of arguments to be applied to the callback function
|
|
172
|
+
* @returns {object} A value which may be used as the intervalId paramter of clearInterval()
|
|
173
|
+
*/
|
|
174
|
+
self.setInterval = function eventLoop$$Worker$setInterval(callback, interval, arg) {
|
|
175
|
+
let timer = innerSetTimeout(callback, +interval || 0, arg);
|
|
176
|
+
timer.recur = interval;
|
|
177
|
+
return timer;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Execute callback after 0 ms, immediately when the event loop allows.
|
|
181
|
+
*
|
|
182
|
+
* @param callback {function} Callback function to fire after a minimum callback time
|
|
183
|
+
* @param arg array of arguments to be applied to the callback function
|
|
184
|
+
* @returns {object} A value which may be used as the intervalId paramter of clearImmediate()
|
|
185
|
+
*/
|
|
186
|
+
self.setImmediate = function eventLoop$$Worker$setImmediate(callback, arg) {
|
|
187
|
+
let timer = innerSetTimeout(callback, 0, arg);
|
|
188
|
+
return timer;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Remove an interval timer from the list of pending interval timers, regardless of its current
|
|
192
|
+
* status. (Same as clearTimeout)
|
|
193
|
+
*
|
|
194
|
+
* @param intervalId {object} The value, returned from setInterval(), identifying the timer.
|
|
195
|
+
*/
|
|
196
|
+
self.clearInterval = self.clearTimeout;
|
|
197
|
+
self.clearImmediate = self.clearTimeout;
|
|
198
|
+
|
|
199
|
+
/** queues a microtask to be executed at a safe time prior to control returning to the event loop
|
|
200
|
+
*
|
|
201
|
+
* @param callback {function} Callback function to fire
|
|
202
|
+
*/
|
|
203
|
+
self.queueMicrotask = function eventLoop$$Worker$queueMicrotask(callback) {
|
|
204
|
+
Promise.resolve().then(callback);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function clearAllTimers() {
|
|
208
|
+
timers.length = 0;
|
|
209
|
+
nextTimer(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
addEventListener('message', async (event) => {
|
|
213
|
+
try {
|
|
214
|
+
if (event.request === 'clearTimers') {
|
|
215
|
+
clearAllTimers();
|
|
216
|
+
ring0PostMessage({
|
|
217
|
+
request: 'clearTimersDone',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
ring0PostMessage({
|
|
222
|
+
request: 'error',
|
|
223
|
+
error: {
|
|
224
|
+
name: error.name,
|
|
225
|
+
message: error.message,
|
|
226
|
+
stack: error.stack,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
})(self.ontimer, self.nextTimer);
|
|
232
|
+
|
|
233
|
+
ontimer = nextTimer = evalTimer = undefined;
|
|
234
|
+
delete self.ontimer;
|
|
235
|
+
delete self.nextTimer;
|
|
236
|
+
delete self.evalTimer;
|
|
237
|
+
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file primitive-timers.js
|
|
3
3
|
*
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* DEPRECATED: left for reference if a similar schema is required in the future.
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
*
|
|
4
10
|
* Intermediate file that takes existing implementations of
|
|
5
11
|
* setTimeout, setInterval, clearTimeout, clearInterval from
|
|
6
12
|
* "Browser Evaluator" web worker API, & Node, and dumbs them down into
|
|
@@ -64,26 +64,9 @@ try {
|
|
|
64
64
|
//Will be removing JSON and KVIN in access-lists.js, so need an alias for them
|
|
65
65
|
var serialize = JSON.stringify
|
|
66
66
|
var deserialize = JSON.parse
|
|
67
|
-
var marshal = KVIN.marshal
|
|
68
67
|
var unmarshal = KVIN.unmarshal
|
|
69
68
|
|
|
70
69
|
self.postMessage = function workerControl$$Worker$postMessage (message) {
|
|
71
|
-
/**
|
|
72
|
-
* If our message is either console or complete, we need to serialize the
|
|
73
|
-
* payload/result because they could potentially be a datatype
|
|
74
|
-
* json.stringify cannot handle.
|
|
75
|
-
*/
|
|
76
|
-
if (!message.value)
|
|
77
|
-
{
|
|
78
|
-
message.value = serialize(marshal(message.value))
|
|
79
|
-
}
|
|
80
|
-
if (message.value.request === "console"){
|
|
81
|
-
//Because JSON.stringify(a) !== JSON.stringify(JSON.stringify(a)), we need to do this
|
|
82
|
-
message.value.payload.message = serialize(marshal(message.value.payload.message))
|
|
83
|
-
} else if (message.value.request === "complete"){
|
|
84
|
-
message.value.result = marshal(message.value.result);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
70
|
send({type: 'workerMessage', message });
|
|
88
71
|
}
|
|
89
72
|
|
|
@@ -154,12 +137,12 @@ try {
|
|
|
154
137
|
outMsg = { type: 'nop', success: true }
|
|
155
138
|
break
|
|
156
139
|
case 'workerMessage':
|
|
157
|
-
if (inMsg.message.request === 'main') {
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
if (inMsg.message.request === 'assign') {
|
|
161
|
-
|
|
162
|
-
}
|
|
140
|
+
// if (inMsg.message.request === 'main') {
|
|
141
|
+
// inMsg.message.data = unmarshal(inMsg.message.data);
|
|
142
|
+
// }
|
|
143
|
+
// if (inMsg.message.request === 'assign') {
|
|
144
|
+
// inMsg.message.job.arguments = unmarshal(inMsg.message.job.arguments);
|
|
145
|
+
// }
|
|
163
146
|
emitEvent('message', {data: inMsg.message})
|
|
164
147
|
outMsg.success = true
|
|
165
148
|
break
|
|
@@ -18,11 +18,26 @@
|
|
|
18
18
|
*/
|
|
19
19
|
let currentRing = -1;
|
|
20
20
|
const currPostMessage = self.postMessage;
|
|
21
|
+
const marshal = KVIN.marshal
|
|
22
|
+
const serialize = JSON.stringify
|
|
21
23
|
|
|
22
24
|
function wrapPostMessage() {
|
|
23
25
|
const ringSource = ++currentRing;
|
|
24
26
|
self.postMessage = function (value) {
|
|
25
|
-
|
|
27
|
+
// Objects may not be transferable objects (https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects),
|
|
28
|
+
// and can remain non-transferable even after kvin.marshal, and it is very hard to detect such objects. One such object
|
|
29
|
+
// is the `arguments` object of any function. In such a case, we need to serialize the message on top of
|
|
30
|
+
const updatedMsg = marshal({ ringSource, value })
|
|
31
|
+
try {
|
|
32
|
+
currPostMessage(updatedMsg);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
const serializedMessage = {
|
|
36
|
+
message: serialize(updatedMsg),
|
|
37
|
+
serialized: true,
|
|
38
|
+
};
|
|
39
|
+
currPostMessage(serializedMessage);
|
|
40
|
+
}
|
|
26
41
|
}
|
|
27
42
|
}
|
|
28
43
|
//Initialize postMessage to ring 0
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** @file wrap-event-listeners.js
|
|
2
|
+
* A wrapper to protect the global event listener
|
|
3
|
+
*
|
|
4
|
+
* The purpose of this is to wrap addEventListener to ensure all messages are
|
|
5
|
+
* deserialized when they come from the supervisor/sandboxHandle before being processed and the
|
|
6
|
+
* command executed (ie all onmessage commands are deserialized).
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
* @author Ryan Saweczko, ryansaweczko@kingsds.network
|
|
10
|
+
* @date Oct 2021
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
self.wrapScriptLoading({ scriptName: 'event-loop-virtualization' }, () => {
|
|
14
|
+
|
|
15
|
+
// Will be removing KVIN in access-lists.js, so need an alias for them
|
|
16
|
+
var unmarshal = KVIN.unmarshal
|
|
17
|
+
|
|
18
|
+
const globalAddEventListener = self.addEventListener;
|
|
19
|
+
self.addEventListener = function workerControl$$Worker$addEventListener (type, listener) {
|
|
20
|
+
if (type === 'message')
|
|
21
|
+
{
|
|
22
|
+
const wrappedListener = (args) => {
|
|
23
|
+
if (args.data && args.data._serializeVerId)
|
|
24
|
+
{
|
|
25
|
+
args = unmarshal(args.data)
|
|
26
|
+
}
|
|
27
|
+
listener(args);
|
|
28
|
+
}
|
|
29
|
+
globalAddEventListener(type, wrappedListener);
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
{
|
|
33
|
+
globalAddEventListener(type, listener);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
});
|