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.
@@ -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
- inMsg.message.data = unmarshal(inMsg.message.data);
159
- }
160
- if (inMsg.message.request === 'assign') {
161
- inMsg.message.job.arguments = unmarshal(inMsg.message.job.arguments);
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
- currPostMessage({ ringSource, value })
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dcp-client",
3
- "version": "4.1.13",
3
+ "version": "4.1.17",
4
4
  "description": "Core libraries for accessing DCP network",
5
5
  "keywords": [
6
6
  "dcp"