dcp-client 4.3.5 → 4.3.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 +18 -0
- package/dist/dcp-client-bundle.js +1 -1
- package/generated/sandbox-definitions.json +1 -1
- package/index.js +6 -0
- package/libexec/sandbox/access-lists.js +82 -87
- package/libexec/sandbox/bravojs-env.js +159 -6
- package/libexec/sandbox/calculate-capabilities.js +3 -3
- package/libexec/sandbox/pyodide-core.js +3 -0
- package/libexec/sandbox/sa-ww-simulation.js +0 -1
- package/libexec/sandbox/worktimes.js +66 -0
- package/libexec/sandbox/wrap-event-listeners.js +49 -16
- package/licenses/pyodide.LICENSE.txt +376 -0
- package/ns-map.js +0 -1
- package/package.json +1 -1
|
@@ -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,24 +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
|
-
|
|
18
|
-
'
|
|
19
|
-
'applyAccesslist',
|
|
16
|
+
// global objects, aggregated from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#Reflection
|
|
17
|
+
'AggregateError',
|
|
20
18
|
'Array',
|
|
21
19
|
'ArrayBuffer',
|
|
22
|
-
'AsyncFunction',
|
|
23
20
|
'Atomics',
|
|
24
21
|
'BigInt',
|
|
25
22
|
'BigInt64Array',
|
|
26
23
|
'BigUint64Array',
|
|
27
24
|
'Boolean',
|
|
28
|
-
|
|
29
|
-
'bravojs',
|
|
30
|
-
'clearInterval',
|
|
31
|
-
'clearTimeout',
|
|
32
|
-
'console',
|
|
25
|
+
|
|
33
26
|
'constructor',
|
|
34
27
|
'DataView',
|
|
35
28
|
'Date',
|
|
@@ -41,12 +34,10 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
41
34
|
'escape',
|
|
42
35
|
'eval',
|
|
43
36
|
'EvalError',
|
|
44
|
-
'File',
|
|
45
|
-
'FileReader',
|
|
46
37
|
'Float32Array',
|
|
47
38
|
'Float64Array',
|
|
48
39
|
'Function',
|
|
49
|
-
'
|
|
40
|
+
'globalThis',
|
|
50
41
|
'Infinity',
|
|
51
42
|
'Int16Array',
|
|
52
43
|
'Int32Array',
|
|
@@ -58,7 +49,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
58
49
|
'Math',
|
|
59
50
|
'module',
|
|
60
51
|
'NaN',
|
|
61
|
-
'navigator',
|
|
62
52
|
'null',
|
|
63
53
|
'Number',
|
|
64
54
|
'Object',
|
|
@@ -67,26 +57,18 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
67
57
|
'onmessage',
|
|
68
58
|
'parseFloat',
|
|
69
59
|
'parseInt',
|
|
70
|
-
'performance',
|
|
71
60
|
'postMessage',
|
|
72
61
|
'Promise',
|
|
73
62
|
'propertyIsEnumerable',
|
|
74
63
|
'Proxy',
|
|
75
|
-
'pt0',
|
|
76
64
|
'RangeError',
|
|
77
65
|
'ReferenceError',
|
|
78
66
|
'Reflect',
|
|
79
67
|
'RegExp',
|
|
80
|
-
'removeEventListener',
|
|
81
|
-
'requestAnimationFrame',
|
|
82
68
|
'require',
|
|
83
69
|
'Response',
|
|
84
|
-
'self',
|
|
85
70
|
'Set',
|
|
86
|
-
'
|
|
87
|
-
'setTimeout',
|
|
88
|
-
'setImmediate',
|
|
89
|
-
'sleep',
|
|
71
|
+
'SharedArrayBuffer',
|
|
90
72
|
'String',
|
|
91
73
|
'Symbol',
|
|
92
74
|
'SyntaxError',
|
|
@@ -96,7 +78,6 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
96
78
|
'toString',
|
|
97
79
|
'TypeError',
|
|
98
80
|
'URIError',
|
|
99
|
-
'URL',
|
|
100
81
|
'Uint16Array',
|
|
101
82
|
'Uint32Array',
|
|
102
83
|
'Uint8Array',
|
|
@@ -106,11 +87,44 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
106
87
|
'valueOf',
|
|
107
88
|
'WeakMap',
|
|
108
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
|
|
109
121
|
'WebAssembly',
|
|
122
|
+
|
|
123
|
+
// WebGL symbols
|
|
110
124
|
'WebGL2RenderingContext',
|
|
111
125
|
'WebGLTexture',
|
|
112
|
-
|
|
113
|
-
//
|
|
126
|
+
|
|
127
|
+
// WebGPU symbols
|
|
114
128
|
'WebGPUWindow',
|
|
115
129
|
'GPU',
|
|
116
130
|
'GPUAdapter',
|
|
@@ -152,9 +166,15 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
152
166
|
'GPUTextureView',
|
|
153
167
|
'GPUUncapturedErrorEvent',
|
|
154
168
|
'GPUValidationError',
|
|
155
|
-
|
|
169
|
+
|
|
170
|
+
// DCP symbols / chosen web API additions
|
|
156
171
|
'progress',
|
|
157
172
|
'work',
|
|
173
|
+
'bravojs',
|
|
174
|
+
'setImmediate',
|
|
175
|
+
'Blob',
|
|
176
|
+
'addEventListener',
|
|
177
|
+
'removeEventListener',
|
|
158
178
|
]);
|
|
159
179
|
|
|
160
180
|
// Origin time for performance polyfill
|
|
@@ -169,15 +189,15 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
169
189
|
// Assumption that if performance exists, performance.now must exist
|
|
170
190
|
performance: typeof performance !== 'undefined' ? performance : {
|
|
171
191
|
now: ()=>{
|
|
172
|
-
res = new Date().getTime() - pt0;
|
|
192
|
+
const res = new Date().getTime() - pt0;
|
|
173
193
|
return res;
|
|
174
194
|
}
|
|
175
195
|
},
|
|
176
196
|
importScripts: function () {
|
|
177
197
|
throw new Error('importScripts is not supported on DCP');
|
|
178
198
|
},
|
|
179
|
-
WorkerGlobalScope: typeof globalThis === 'undefined' ? self : globalThis,
|
|
180
199
|
globalThis: typeof globalThis === 'undefined' ? self : globalThis,
|
|
200
|
+
WorkerGlobalScope: typeof globalThis === 'undefined' ? self : globalThis,
|
|
181
201
|
// For browsers/SA-workers that don't support btoa/atob, modified from https://github.com/MaxArt2501/base64-js/blob/master/base64.js
|
|
182
202
|
btoa: function (string) {
|
|
183
203
|
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
@@ -659,10 +679,9 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
659
679
|
*
|
|
660
680
|
* @param {object} obj - The object, which will have the allow list applied to its properties.
|
|
661
681
|
* @param {Set} allowList - A set of properties to allow people to access.
|
|
662
|
-
* @param {
|
|
663
|
-
* @param {Set} polyfills - An object of property names that have been polyfilled.
|
|
682
|
+
* @param {Object} blockList - An object of property names mapping to booleans to indicate whether access is allowed or not.
|
|
664
683
|
*/
|
|
665
|
-
function applyAccessLists(obj, allowList, blockList = {}
|
|
684
|
+
function applyAccessLists(obj, allowList, blockList = {}) {
|
|
666
685
|
if (!obj) { return; }
|
|
667
686
|
Object.getOwnPropertyNames(obj).forEach(function (prop) {
|
|
668
687
|
if (Object.getOwnPropertyDescriptor(obj, prop).configurable) {
|
|
@@ -670,17 +689,13 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
670
689
|
let isSet = false;
|
|
671
690
|
let propValue;
|
|
672
691
|
Object.defineProperty(obj, prop, {
|
|
673
|
-
get: function () {
|
|
674
|
-
if (isSet)
|
|
692
|
+
get: function getProtectedProperty() {
|
|
693
|
+
if (isSet)
|
|
675
694
|
return propValue;
|
|
676
|
-
|
|
677
|
-
if (prop in polyfills) {
|
|
678
|
-
return polyfills[prop];
|
|
679
|
-
}
|
|
695
|
+
else
|
|
680
696
|
return undefined;
|
|
681
|
-
}
|
|
682
697
|
},
|
|
683
|
-
set: function (value) {
|
|
698
|
+
set: function setProtectedProperty(value) {
|
|
684
699
|
propValue = value;
|
|
685
700
|
isSet = true;
|
|
686
701
|
},
|
|
@@ -711,30 +726,20 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
711
726
|
}
|
|
712
727
|
|
|
713
728
|
/**
|
|
714
|
-
*
|
|
715
|
-
* this list through the objects entire prototype chain
|
|
729
|
+
* Assigns each property from some polyfill object to another object, inserting or replacing for each property
|
|
716
730
|
*
|
|
717
|
-
* @param {Object} obj - The
|
|
718
|
-
* @param {
|
|
731
|
+
* @param {Object} obj - The object to add properties on
|
|
732
|
+
* @param {Object} polyfills - An object of properties to create/polyfill
|
|
719
733
|
*/
|
|
720
|
-
function applyPolyfills(obj, polyfills
|
|
734
|
+
function applyPolyfills(obj, polyfills) {
|
|
721
735
|
// Apply symbols from polyfill object
|
|
722
736
|
for (let prop in polyfills) {
|
|
723
|
-
let found = false;
|
|
724
|
-
for (let o = obj; o.__proto__ && (o.__proto__ !== Object); o = o.__proto__) {
|
|
725
|
-
if (o.hasOwnProperty(prop)) {
|
|
726
|
-
found = true;
|
|
727
|
-
break;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
if (found) { continue; }
|
|
731
737
|
let propValue = polyfills[prop];
|
|
732
738
|
Object.defineProperty(obj, prop, {
|
|
733
|
-
get: function () {
|
|
739
|
+
get: function getPolyfill() {
|
|
734
740
|
return propValue;
|
|
735
|
-
|
|
736
741
|
},
|
|
737
|
-
set: function (value) {
|
|
742
|
+
set: function setPolyfill(value) {
|
|
738
743
|
propValue = value;
|
|
739
744
|
},
|
|
740
745
|
configurable: false
|
|
@@ -748,43 +753,23 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
748
753
|
* so that the blockList is accessible to modify w/o adding it to the allowList.
|
|
749
754
|
*/
|
|
750
755
|
function applyAllAccessLists() {
|
|
751
|
-
// We need to apply the access lists to global, global
|
|
752
|
-
// because there's networking-accessing functions inside
|
|
753
|
-
//
|
|
754
|
-
// If we're in a robust environment (node, browser, WebWorker, basically anything but v8),
|
|
755
|
-
// then we have to climb the prototype chain and apply the allowList there, but we have to stop
|
|
756
|
-
// before we allow Object's properties
|
|
756
|
+
// We need to apply the access lists to global, and the entirety of global's prototype chain
|
|
757
|
+
// because there's networking-accessing functions inside the chain, like fetch.
|
|
757
758
|
|
|
758
759
|
var global = typeof globalThis === 'undefined' ? self : globalThis;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
let _GPU = ((typeof navigator !== 'undefined') && (typeof navigator.gpu !== 'undefined')) ? navigator.gpu :
|
|
762
|
-
(typeof GPU !== 'undefined'? GPU : undefined);
|
|
763
|
-
|
|
764
|
-
for (let g = global; g.__proto__ && (g.__proto__ !== Object); g = g.__proto__) {
|
|
765
|
-
applyAccessLists(g, allowList, blockList, polyfills);
|
|
766
|
-
}
|
|
760
|
+
for (let g = global; Object.getPrototypeOf(g); g = Object.getPrototypeOf(g))
|
|
761
|
+
applyAccessLists(g, allowList, blockList);
|
|
767
762
|
|
|
768
763
|
if (typeof navigator === 'undefined')
|
|
764
|
+
navigator = { userAgent: 'not a browser' };
|
|
765
|
+
else
|
|
769
766
|
{
|
|
770
|
-
|
|
771
|
-
userAgent: 'not a browser',
|
|
772
|
-
gpu: _GPU,
|
|
767
|
+
const navPolyFill = {
|
|
768
|
+
userAgent: navigator.userAgent || 'not a browser',
|
|
773
769
|
};
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
// We also want to allowList certain parts of navigator, but not others.
|
|
778
|
-
const navAllowlist = new Set([
|
|
779
|
-
'userAgent',
|
|
780
|
-
'gpu',
|
|
781
|
-
]);
|
|
782
|
-
let navPolyfill = {
|
|
783
|
-
userAgent: typeof navigator.userAgent !== 'undefined'? navigator.userAgent : 'not a browser',
|
|
784
|
-
gpu: _GPU,
|
|
785
|
-
};
|
|
786
|
-
applyAccessLists(navigator.__proto__, navAllowlist, {}, {}, navPolyfill);
|
|
787
|
-
applyPolyfills(navigator.__proto__, navPolyfill);
|
|
770
|
+
if (navigator.gpu)
|
|
771
|
+
navPolyFill.gpu = navigator.gpu;
|
|
772
|
+
navigator = navPolyFill;
|
|
788
773
|
}
|
|
789
774
|
}
|
|
790
775
|
|
|
@@ -835,6 +820,12 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
835
820
|
};
|
|
836
821
|
}
|
|
837
822
|
|
|
823
|
+
function allowWorktimeSymbols(symbols)
|
|
824
|
+
{
|
|
825
|
+
for (let symbol of symbols)
|
|
826
|
+
allowList.add(symbol);
|
|
827
|
+
}
|
|
828
|
+
|
|
838
829
|
addEventListener('message', async (event) => {
|
|
839
830
|
try {
|
|
840
831
|
if (event.request === 'applyRequirements') {
|
|
@@ -845,6 +836,10 @@ self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, fun
|
|
|
845
836
|
blockList.OffscreenCanvas = !requirements.environment.offscreenCanvas;
|
|
846
837
|
blockList.WebGPUWindow = !requirements.environment.webgpu;
|
|
847
838
|
blockList.GPU = !requirements.environment.webgpu;
|
|
839
|
+
|
|
840
|
+
if (event.worktime && protectedStorage.worktimeGlobals[event.worktime])
|
|
841
|
+
allowWorktimeSymbols(protectedStorage.worktimeGlobals[event.worktime]);
|
|
842
|
+
|
|
848
843
|
applyAllAccessLists();
|
|
849
844
|
|
|
850
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
*/
|
|
@@ -48,9 +48,7 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
|
|
|
48
48
|
return arguments[0];
|
|
49
49
|
});`
|
|
50
50
|
|
|
51
|
-
webgpu =
|
|
52
|
-
(typeof navigator !== 'undefined' &&
|
|
53
|
-
typeof navigator.gpu !== 'undefined');
|
|
51
|
+
webgpu = Boolean(globalThis.navigator?.gpu);
|
|
54
52
|
|
|
55
53
|
if (webgpu) {
|
|
56
54
|
try {
|
|
@@ -135,9 +133,11 @@ self.wrapScriptLoading({ scriptName: 'calculate-capabilities' }, function calcul
|
|
|
135
133
|
addEventListener('message', async (event) => {
|
|
136
134
|
try {
|
|
137
135
|
if (event.request === 'describe') {
|
|
136
|
+
const worktimes = globalThis.worktimes;
|
|
138
137
|
const capabilities = await getCapabilities();
|
|
139
138
|
ring2PostMessage({
|
|
140
139
|
capabilities,
|
|
140
|
+
worktimes,
|
|
141
141
|
request: 'describe',
|
|
142
142
|
});
|
|
143
143
|
}
|