dcp-client 4.3.6 → 4.3.8

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.
@@ -1 +1 @@
1
- {"browser":["deny-node","kvin/kvin.js","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"node":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"native":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap"],"nodeTesting":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap","testing.js"],"testing":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","calculate-capabilities","bootstrap","testing.js"]}
1
+ {"browser":["deny-node","kvin/kvin.js","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","worktimes","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","pyodide-core","calculate-capabilities","bootstrap"],"node":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","worktimes","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","pyodide-core","calculate-capabilities","bootstrap"],"native":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","worktimes","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","pyodide-core","calculate-capabilities","bootstrap"],"nodeTesting":["kvin/kvin.js","sa-ww-simulation","script-load-wrapper","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","worktimes","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","pyodide-core","calculate-capabilities","bootstrap","testing.js"],"testing":["deny-node","kvin/kvin.js","sa-ww-simulation","script-load-wrapper","native-event-loop","wrap-event-listeners","timer-classes","event-loop-virtualization","unique-timing","worktimes","access-lists","bravojs-init","bravojs/bravo.js","bravojs-env","pyodide-core","calculate-capabilities","bootstrap","testing.js"]}
package/index.js CHANGED
@@ -373,11 +373,17 @@ function magicView(node, seen)
373
373
  *
374
374
  * Returns false is the file simply does not exist.
375
375
  *
376
+ * Setting `DCP_CLIENT_ALLOW_INSECURE_CONFIGURATION` to a non-empty value disables
377
+ * the security check.
378
+ *
376
379
  * @param {string} fullPath the full path to the file to check
377
380
  * @param {object} statBuf [optional] existing stat buf for the file
378
381
  */
379
382
  function checkConfigFileSafePerms(fullPath, statBuf)
380
383
  {
384
+ if (process.env.DCP_CLIENT_ALLOW_INSECURE_CONFIGURATION)
385
+ return true;
386
+
381
387
  const fun = checkConfigFileSafePerms;
382
388
 
383
389
  if (!fs.existsSync(fullPath))
@@ -12,35 +12,17 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
12
12
  const ring1PostMessage = self.postMessage;
13
13
  const global = typeof globalThis === 'undefined' ? self : globalThis;
14
14
 
15
- // aggregated from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#Reflection
16
15
  const allowList = new Set([
17
- '__proto__',
18
-
19
- // properties guarnteed by polyfills
20
- 'globalThis',
21
- 'location',
22
- 'performance',
23
- 'importScripts',
24
- 'WorkerGlobalScope',
25
- 'btoa',
26
- 'atob',
27
- 'Blob',
28
-
29
- // General allowed symbols
30
- 'addEventListener',
31
- 'applyAccesslist',
16
+ // global objects, aggregated from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#Reflection
17
+ 'AggregateError',
32
18
  'Array',
33
19
  'ArrayBuffer',
34
- 'AsyncFunction',
35
20
  'Atomics',
36
21
  'BigInt',
37
22
  'BigInt64Array',
38
23
  'BigUint64Array',
39
24
  'Boolean',
40
- 'bravojs',
41
- 'clearInterval',
42
- 'clearTimeout',
43
- 'console',
25
+
44
26
  'constructor',
45
27
  'DataView',
46
28
  'Date',
@@ -52,12 +34,10 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
52
34
  'escape',
53
35
  'eval',
54
36
  'EvalError',
55
- 'File',
56
- 'FileReader',
57
37
  'Float32Array',
58
38
  'Float64Array',
59
39
  'Function',
60
- 'Headers',
40
+ 'globalThis',
61
41
  'Infinity',
62
42
  'Int16Array',
63
43
  'Int32Array',
@@ -69,7 +49,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
69
49
  'Math',
70
50
  'module',
71
51
  'NaN',
72
- 'navigator',
73
52
  'null',
74
53
  'Number',
75
54
  'Object',
@@ -82,21 +61,14 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
82
61
  'Promise',
83
62
  'propertyIsEnumerable',
84
63
  'Proxy',
85
- 'pt0',
86
64
  'RangeError',
87
65
  'ReferenceError',
88
66
  'Reflect',
89
67
  'RegExp',
90
- 'removeEventListener',
91
- 'requestAnimationFrame',
92
68
  'require',
93
69
  'Response',
94
- 'self',
95
70
  'Set',
96
- 'setInterval',
97
- 'setTimeout',
98
- 'setImmediate',
99
- 'sleep',
71
+ 'SharedArrayBuffer',
100
72
  'String',
101
73
  'Symbol',
102
74
  'SyntaxError',
@@ -106,7 +78,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
106
78
  'toString',
107
79
  'TypeError',
108
80
  'URIError',
109
- 'URL',
110
81
  'Uint16Array',
111
82
  'Uint32Array',
112
83
  'Uint8Array',
@@ -116,10 +87,44 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
116
87
  'valueOf',
117
88
  'WeakMap',
118
89
  'WeakSet',
90
+ 'WeakRef',
91
+ '__proto__',
92
+
93
+ // WorkerGlobalScope symbols, aggregated from https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope
94
+ // 'caches',
95
+ //'crossOriginIsolated',
96
+ 'crypto',
97
+ //'fonts',
98
+ // 'indexedDB',
99
+ 'isSecureContext',
100
+ 'location',
101
+ 'navigator',
102
+ //'origin',
103
+ 'performance',
104
+ // 'scheduler',
105
+ 'self',
106
+ 'console',
107
+ 'atob',
108
+ 'btoa',
109
+ 'clearInterval',
110
+ 'clearTimeout',
111
+ // 'fetch',
112
+ // 'importScripts',
113
+ 'queueMicrotask',
114
+ 'setInterval',
115
+ 'setTimeout',
116
+ 'structuredClone',
117
+ 'reportError',
118
+ 'WorkerGlobalScope',
119
+
120
+ // WebAssembly symbols
119
121
  'WebAssembly',
122
+
123
+ // WebGL symbols
120
124
  'WebGL2RenderingContext',
121
125
  'WebGLTexture',
122
- // All webGPU symbols are allowed
126
+
127
+ // WebGPU symbols
123
128
  'WebGPUWindow',
124
129
  'GPU',
125
130
  'GPUAdapter',
@@ -161,9 +166,15 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
161
166
  'GPUTextureView',
162
167
  'GPUUncapturedErrorEvent',
163
168
  'GPUValidationError',
164
- // Our own symbols
169
+
170
+ // DCP symbols / chosen web API additions
165
171
  'progress',
166
172
  'work',
173
+ 'bravojs',
174
+ 'setImmediate',
175
+ 'Blob',
176
+ 'addEventListener',
177
+ 'removeEventListener',
167
178
  ]);
168
179
 
169
180
  // Origin time for performance polyfill
@@ -178,15 +189,15 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
178
189
  // Assumption that if performance exists, performance.now must exist
179
190
  performance: typeof performance !== 'undefined' ? performance : {
180
191
  now: ()=>{
181
- res = new Date().getTime() - pt0;
192
+ const res = new Date().getTime() - pt0;
182
193
  return res;
183
194
  }
184
195
  },
185
196
  importScripts: function () {
186
197
  throw new Error('importScripts is not supported on DCP');
187
198
  },
188
- WorkerGlobalScope: typeof globalThis === 'undefined' ? self : globalThis,
189
199
  globalThis: typeof globalThis === 'undefined' ? self : globalThis,
200
+ WorkerGlobalScope: typeof globalThis === 'undefined' ? self : globalThis,
190
201
  // For browsers/SA-workers that don't support btoa/atob, modified from https://github.com/MaxArt2501/base64-js/blob/master/base64.js
191
202
  btoa: function (string) {
192
203
  var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
@@ -673,7 +684,7 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
673
684
  function applyAccessLists(obj, allowList, blockList = {}) {
674
685
  if (!obj) { return; }
675
686
  Object.getOwnPropertyNames(obj).forEach(function (prop) {
676
- if (Object.getOwnPropertyDescriptor(obj, prop).configurable) {
687
+ if (Object.getOwnPropertyDescriptor(obj, prop)?.configurable) {
677
688
  if (!allowList.has(prop)) {
678
689
  let isSet = false;
679
690
  let propValue;
@@ -809,6 +820,12 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
809
820
  };
810
821
  }
811
822
 
823
+ function allowWorktimeSymbols(symbols)
824
+ {
825
+ for (let symbol of symbols)
826
+ allowList.add(symbol);
827
+ }
828
+
812
829
  addEventListener('message', async (event) => {
813
830
  try {
814
831
  if (event.request === 'applyRequirements') {
@@ -819,6 +836,10 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
819
836
  blockList.OffscreenCanvas = !requirements.environment.offscreenCanvas;
820
837
  blockList.WebGPUWindow = !requirements.environment.webgpu;
821
838
  blockList.GPU = !requirements.environment.webgpu;
839
+
840
+ if (event.worktime && protectedStorage.worktimeGlobals[event.worktime])
841
+ allowWorktimeSymbols(protectedStorage.worktimeGlobals[event.worktime]);
842
+
822
843
  applyAllAccessLists();
823
844
 
824
845
  ring1PostMessage({ request: 'applyRequirementsDone' });
@@ -74,7 +74,7 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
74
74
  protectedStorage.sandboxConfig = message.sandboxConfig;
75
75
  Object.assign(self.work.job.public, message.job.public); /* override locale-specific defaults if specified */
76
76
  // Load bravojs' module.main with the work function
77
- module.declare(message.job.dependencies || (message.job.requireModules /* deprecated */), function mainModule(require, exports, module) {
77
+ module.declare(message.job.dependencies || (message.job.requireModules /* deprecated */), async function mainModule(require, exports, module) {
78
78
  try {
79
79
  if (exports.hasOwnProperty('job'))
80
80
  throw new Error("Tried to assign sandbox when it was already assigned"); /* Should be impossible - might happen if throw during assign? */
@@ -83,11 +83,22 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
83
83
  message.job.requirePath.map(p => require.paths.push(p));
84
84
  message.job.modulePath.map(p => module.paths.push(p));
85
85
  exports.arguments = message.job.arguments;
86
-
87
- if (message.job.useStrict)
88
- exports.job = indirectEval(`"use strict"; (${message.job.workFunction})`);
89
- else
90
- exports.job = indirectEval(`(${message.job.workFunction})`);
86
+ exports.worktime = message.job.worktime;
87
+
88
+ switch (message.job.worktime.name)
89
+ {
90
+ case 'map-basic':
91
+ if (message.job.useStrict)
92
+ exports.job = indirectEval(`"use strict"; (${message.job.workFunction})`);
93
+ else
94
+ exports.job = indirectEval(`(${message.job.workFunction})`);
95
+ break;
96
+ case 'pyodide':
97
+ exports.job = await generatePyodideFunction(message.job);
98
+ break;
99
+ default:
100
+ throw new Error(`Unsupported worktime: ${message.job.worktime.name}`);
101
+ }
91
102
  } catch(e) {
92
103
  reportError(e);
93
104
  return;
@@ -125,6 +136,148 @@ self.wrapScriptLoading({ scriptName: 'bravojs-env', ringTransition: true }, func
125
136
  }
126
137
  })
127
138
 
139
+ /**
140
+ * Factory function which generates a "map-basic"-like workFunction
141
+ * out of a Pyodide Worktime job (Python code, files, env variables).
142
+ *
143
+ * It takes any "images" passed in the workFunction "arguments" and
144
+ * writes them to the in memory filesystem provided by Emscripten.
145
+ * It adds any environment variables specified in the workFunction
146
+ * "arguments" to the pseudo-"process" for use.
147
+ * It globally imports a dcp module with function "set_slice_handler"
148
+ * which takes a python function as input. The python function passed
149
+ * to that slice handler is invoked by the function which this
150
+ * factory function returns.
151
+ *
152
+ * @param {Object} job The job data associated with the message
153
+ * @returns {Function} function pyodideWorkFn(slice) -> result
154
+ */
155
+ async function generatePyodideFunction(job)
156
+ {
157
+ var pythonSliceHandler;
158
+
159
+ const pyodide = await pyodideInit();
160
+ const sys = pyodide.pyimport('sys');
161
+
162
+ const findImports = pyodide.runPython('import pyodide; pyodide.code.find_imports');
163
+ const findPythonModuleLoader = pyodide.runPython('import importlib; importlib.find_loader');
164
+
165
+ const parsedArguments = parsePyodideArguments(job.arguments);
166
+
167
+ // write images to file and set environment variables
168
+ const prepPyodide = pyodide.runPython(`
169
+ import tarfile, io
170
+ import os, sys
171
+
172
+ def prepPyodide(args):
173
+ for image in args['images']:
174
+ image = bytes(image)
175
+ imageFile = io.BytesIO(image)
176
+ tar = tarfile.open(mode='r', fileobj=imageFile)
177
+ tar.extractall()
178
+
179
+ for item, value in args['environmentVariables'].items():
180
+ os.environ[item] = value
181
+
182
+ sys.argv.extend(args['sysArgv'])
183
+
184
+ return
185
+
186
+ prepPyodide`);
187
+
188
+ prepPyodide(pyodide.toPy(parsedArguments));
189
+
190
+ // register the dcp Python module
191
+ if (!sys.modules.get('dcp'))
192
+ {
193
+ const create_proxy = pyodide.runPython('import pyodide;pyodide.ffi.create_proxy');
194
+
195
+ pyodide.registerJsModule('dcp', {
196
+ set_slice_handler: function pyodide$$dcp$$setSliceHandler(func) {
197
+ pythonSliceHandler = create_proxy(func);
198
+ },
199
+ progress,
200
+ });
201
+ }
202
+ pyodide.runPython( 'import dcp' );
203
+
204
+ // attempt to import packages from the package manager (if they're not already loaded)
205
+ const workFunctionPythonImports = findImports(job.workFunction).toJs();
206
+ const packageManagerImports = workFunctionPythonImports.filter(x=>!findPythonModuleLoader(x));
207
+ if (packageManagerImports.length > 0)
208
+ {
209
+ await fetchAndInitPyodidePackages(packageManagerImports);
210
+ await pyodide.loadPackage(packageManagerImports);
211
+ }
212
+
213
+ return workFunctionWrapper;
214
+
215
+ /**
216
+ * Evaluates the Python WorkFunction string and then executes the slice
217
+ * handler Python function. If no call to `dcp.set_slice_handler` is passed
218
+ * or a non function is passed to it.
219
+ * This function specifically only takes one parameter since Pyodide Slice
220
+ * Handlers only accept one parameter.
221
+ */
222
+ async function workFunctionWrapper(datum)
223
+ {
224
+ const pyodide = await pyodideInit(); // returns the same promise when called multiple times
225
+
226
+ // load and execute the Python Workfunction, this populates the pythonSliceHandler variable
227
+ await pyodide.runPython(job.workFunction);
228
+
229
+ // failure to specify a slice handler is considered an error
230
+ if (!pythonSliceHandler)
231
+ throw new Error('ENOSLICEHANDLER: Must specify the slice handler using `dcp.set_slice_handler(fn)`');
232
+
233
+ // setting the slice handler to a non function or lambda is not supported
234
+ else if (typeof pythonSliceHandler !== 'function')
235
+ throw new Error('ENOSLICEHANDLER: Slice Handler must be a function');
236
+
237
+ const sliceHandlerResult = await pythonSliceHandler(datum);
238
+
239
+ // if it is a PyProxy, convert its value to JavaScript
240
+ if (sliceHandlerResult.toJs)
241
+ return sliceHandlerResult.toJs();
242
+
243
+ return sliceHandlerResult;
244
+ }
245
+
246
+ /*
247
+ * Refer to the "The Pyodide Worktime"."Work Function (JS)"."Arguments"."Commands"
248
+ * part of the DCP Worktimes spec.
249
+ */
250
+ function parsePyodideArguments(args)
251
+ {
252
+ var index = 1;
253
+ const numArgs = args[0];
254
+ const images = [];
255
+ const environmentVariables = {};
256
+ const sysArgv = args.slice(numArgs);
257
+
258
+ while (index < numArgs)
259
+ {
260
+ switch (args[index])
261
+ {
262
+ case 'gzImage':
263
+ const image = args[index+1];
264
+ images.push(image);
265
+ index+=2;
266
+ break;
267
+ case 'env':
268
+ const env = args[index+1].split(/=(.*)/s);
269
+ index+=2;
270
+ environmentVariables[env[0]] = env[1];
271
+ break;
272
+ default:
273
+ throw new Error(`Invalid argument ${args[index]}`);
274
+ }
275
+ }
276
+
277
+ return { sysArgv, images, environmentVariables };
278
+ }
279
+ }
280
+
128
281
  /** A module.declare suitable for running when processing modules arriving as part
129
282
  * of a module group or other in-memory cache.
130
283
  */
@@ -133,9 +133,11 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
133
133
  addEventListener('message', async (event) => {
134
134
  try {
135
135
  if (event.request === 'describe') {
136
+ const worktimes = globalThis.worktimes;
136
137
  const capabilities = await getCapabilities();
137
138
  ring2PostMessage({
138
139
  capabilities,
140
+ worktimes,
139
141
  request: 'describe',
140
142
  });
141
143
  }