dcp-client 4.3.0-3 → 4.3.0-4

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.
@@ -3967,7 +3967,7 @@ eval("// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission
3967
3967
  \**********************/
3968
3968
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
3969
3969
 
3970
- eval("/**\n * @file build.js\n * Provide build information for DCP rtlink code, the same way as the dcp/build\n * module is injected for dcp-client code. That is what src/common/dcp-build.js\n * was supposed to do. We collide symbols here with a dcp-client built-in, so\n * that the code works the same everywhere but a gives different results depending\n * on how it was linked.\n *\n * This file is expressly processed by the copy-stamp rule in the core install\n * map, so that the symbols in here get substituted with their 'real' values as\n * the product is installed. A side effect of this is that dev versions running\n * out of the source tree will have symbol names instead of symbol values here.\n *\n * @author Wes Garland, wes@distributive.network\n * @date Nov 2022\n */\n\nexports.version = '03556f0a9576ead0784808e7f17268e43af253bb';\nexports.branch = 'develop';\n\n/* When/how configure.sh ran */\nexports.config = __webpack_require__(/*! ../etc/local-config.json */ \"./etc/local-config.json\");\ndelete exports.config.install;\n\n/* When/how install.sh ran (not for dcp-client) */\nexports.install = {\n timestamp: new Date(Number('__TIMESTAMP__') * 1000),\n build: '__DCP_BUILD',\n};\n\n\n\n\n//# sourceURL=webpack://dcp/./src/build.js?");
3970
+ eval("/**\n * @file build.js\n * Provide build information for DCP rtlink code, the same way as the dcp/build\n * module is injected for dcp-client code. That is what src/common/dcp-build.js\n * was supposed to do. We collide symbols here with a dcp-client built-in, so\n * that the code works the same everywhere but a gives different results depending\n * on how it was linked.\n *\n * This file is expressly processed by the copy-stamp rule in the core install\n * map, so that the symbols in here get substituted with their 'real' values as\n * the product is installed. A side effect of this is that dev versions running\n * out of the source tree will have symbol names instead of symbol values here.\n *\n * @author Wes Garland, wes@distributive.network\n * @date Nov 2022\n */\n\nexports.version = '050c7461ed59f387f7f585e05615ff7ae0eef059';\nexports.branch = 'develop';\n\n/* When/how configure.sh ran */\nexports.config = __webpack_require__(/*! ../etc/local-config.json */ \"./etc/local-config.json\");\ndelete exports.config.install;\n\n/* When/how install.sh ran (not for dcp-client) */\nexports.install = {\n timestamp: new Date(Number('__TIMESTAMP__') * 1000),\n build: '__DCP_BUILD',\n};\n\n\n\n\n//# sourceURL=webpack://dcp/./src/build.js?");
3971
3971
 
3972
3972
  /***/ }),
3973
3973
 
@@ -4019,7 +4019,7 @@ eval("/**\n * @file dcp-assert.js\n * Simple assertion modul
4019
4019
  \*********************************/
4020
4020
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4021
4021
 
4022
- eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file dcp-build.js Return an object describing the current DCP build.\n * @author Ryan Rossiter <ryan@kingsds.network>\n * @author Wes Garland, wes@distributive.network\n * @date July 2020\n * @date Apr 2023\n */\n\nif ((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").platform) === 'nodejs')\n{\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n const fs = requireNative('fs');\n const path = requireNative('path');\n\n try\n {\n const rtlink = requireNative('dcp-rtlink');\n const localConfig = false\n || process.env.DCP_LOCAL_CONFIG_JSON /* dcp-client bundler's temp dir */\n || path.join(rtlink.runningLocation, 'etc', 'local-config.json'); /* installed or src dir config */\n Object.assign(exports, JSON.parse(fs.readFileSync(localConfig), 'utf-8'));\n }\n catch(error)\n {\n if (error.code !== 'MODULE_NOT_FOUND')\n throw error;\n /* If we arrive here, we couldn't resolve dcp-rtlink => we're on dcp-client */\n Object.assign(exports, {\"version\":\"03556f0a9576ead0784808e7f17268e43af253bb\",\"branch\":\"develop\",\"dcpClient\":{\"version\":\"4.3.0-2\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#eb9670bbaaba1a3cc588440632ae9d7a51618da7\",\"overridden\":false},\"built\":\"Mon Sep 11 2023 10:19:42 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Mon 11 Sep 2023 10:19:41 AM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.88.2\",\"node\":\"v18.17.1\"}.config);\n }\n}\nelse\n{\n /* For all non-node platforms, just assume that we're also on dcp-client */\n Object.assign(exports, {\"version\":\"03556f0a9576ead0784808e7f17268e43af253bb\",\"branch\":\"develop\",\"dcpClient\":{\"version\":\"4.3.0-2\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#eb9670bbaaba1a3cc588440632ae9d7a51618da7\",\"overridden\":false},\"built\":\"Mon Sep 11 2023 10:19:42 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Mon 11 Sep 2023 10:19:41 AM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.88.2\",\"node\":\"v18.17.1\"}.config);\n}\n\nif (typeof exports.build === 'undefined') {\n throw new Error('Could not determine build type!');\n}\n\n\n//# sourceURL=webpack://dcp/./src/common/dcp-build.js?");
4022
+ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file dcp-build.js Return an object describing the current DCP build.\n * @author Ryan Rossiter <ryan@kingsds.network>\n * @author Wes Garland, wes@distributive.network\n * @date July 2020\n * @date Apr 2023\n */\n\nif ((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").platform) === 'nodejs')\n{\n const { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\n const fs = requireNative('fs');\n const path = requireNative('path');\n\n try\n {\n const rtlink = requireNative('dcp-rtlink');\n const localConfig = false\n || process.env.DCP_LOCAL_CONFIG_JSON /* dcp-client bundler's temp dir */\n || path.join(rtlink.runningLocation, 'etc', 'local-config.json'); /* installed or src dir config */\n Object.assign(exports, JSON.parse(fs.readFileSync(localConfig), 'utf-8'));\n }\n catch(error)\n {\n if (error.code !== 'MODULE_NOT_FOUND')\n throw error;\n /* If we arrive here, we couldn't resolve dcp-rtlink => we're on dcp-client */\n Object.assign(exports, {\"version\":\"050c7461ed59f387f7f585e05615ff7ae0eef059\",\"branch\":\"develop\",\"dcpClient\":{\"version\":\"4.3.0-3\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#83021d39982e2d0e6fd5b400e59a4f7501fb3e7e\",\"overridden\":false},\"built\":\"Mon Sep 18 2023 14:14:55 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Mon 18 Sep 2023 02:14:54 PM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.88.2\",\"node\":\"v18.17.1\"}.config);\n }\n}\nelse\n{\n /* For all non-node platforms, just assume that we're also on dcp-client */\n Object.assign(exports, {\"version\":\"050c7461ed59f387f7f585e05615ff7ae0eef059\",\"branch\":\"develop\",\"dcpClient\":{\"version\":\"4.3.0-3\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#83021d39982e2d0e6fd5b400e59a4f7501fb3e7e\",\"overridden\":false},\"built\":\"Mon Sep 18 2023 14:14:55 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Mon 18 Sep 2023 02:14:54 PM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.88.2\",\"node\":\"v18.17.1\"}.config);\n}\n\nif (typeof exports.build === 'undefined') {\n throw new Error('Could not determine build type!');\n}\n\n\n//# sourceURL=webpack://dcp/./src/common/dcp-build.js?");
4023
4023
 
4024
4024
  /***/ }),
4025
4025
 
@@ -4293,7 +4293,7 @@ eval("/**\n * @file password.js\n * Modal providing a way to
4293
4293
  \**********************************************/
4294
4294
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4295
4295
 
4296
- eval("/**\n * @file client-modal/utils.js\n * @author KC Erb\n * @date Mar 2020\n * \n * All shared functions among the modals.\n */\nconst { fetchRelative } = __webpack_require__(/*! ./fetch-relative */ \"./src/dcp-client/client-modal/fetch-relative.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nexports.OnCloseErrorCode = 'DCP_CM:CANCELX';\n\nif (DCP_ENV.isBrowserPlatform) {\n // Provide as export for the convenience of `utils.MicroModal` instead of a separate require.\n exports.MicroModal = __webpack_require__(/*! micromodal */ \"./node_modules/micromodal/dist/micromodal.es.js\")[\"default\"];\n}\n\n/**\n * Return a unique string, formatted as a GET parameter, that changes often enough to\n * always force the browser to fetch the latest version of our resource.\n *\n * @note Currently always returns the Date-based poison due to webpack. \n */\nfunction cachePoison() {\n if (true)\n return '?ucp=03556f0a9576ead0784808e7f17268e43af253bb'; /* installer token */\n return '?ucp=' + Date.now();\n}\n \n/* Detect load type - on webpack, load dynamic content relative to webpack bundle;\n * otherwise load relative to the current scheduler's configured portal.\n */\nexports.myScript = (typeof document !== 'undefined') && document.currentScript;\nexports.corsProxyHref = undefined;\nif (exports.myScript && exports.myScript === (__webpack_require__(/*! ./fetch-relative */ \"./src/dcp-client/client-modal/fetch-relative.js\").myScript)) {\n let url = new ((__webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\").DcpURL))(exports.myScript.src);\n exports.corsProxyHref = url.resolve('../cors-proxy.html');\n}\n\n/**\n * Look for modal id and required ids on page based on config, if not found, provide from dcp-client.\n * The first id in the required array must be the id of the modal's form element.\n * @param {Object} modalConfig Modal configuration object\n * @param {string} modalConfig.id Id of parent modal element\n * @param {string[]} modalConfig.required Array of required ids in parent modal element\n * @param {string[]} [modalConfig.optional] Array of optional ids in parent modal element\n * @param {string} modalConfig.path Relative path to modal html in dcp-client\n * @returns {DOMElement[]} Array of modal elements on page [config.id, ...config.required]\n */\nexports.initModal = async function (modalConfig, onClose) {\n exports.corsProxyHref = exports.corsProxyHref || dcpConfig.portal.location.resolve('dcp-client/cors-proxy.html');\n\n // Call ensure modal on any eager-loaded modals.\n if (modalConfig.eagerLoad) {\n Promise.all(\n modalConfig.eagerLoad.map(config => ensureModal(config))\n )\n };\n\n const [elements, optionalElements] = await ensureModal(modalConfig);\n\n // Wire up form to prevent default, resolve on submission, reject+reset when closed (or call onClose when closed)\n const [modal, form] = elements;\n form.reset(); // ensure that form is fresh\n let formResolve, formReject;\n let formPromise = new Promise( function(res, rej) {\n formResolve = res;\n formReject = rej;\n });\n form.onsubmit = function (submitEvent) {\n submitEvent.preventDefault();\n modal.setAttribute(\"data-state\", \"submitted\");\n formResolve(submitEvent);\n }\n\n exports.MicroModal.show(modalConfig.id, { \n disableFocus: true, \n onClose: onClose || getDefaultOnClose(formReject)\n });\n return [elements, formPromise, optionalElements];\n};\n\n// Ensure all required modal elements are on page according to modalConfig\nasync function ensureModal(modalConfig) {\n let allRequiredIds = [modalConfig.id, ...modalConfig.required];\n let missing = allRequiredIds.filter( id => !document.getElementById(id) );\n if (missing.length > 0) {\n if (missing.length !== allRequiredIds.length)\n console.warn(`Some of the ids needed to replace the default DCP-modal were found, but not all. So the default DCP-Modal will be used. Missing ids are: [${missing}].`);\n let contents = await fetchRelative(exports.corsProxyHref, modalConfig.path + cachePoison());\n const container = document.createElement('div');\n container.innerHTML = contents;\n document.body.appendChild(container);\n }\n\n const elements = allRequiredIds.map(id => document.getElementById(id));\n const optionalElements = (modalConfig.optional || []).map(id => document.getElementById(id));\n return [elements, optionalElements];\n};\n\n// This onClose is called by MicroModal and thus has the modal passed to it.\nfunction getDefaultOnClose (formReject) {\n return (modal) => {\n modal.offsetLeft; // forces style recalc\n const origState = modal.dataset.state;\n // reset form including data-state\n modal.setAttribute(\"data-state\", \"new\");\n // reject if closed without submitting form.\n if (origState !== \"submitted\") {\n const err = new DCPError(\"Modal was closed but modal's form was not submitted.\", exports.OnCloseErrorCode);\n formReject(err);\n }\n }\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/client-modal/utils.js?");
4296
+ eval("/**\n * @file client-modal/utils.js\n * @author KC Erb\n * @date Mar 2020\n * \n * All shared functions among the modals.\n */\nconst { fetchRelative } = __webpack_require__(/*! ./fetch-relative */ \"./src/dcp-client/client-modal/fetch-relative.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nexports.OnCloseErrorCode = 'DCP_CM:CANCELX';\n\nif (DCP_ENV.isBrowserPlatform) {\n // Provide as export for the convenience of `utils.MicroModal` instead of a separate require.\n exports.MicroModal = __webpack_require__(/*! micromodal */ \"./node_modules/micromodal/dist/micromodal.es.js\")[\"default\"];\n}\n\n/**\n * Return a unique string, formatted as a GET parameter, that changes often enough to\n * always force the browser to fetch the latest version of our resource.\n *\n * @note Currently always returns the Date-based poison due to webpack. \n */\nfunction cachePoison() {\n if (true)\n return '?ucp=050c7461ed59f387f7f585e05615ff7ae0eef059'; /* installer token */\n return '?ucp=' + Date.now();\n}\n \n/* Detect load type - on webpack, load dynamic content relative to webpack bundle;\n * otherwise load relative to the current scheduler's configured portal.\n */\nexports.myScript = (typeof document !== 'undefined') && document.currentScript;\nexports.corsProxyHref = undefined;\nif (exports.myScript && exports.myScript === (__webpack_require__(/*! ./fetch-relative */ \"./src/dcp-client/client-modal/fetch-relative.js\").myScript)) {\n let url = new ((__webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\").DcpURL))(exports.myScript.src);\n exports.corsProxyHref = url.resolve('../cors-proxy.html');\n}\n\n/**\n * Look for modal id and required ids on page based on config, if not found, provide from dcp-client.\n * The first id in the required array must be the id of the modal's form element.\n * @param {Object} modalConfig Modal configuration object\n * @param {string} modalConfig.id Id of parent modal element\n * @param {string[]} modalConfig.required Array of required ids in parent modal element\n * @param {string[]} [modalConfig.optional] Array of optional ids in parent modal element\n * @param {string} modalConfig.path Relative path to modal html in dcp-client\n * @returns {DOMElement[]} Array of modal elements on page [config.id, ...config.required]\n */\nexports.initModal = async function (modalConfig, onClose) {\n exports.corsProxyHref = exports.corsProxyHref || dcpConfig.portal.location.resolve('dcp-client/cors-proxy.html');\n\n // Call ensure modal on any eager-loaded modals.\n if (modalConfig.eagerLoad) {\n Promise.all(\n modalConfig.eagerLoad.map(config => ensureModal(config))\n )\n };\n\n const [elements, optionalElements] = await ensureModal(modalConfig);\n\n // Wire up form to prevent default, resolve on submission, reject+reset when closed (or call onClose when closed)\n const [modal, form] = elements;\n form.reset(); // ensure that form is fresh\n let formResolve, formReject;\n let formPromise = new Promise( function(res, rej) {\n formResolve = res;\n formReject = rej;\n });\n form.onsubmit = function (submitEvent) {\n submitEvent.preventDefault();\n modal.setAttribute(\"data-state\", \"submitted\");\n formResolve(submitEvent);\n }\n\n exports.MicroModal.show(modalConfig.id, { \n disableFocus: true, \n onClose: onClose || getDefaultOnClose(formReject)\n });\n return [elements, formPromise, optionalElements];\n};\n\n// Ensure all required modal elements are on page according to modalConfig\nasync function ensureModal(modalConfig) {\n let allRequiredIds = [modalConfig.id, ...modalConfig.required];\n let missing = allRequiredIds.filter( id => !document.getElementById(id) );\n if (missing.length > 0) {\n if (missing.length !== allRequiredIds.length)\n console.warn(`Some of the ids needed to replace the default DCP-modal were found, but not all. So the default DCP-Modal will be used. Missing ids are: [${missing}].`);\n let contents = await fetchRelative(exports.corsProxyHref, modalConfig.path + cachePoison());\n const container = document.createElement('div');\n container.innerHTML = contents;\n document.body.appendChild(container);\n }\n\n const elements = allRequiredIds.map(id => document.getElementById(id));\n const optionalElements = (modalConfig.optional || []).map(id => document.getElementById(id));\n return [elements, optionalElements];\n};\n\n// This onClose is called by MicroModal and thus has the modal passed to it.\nfunction getDefaultOnClose (formReject) {\n return (modal) => {\n modal.offsetLeft; // forces style recalc\n const origState = modal.dataset.state;\n // reset form including data-state\n modal.setAttribute(\"data-state\", \"new\");\n // reject if closed without submitting form.\n if (origState !== \"submitted\") {\n const err = new DCPError(\"Modal was closed but modal's form was not submitted.\", exports.OnCloseErrorCode);\n formReject(err);\n }\n }\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/client-modal/utils.js?");
4297
4297
 
4298
4298
  /***/ }),
4299
4299
 
@@ -4325,7 +4325,7 @@ eval("/**\n * @file Module that implements Compute API\n * @module dcp/comput
4325
4325
  /***/ ((module, exports, __webpack_require__) => {
4326
4326
 
4327
4327
  "use strict";
4328
- eval("/* module decorator */ module = __webpack_require__.nmd(module);\n/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file dcp-client-bundle-src.js\n * Top-level file which gets webpacked into the bundle consumed by dcp-client 2.5\n * @author Wes Garland, wes@kingsds.network\n * @date July 2019\n */\n\n{\n let thisScript = typeof document !== 'undefined' ? (typeof document.currentScript !== 'undefined' && document.currentScript) || document.getElementById('_dcp_client_bundle') : {}\n let realModuleDeclare\n\n if ( false || typeof module.declare === 'undefined') {\n realModuleDeclare = ( true) ? module.declare : 0\n if (false) {}\n module.declare = function moduleUnWrapper (deps, factory) {\n factory(null, module.exports, module)\n return module.exports\n }\n }\n\n let _debugging = () => false\n if (process.env.DCP_CONFIG_USE_DEPRECATED_FUTURE)\n dcpConfig.future = (__webpack_require__(/*! ../common/config-future.js */ \"./src/common/config-future.js\").futureFactory)(_debugging, dcpConfig);\n\n /* These modules are official API and must be part of DCP Client */\n let officialApi = {\n 'protocol': __webpack_require__(/*! ../protocol-v4 */ \"./src/protocol-v4/index.js\"),\n 'compute': (__webpack_require__(/*! ./compute */ \"./src/dcp-client/compute.js\").compute),\n 'worker': __webpack_require__(/*! ./worker */ \"./src/dcp-client/worker/index.js\"),\n 'wallet': __webpack_require__(/*! ./wallet */ \"./src/dcp-client/wallet/index.js\"),\n };\n\n /* Some of these modules are API-track. Some of them need to be published to be\n * available for top-level resolution by DCP internals. Those (mostly) should have\n * been written using relative module paths.....\n */\n let modules = Object.assign({\n 'dcp-build': {\"version\":\"03556f0a9576ead0784808e7f17268e43af253bb\",\"branch\":\"develop\",\"dcpClient\":{\"version\":\"4.3.0-2\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#eb9670bbaaba1a3cc588440632ae9d7a51618da7\",\"overridden\":false},\"built\":\"Mon Sep 11 2023 10:19:42 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Mon 11 Sep 2023 10:19:41 AM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.88.2\",\"node\":\"v18.17.1\"},\n 'dcp-xhr': __webpack_require__(/*! ../common/dcp-xhr */ \"./src/common/dcp-xhr.js\"),\n 'dcp-env': __webpack_require__(/*! ../common/dcp-env */ \"./src/common/dcp-env.js\"),\n 'dcp-url': __webpack_require__(/*! ../common/dcp-url */ \"./src/common/dcp-url.js\"),\n 'cli': __webpack_require__(/*! ../common/cli */ \"./src/common/cli.js\"),\n 'dcp-timers': __webpack_require__(/*! ../common/dcp-timers */ \"./src/common/dcp-timers.js\"),\n 'dcp-dot-dir': __webpack_require__(/*! ../common/dcp-dot-dir */ \"./src/common/dcp-dot-dir.js\"),\n 'dcp-assert': __webpack_require__(/*! ../common/dcp-assert */ \"./src/common/dcp-assert.js\"),\n 'dcp-events': __webpack_require__(/*! ../common/dcp-events */ \"./src/common/dcp-events/index.js\"),\n 'utils': __webpack_require__(/*! ../utils */ \"./src/utils/index.js\"),\n 'debugging': __webpack_require__(/*! ../debugging */ \"./src/debugging.js\"),\n 'publish': __webpack_require__(/*! ../common/dcp-publish */ \"./src/common/dcp-publish.js\"),\n 'compute-groups': {\n ...__webpack_require__(/*! ./compute-groups */ \"./src/dcp-client/compute-groups/index.js\"),\n publicGroupOpaqueId: (__webpack_require__(/*! ../common/scheduler-constants */ \"./src/common/scheduler-constants.js\").computeGroups[\"public\"].opaqueId),\n },\n 'bank-util': __webpack_require__(/*! ./bank-util */ \"./src/dcp-client/bank-util.js\"),\n 'protocol-v4': __webpack_require__(/*! ../protocol-v4 */ \"./src/protocol-v4/index.js\"), /* deprecated */\n 'client-modal': __webpack_require__(/*! ./client-modal */ \"./src/dcp-client/client-modal/index.js\"),\n 'eth': __webpack_require__(/*! ./wallet/eth */ \"./src/dcp-client/wallet/eth.js\"),\n 'serialize': __webpack_require__(/*! ../utils/serialize */ \"./src/utils/serialize.js\"),\n 'kvin': __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\"),\n 'job': __webpack_require__(/*! ./job */ \"./src/dcp-client/job/index.js\"),\n 'range-object': __webpack_require__(/*! ./range-object */ \"./src/dcp-client/range-object.js\"),\n 'stats-ranges': __webpack_require__(/*! ./stats-ranges */ \"./src/dcp-client/stats-ranges.js\"),\n 'job-values': __webpack_require__(/*! ./job-values */ \"./src/dcp-client/job-values.js\"),\n 'signal-handler': __webpack_require__(/*! ../node-libs/signal-handler */ \"./src/node-libs/signal-handler.js\"),\n 'standard-objects': {}\n }, officialApi);\n\n /* Export the JS Standard Classes (etc) from the global object of the bundle evaluation context,\n * in case we have code somewhere that needs to use these for instanceof checks.\n */\n ;[ Object, Function, Boolean, Symbol,\n Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError,\n Number, Math, Date,\n String, RegExp,\n Array, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array,\n Map, Set, WeakMap, WeakSet,\n ArrayBuffer, DataView, JSON,\n Promise, \n Reflect, Proxy, Intl, WebAssembly, __webpack_require__\n ].forEach(function (obj) {\n if (obj.name && (typeof obj === 'function' || typeof obj === 'object'))\n modules['standard-objects'][obj.name] = obj\n })\n\n /* Export the constructors used by object literals and boxing objects. Usually these are the same as\n * the standard objects, but not always -- evaluation environments like node vm contexts can have\n * different standard objects supplied via the sandboxing object than the engine uses internally.\n */\n modules['engine-constructors'] = {\n Object: ({}).constructor,\n Array: ([]).constructor,\n Number: (0).constructor,\n String: ('').constructor,\n Boolean: (false).constructor,\n Function: (()=>1).constructor,\n };\n\n if (typeof BigInt !== 'undefined')\n {\n modules['standard-objects']['BigInt'] === BigInt;\n modules['engine-constructors'].BigInt = (0n).constructor;\n }\n if (typeof BigInt64Array !== 'undefined')\n modules['standard-objects']['BigInt64Array'] === BigInt64Array;\n if (typeof BigInt64Array !== 'undefined')\n modules['standard-objects']['BigUint64Array'] === BigUint64Array;\n\n module.declare([], function(require, exports, module) {\n Object.assign(exports, modules)\n exports['dcp-config'] = dcpConfig\n exports['dcp-default-config'] = {\"_serializeVerId\":\"v8\",\"what\":{\"ctr\":0,\"ps\":{\"dcp\":{\"ctr\":0,\"ps\":{\"connectionOptions\":{\"ctr\":0,\"ps\":{\"default\":{\"ctr\":0,\"ps\":{\"connectTimeout\":60,\"disconnectTimeout\":900,\"lingerTimeout\":18000,\"identityUnlockTimeout\":300,\"batchWaitTime\":0.03,\"ttl\":{\"raw\":{\"min\":15,\"max\":600,\"default\":120}},\"transports\":{\"arr\":[\"socketio\"]},\"socketio\":{\"raw\":{}}}}}},\"validitySlopValue\":10,\"validityStampCachePurgeInterval\":60,\"maxConnectionTimeout\":300000}},\"worker\":{\"raw\":{}},\"evaluator\":{\"ctr\":0,\"ps\":{\"listen\":{\"ctr\":\"dcpUrl$$DcpURL\",\"ps\":{},\"arg\":\"http://localhost:9000/\"},\"location\":{\"ctr\":\"dcpUrl$$DcpURL\",\"ps\":{},\"arg\":\"http://localhost:9000/\"},\"friendLocation\":{\"seen\":6}}},\"supervisor\":{\"raw\":{\"dcp\":{\"connectionOptions\":{\"default\":{\"identityUnlockTimeout\":900}}},\"tuning\":{\"watchdogInterval\":7,\"minSandboxStartDelay\":0.1,\"maxSandboxStartDelay\":0.7,\"minSandboxSlack\":0.2,\"maxSandboxSlack\":0.5,\"maxSandboxSliceRetries\":1,\"cachedJobsThreshold\":0,\"prefetchInterval\":30,\"pruneFrequency\":30000,\"mustPruneMultiplier\":1.3,\"defaultDelayIncrement\":50,\"maxExtraSandboxes\":8,\"maxResultSubmissionRetries\":3,\"reservedSliceLifetime\":300000},\"repoMan\":{\"frequency\":60000,\"thresholdMultiplier\":2},\"sandbox\":{\"generalTimeout\":6000,\"sliceTimeout\":86400000,\"progressTimeout\":300000,\"progressReportInterval\":1200000,\"maxSandboxUse\":1000},\"pCores\":0}},\"standaloneWorker\":{\"undefined\":true},\"scheduler\":{\"ctr\":0,\"ps\":{\"location\":{\"ctr\":\"dcpUrl$$DcpURL\",\"ps\":{},\"arg\":\"https://scheduler.distributed.computer/\"},\"worker\":{\"ctr\":0,\"ps\":{\"types\":{\"arr\":[\"v4\"]},\"operations\":{\"raw\":\"1.0.0\"}}},\"compatibility\":{\"raw\":{\"minimum\":{\"dcp\":\"^5.0.0\",\"dcp-client\":\"^4.0.0\",\"dcp-worker\":\"^2.1.0\",\"operations\":{\"work\":\"^4.1.0\",\"compute\":\"^1.0.0\",\"bank\":\"^4.0.0\"}},\"exclusions\":{}}}}}}}};\n })\n if (realModuleDeclare)\n module.declare = realModuleDeclare\n\n bundleExports = thisScript.exports = exports; /* must be last expression evaluated! */\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/index.js?");
4328
+ eval("/* module decorator */ module = __webpack_require__.nmd(module);\n/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/**\n * @file dcp-client-bundle-src.js\n * Top-level file which gets webpacked into the bundle consumed by dcp-client 2.5\n * @author Wes Garland, wes@kingsds.network\n * @date July 2019\n */\n\n{\n let thisScript = typeof document !== 'undefined' ? (typeof document.currentScript !== 'undefined' && document.currentScript) || document.getElementById('_dcp_client_bundle') : {}\n let realModuleDeclare\n\n if ( false || typeof module.declare === 'undefined') {\n realModuleDeclare = ( true) ? module.declare : 0\n if (false) {}\n module.declare = function moduleUnWrapper (deps, factory) {\n factory(null, module.exports, module)\n return module.exports\n }\n }\n\n let _debugging = () => false\n if (process.env.DCP_CONFIG_USE_DEPRECATED_FUTURE)\n dcpConfig.future = (__webpack_require__(/*! ../common/config-future.js */ \"./src/common/config-future.js\").futureFactory)(_debugging, dcpConfig);\n\n /* These modules are official API and must be part of DCP Client */\n let officialApi = {\n 'protocol': __webpack_require__(/*! ../protocol-v4 */ \"./src/protocol-v4/index.js\"),\n 'compute': (__webpack_require__(/*! ./compute */ \"./src/dcp-client/compute.js\").compute),\n 'worker': __webpack_require__(/*! ./worker */ \"./src/dcp-client/worker/index.js\"),\n 'wallet': __webpack_require__(/*! ./wallet */ \"./src/dcp-client/wallet/index.js\"),\n };\n\n /* Some of these modules are API-track. Some of them need to be published to be\n * available for top-level resolution by DCP internals. Those (mostly) should have\n * been written using relative module paths.....\n */\n let modules = Object.assign({\n 'dcp-build': {\"version\":\"050c7461ed59f387f7f585e05615ff7ae0eef059\",\"branch\":\"develop\",\"dcpClient\":{\"version\":\"4.3.0-3\",\"resolved\":\"git+ssh://git@gitlab.com/Distributed-Compute-Protocol/dcp-client.git#83021d39982e2d0e6fd5b400e59a4f7501fb3e7e\",\"overridden\":false},\"built\":\"Mon Sep 18 2023 14:14:55 GMT-0400 (Eastern Daylight Saving Time)\",\"config\":{\"generated\":\"Mon 18 Sep 2023 02:14:54 PM EDT by erose on lorge\",\"build\":\"debug\"},\"webpack\":\"5.88.2\",\"node\":\"v18.17.1\"},\n 'dcp-xhr': __webpack_require__(/*! ../common/dcp-xhr */ \"./src/common/dcp-xhr.js\"),\n 'dcp-env': __webpack_require__(/*! ../common/dcp-env */ \"./src/common/dcp-env.js\"),\n 'dcp-url': __webpack_require__(/*! ../common/dcp-url */ \"./src/common/dcp-url.js\"),\n 'cli': __webpack_require__(/*! ../common/cli */ \"./src/common/cli.js\"),\n 'dcp-timers': __webpack_require__(/*! ../common/dcp-timers */ \"./src/common/dcp-timers.js\"),\n 'dcp-dot-dir': __webpack_require__(/*! ../common/dcp-dot-dir */ \"./src/common/dcp-dot-dir.js\"),\n 'dcp-assert': __webpack_require__(/*! ../common/dcp-assert */ \"./src/common/dcp-assert.js\"),\n 'dcp-events': __webpack_require__(/*! ../common/dcp-events */ \"./src/common/dcp-events/index.js\"),\n 'utils': __webpack_require__(/*! ../utils */ \"./src/utils/index.js\"),\n 'debugging': __webpack_require__(/*! ../debugging */ \"./src/debugging.js\"),\n 'publish': __webpack_require__(/*! ../common/dcp-publish */ \"./src/common/dcp-publish.js\"),\n 'compute-groups': {\n ...__webpack_require__(/*! ./compute-groups */ \"./src/dcp-client/compute-groups/index.js\"),\n publicGroupOpaqueId: (__webpack_require__(/*! ../common/scheduler-constants */ \"./src/common/scheduler-constants.js\").computeGroups[\"public\"].opaqueId),\n },\n 'bank-util': __webpack_require__(/*! ./bank-util */ \"./src/dcp-client/bank-util.js\"),\n 'protocol-v4': __webpack_require__(/*! ../protocol-v4 */ \"./src/protocol-v4/index.js\"), /* deprecated */\n 'client-modal': __webpack_require__(/*! ./client-modal */ \"./src/dcp-client/client-modal/index.js\"),\n 'eth': __webpack_require__(/*! ./wallet/eth */ \"./src/dcp-client/wallet/eth.js\"),\n 'serialize': __webpack_require__(/*! ../utils/serialize */ \"./src/utils/serialize.js\"),\n 'kvin': __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\"),\n 'job': __webpack_require__(/*! ./job */ \"./src/dcp-client/job/index.js\"),\n 'range-object': __webpack_require__(/*! ./range-object */ \"./src/dcp-client/range-object.js\"),\n 'stats-ranges': __webpack_require__(/*! ./stats-ranges */ \"./src/dcp-client/stats-ranges.js\"),\n 'job-values': __webpack_require__(/*! ./job-values */ \"./src/dcp-client/job-values.js\"),\n 'signal-handler': __webpack_require__(/*! ../node-libs/signal-handler */ \"./src/node-libs/signal-handler.js\"),\n 'standard-objects': {}\n }, officialApi);\n\n /* Export the JS Standard Classes (etc) from the global object of the bundle evaluation context,\n * in case we have code somewhere that needs to use these for instanceof checks.\n */\n ;[ Object, Function, Boolean, Symbol,\n Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError,\n Number, Math, Date,\n String, RegExp,\n Array, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array,\n Map, Set, WeakMap, WeakSet,\n ArrayBuffer, DataView, JSON,\n Promise, \n Reflect, Proxy, Intl, WebAssembly, __webpack_require__\n ].forEach(function (obj) {\n if (obj.name && (typeof obj === 'function' || typeof obj === 'object'))\n modules['standard-objects'][obj.name] = obj\n })\n\n /* Export the constructors used by object literals and boxing objects. Usually these are the same as\n * the standard objects, but not always -- evaluation environments like node vm contexts can have\n * different standard objects supplied via the sandboxing object than the engine uses internally.\n */\n modules['engine-constructors'] = {\n Object: ({}).constructor,\n Array: ([]).constructor,\n Number: (0).constructor,\n String: ('').constructor,\n Boolean: (false).constructor,\n Function: (()=>1).constructor,\n };\n\n if (typeof BigInt !== 'undefined')\n {\n modules['standard-objects']['BigInt'] === BigInt;\n modules['engine-constructors'].BigInt = (0n).constructor;\n }\n if (typeof BigInt64Array !== 'undefined')\n modules['standard-objects']['BigInt64Array'] === BigInt64Array;\n if (typeof BigInt64Array !== 'undefined')\n modules['standard-objects']['BigUint64Array'] === BigUint64Array;\n\n module.declare([], function(require, exports, module) {\n Object.assign(exports, modules)\n exports['dcp-config'] = dcpConfig\n exports['dcp-default-config'] = {\"_serializeVerId\":\"v8\",\"what\":{\"ctr\":0,\"ps\":{\"dcp\":{\"ctr\":0,\"ps\":{\"connectionOptions\":{\"ctr\":0,\"ps\":{\"default\":{\"ctr\":0,\"ps\":{\"connectTimeout\":60,\"disconnectTimeout\":900,\"lingerTimeout\":18000,\"identityUnlockTimeout\":300,\"batchWaitTime\":0.03,\"ttl\":{\"raw\":{\"min\":15,\"max\":600,\"default\":120}},\"transports\":{\"arr\":[\"socketio\"]},\"socketio\":{\"raw\":{}}}}}},\"validitySlopValue\":10,\"validityStampCachePurgeInterval\":60,\"maxConnectionTimeout\":300000}},\"worker\":{\"raw\":{}},\"evaluator\":{\"ctr\":0,\"ps\":{\"listen\":{\"ctr\":\"dcpUrl$$DcpURL\",\"ps\":{},\"arg\":\"http://localhost:9000/\"},\"location\":{\"ctr\":\"dcpUrl$$DcpURL\",\"ps\":{},\"arg\":\"http://localhost:9000/\"},\"friendLocation\":{\"seen\":6}}},\"supervisor\":{\"raw\":{\"dcp\":{\"connectionOptions\":{\"default\":{\"identityUnlockTimeout\":900}}},\"tuning\":{\"watchdogInterval\":7,\"minSandboxStartDelay\":0.1,\"maxSandboxStartDelay\":0.7,\"minSandboxSlack\":0.2,\"maxSandboxSlack\":0.5,\"maxSandboxSliceRetries\":1,\"cachedJobsThreshold\":0,\"prefetchInterval\":30,\"pruneFrequency\":30000,\"mustPruneMultiplier\":1.3,\"defaultDelayIncrement\":50,\"maxExtraSandboxes\":8,\"maxResultSubmissionRetries\":3,\"reservedSliceLifetime\":300000},\"repoMan\":{\"frequency\":60000,\"thresholdMultiplier\":2},\"sandbox\":{\"generalTimeout\":6000,\"sliceTimeout\":86400000,\"progressTimeout\":300000,\"progressReportInterval\":1200000,\"maxSandboxUse\":1000},\"pCores\":0}},\"standaloneWorker\":{\"undefined\":true},\"scheduler\":{\"ctr\":0,\"ps\":{\"location\":{\"ctr\":\"dcpUrl$$DcpURL\",\"ps\":{},\"arg\":\"https://scheduler.distributed.computer/\"},\"worker\":{\"ctr\":0,\"ps\":{\"types\":{\"arr\":[\"v4\"]},\"operations\":{\"raw\":\"1.0.0\"}}},\"compatibility\":{\"raw\":{\"minimum\":{\"dcp\":\"^5.0.0\",\"dcp-client\":\"^4.0.0\",\"dcp-worker\":\"^2.1.0\",\"operations\":{\"work\":\"^4.1.0\",\"compute\":\"^1.0.0\",\"bank\":\"^4.0.0\"}},\"exclusions\":{}}}}}}}};\n })\n if (realModuleDeclare)\n module.declare = realModuleDeclare\n\n bundleExports = thisScript.exports = exports; /* must be last expression evaluated! */\n}\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/index.js?");
4329
4329
 
4330
4330
  /***/ }),
4331
4331
 
@@ -4346,7 +4346,7 @@ eval("/**\n * @file job-values.js\n * Utility code related t
4346
4346
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4347
4347
 
4348
4348
  "use strict";
4349
- eval("/**\n * @file job/index.js\n * @author Eddie Roosenmaallen, eddie@kingsds.network\n * Matthew Palma, mpalma@kingsds.network\n * Wes Garland, wes@kingsds.network\n * Paul, paul@kingsds.network\n * Ryan Saweczko, ryansaweczko@kingsds.network\n * @date November 2018\n * November 2018\n * February 2022\n * May 2022\n * Jun 2022\n *\n * This module implements the Compute API's Job Handle\n *\n */\n// @ts-check\n\n\nconst { BigNumber } = __webpack_require__(/*! bignumber.js */ \"./node_modules/bignumber.js/bignumber.js\");\nconst { v4: uuidv4 } = __webpack_require__(/*! uuid */ \"./node_modules/uuid/dist/esm-browser/index.js\");\nconst { EventEmitter, PropagatingEventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst { RangeObject, MultiRangeObject, DistributionRange, SuperRangeObject, SparseRangeObject } = __webpack_require__(/*! dcp/dcp-client/range-object */ \"./src/dcp-client/range-object.js\");\nconst { fetchURI, encodeDataURI, createTempFile } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { serializeJobValue, RemoteValue } = __webpack_require__(/*! dcp/dcp-client/job-values */ \"./src/dcp-client/job-values.js\");\nconst wallet = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst protocolV4 = __webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst ClientModal = __webpack_require__(/*! dcp/dcp-client/client-modal */ \"./src/dcp-client/client-modal/index.js\");\nconst { Worker } = __webpack_require__(/*! dcp/dcp-client/worker */ \"./src/dcp-client/worker/index.js\");\nconst { RemoteDataSet } = __webpack_require__(/*! dcp/dcp-client/remote-data-set */ \"./src/dcp-client/remote-data-set.js\");\nconst { RemoteDataPattern } = __webpack_require__(/*! dcp/dcp-client/remote-data-pattern */ \"./src/dcp-client/remote-data-pattern.js\");\nconst { ResultHandle } = __webpack_require__(/*! ./result-handle */ \"./src/dcp-client/job/result-handle.js\");\nconst { SlicePaymentOffer } = __webpack_require__(/*! ./slice-payment-offer */ \"./src/dcp-client/job/slice-payment-offer.js\");\nconst { addSlices } = __webpack_require__(/*! ./upload-slices */ \"./src/dcp-client/job/upload-slices.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst computeGroups = __webpack_require__(/*! dcp/dcp-client/compute-groups */ \"./src/dcp-client/compute-groups/index.js\");\nconst schedulerConstants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { DEFAULT_REQUIREMENTS, removeBadRequirements } = __webpack_require__(/*! dcp/common/job-requirements-defaults */ \"./src/common/job-requirements-defaults.js\");\nconst { sliceStatus, jobValueKind } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { jobStatus } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst bankUtil = __webpack_require__(/*! dcp/dcp-client/bank-util */ \"./src/dcp-client/bank-util.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp-client');\nconst kvin = __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\");\nconst { setTimeout } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nlet tunedKvin;\n\nconst log = (...args) => {\n if (debugging('job')) {\n console.debug('dcp-client:job', ...args);\n }\n};\n\nconst ON_BROWSER = DCP_ENV.isBrowserPlatform;\nconst sideloaderModuleIdentifier = 'sideloader-v1';\n\n/** @typedef {import('dcp/dcp-client/range-object').RangeLike} RangeLike */\n/** @typedef {import('dcp/dcp-client/wallet/keystore').Keystore} Keystore */\n/** @typedef {import('dcp/utils/jsdoc-types').SupervisorOptions} SupervisorOptions */\n/** @typedef {import('dcp/common/dcp-events').PropagatingEventEmitter} PropagatingEventEmitter */\n/** @typedef {import('scheduler-v4/libexec/job-submit/operations/submit').MarshaledInputData} MarshaledInputData */\n\n/**\n * Ensure input data is an appropriate format\n * @param {RangeObject | DistributionRange | RemoteDataSet | Array | RangeLike | Iterable}\n * inputData - A URI-shaped string, a [Multi]RangeObject-constructing value, or\n * an array of slice data\n * @return {RangeObject | DistributionRange | RemoteDataSet | Array | RangeLike | Iterable}\n * The coerced input in an appropriate format ([Multi]RangeObject,\n * DistributionRange, RemoteDataSet, or array)\n */\n const wrangleData = (inputData) => {\n\n if (RangeObject.isRangelike(inputData)) { return inputData }\n if (RangeObject.isRangeObject(inputData)) { return inputData }\n if (DistributionRange.isDistribution(inputData)) { return inputData }\n if (inputData instanceof SparseRangeObject) { return inputData }\n if (inputData instanceof MultiRangeObject) { return inputData }\n if (MultiRangeObject.isProtoMultiRangelike(inputData)) { return new MultiRangeObject(inputData) }\n if (RangeObject.isProtoRangelike(inputData)) { return new RangeObject(inputData) }\n if (DistributionRange.isProtoDistribution(inputData)) { return new DistributionRange(inputData) }\n if (RemoteDataSet.isRemoteDataSet(inputData)) { return inputData }\n if (RemoteDataPattern.isRemoteDataPattern(inputData)) { return inputData }\n\n return Array.isArray(inputData) ? inputData : [inputData];\n};\n\n/**\n * @classdesc The Compute API's Job Handle (see {@link https://docs.dcp.dev/specs/compute-api.html#job-handles|Compute API spec})\n * Job handles are objects which correspond to jobs. \n * They are created by some exports of the compute module, such as {@link module:dcp/compute.do|compute.do} and {@link module:dcp/compute.for|compute.for}.\n * @extends {PropagatingEventEmitter}\n * @hideconstructor\n * @access public\n */\nclass Job extends PropagatingEventEmitter\n{\n /* Private class fields */\n #jobDeployRef; /* Event loop reference to the job deploy cyle. Different from Job.ref() which will reference the job. */\n #jobRef; /* Keep the job alive. */\n\n /**\n * Fired when the job is accepted by the scheduler on deploy.\n * \n * @event Job#accepted\n * @access public\n * @type {object}\n *//**\n * Fired when the job is cancelled.\n * \n * @event Job#cancel\n * @access public\n *//**\n * Fired when a result is returned.\n * \n * @event Job#result\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {string} task ID of the task (slice) the result came from\n * @property {number} sort The index of the slice\n * @property {object} result\n * @property {string} result.request\n * @property {*} result.result The value returned from the work function\n *//**\n * Fired when the result handle is modified, either when a new `result` event is fired or when the results are populated with `results.fetch()`\n * \n * @event Job#resultsUpdated\n * @access public\n *//**\n * Fired when the job has been completed.\n * \n * @event Job#complete\n * @access public\n * @type {ResultHandle}\n *//**\n * Fired when the job's status changes.\n * \n * @event Job#status\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} total Total number of slices in the job\n * @property {number} distributed Number of slices that have been distributed\n * @property {number} computed Number of slices that have completed execution (returned a result)\n * @property {string} runStatus Current runStatus of the job\n *//**\n * Fired when a slice throws an error.\n * \n * @event Job#error\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} sliceIndex Index of the slice that threw the error\n * @property {string} message The error message\n * @property {string} stack The error stacktrace\n * @property {string} name The error type name\n *//**\n * Fired when a slice uses one of the console log functions.\n * \n * @event Job#console\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} sliceIndex The index of the slice that produced this event\n * @property {string} level The log level, one of `debug`, `info`, `log`, `warn`, or `error`\n * @property {string} message The console log message\n *//**\n * Fired when a slice is stopped for not calling progress. Contains information about how long the slice ran for, and about the last reported progress calls.\n * \n * @event Job#noProgress\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} sliceIndex The index of the slice that failed due to no progress\n * @property {number} timestamp How long the slice ran before failing\n * @property {object} progressReports\n * @property {object} progressReports.last The last progress report received from the worker\n * @property {number} progressReports.last.timestamp Time since the start of the slice\n * @property {number} progressReports.last.progress Progress value reported\n * @property {*} progressReports.last.value The last value that was passed to the progress function\n * @property {number} progressReports.last.throttledReports Number of calls to progress that were throttled since the last report\n * @property {object} progressReports.lastUpdate The last determinate (update to the progress param) progress report received from the worker\n * @property {number} progressReports.lastUpdate.timestamp\n * @property {number} progressReports.lastUpdate.progress\n * @property {*} progressReports.lastUpdate.value\n * @property {number} progressReports.lastUpdate.throttledReports\n *//**\n @todo: is this in the spec? is there a no progress data? should there be?\n * Identical to `noProgress`, except that it also contains the data that the slice was executed with.\n * \n * @event Job#noProgressData\n * @access public\n * @type {object}\n * @property {*} data The data that the slice was executed with\n *//**\n * Fired when the job is paused due to running out of funds. The job can be resumed by escrowing more funds then resuming the job.\n * @todo: is this a thing, should it be a thing (the payload)\n * Event payload is the estimated funds required to complete the job\n * \n * @event Job#nofunds\n * @access public\n * @type {BigNumber}\n *//**\n * Fired when the job cannot be deployed due to no bank account / not enough balance to deploy the job\n * \n * @event Job#ENOFUNDS\n * @access public\n *//**\n * Fired when the job is cancelled due to the work function not calling the `progress` method frequently enough.\n * \n * @event Job#ENOPROGRESS\n * @access public\n *//**\n * The job was cancelled because scheduler has determined that individual tasks in this job exceed the maximum allowable execution time.\n * \n * @event Job#ESLICETOOSLOW\n * @access public\n *//**\n * Fired when the job is cancelled because too many work functions are terminating with uncaught exceptions.\n * \n * @event Job#ETOOMANYERRORS\n * @access public\n */\n\n /**\n * @form1 new Job('application_worker_address'[, data[, arguments]])\n * @form2a new Job('worker source'[, data[, arguments]])\n * @form2b new Job(worker_function[, data[, arguments]])\n */\n constructor ()\n {\n super('Job');\n\n /* A default reference the Job is set. */\n this.ref();\n\n if (typeof arguments[0] === 'function')\n arguments[0] = arguments[0].toString();\n\n if (typeof arguments[0] === 'string')\n {\n const { encodeDataURI } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n this.workFunctionURI = encodeDataURI(arguments[0], 'application/javascript');\n } \n else if (DcpURL.isURL(arguments[0]))\n this.workFunctionURI = arguments[0].href;\n\n this.jobInputData = wrangleData(arguments[1] || []);\n this.jobArguments = wrangleData(arguments[2] || []);\n \n log('num wrangledInputData:', this.jobInputData.length);\n log('num wrangledArguments:', this.jobArguments.length);\n\n this.initEventSystems();\n\n /**\n * An object describing the cost the user believes each the average slice will incur, in terms of CPU/GPU and I/O.\n * If defined, this object is used to provide initial scheduling hints and to calculate escrow amounts.\n *\n * @type {object}\n * @access public\n */\n this.initialSliceProfile = undefined;\n \n // The max value that the client is willing to spend to deploy\n // (list on the scheduler, doesn't include compute payment)\n // maxDeployPayment is the max the user is willing to pay to DCP (as a\n // Hold), in addition to the per-slice offer and associated scrape.\n // Currently calculated as `deployCost = costPerKB *\n // (JSON.stringify(job).length / 1024) // 1e-9 per kb`\n // @todo: figure this out / er nov 2018\n this.maxDeployPayment = 1;\n\n /**\n * An object describing the requirements that workers must have to be eligible for this job. The default values are set in the job-submitter, and only the client specified\n * requirements are sent over the wire. See {@link https://docs.dcp.dev/specs/compute-api.html#requirements-objects|Requirements Objects}.\n *\n * @type {object}\n * @access public\n */\n this.requirements = {};\n\n /**\n * A place to store public-facing attributes of the job. Anything stored on this object will be available inside the work \n * function (see {@link module:dcp/compute~sandboxEnv.work}). The properties documented here may be used by workers to display what jobs are currently being \n * worked on.\n * @access public\n * @property {string} name Public-facing name of this job.\n * @property {string} description Public-facing description for this job.\n * @property {string} link Public-facing link to external resource about this job.\n */\n this.public = {\n name: null,\n description: null,\n link: null,\n };\n \n /**\n * A cryptographic receipt indicating deployment of the job on the scheduler\n * @type {object}\n * @access public\n */\n this.receipt = null;\n \n /**\n * a SliceProfile object which contains the average costs for the slices which have been computed to date.\n * Until the first result is returned, this property is undefined.\n * @type {object}\n * @access public\n */\n this.meanSliceProfile = null;\n \n /**\n * A number (can be null, undefined, or infinity) describing the estimationSlicesRemaining in the jpd (dcp-2593)\n * @type {number}\n * @access public\n */\n this.estimationSlices = undefined;\n /**\n * When true, allows a job in estimation to have requestTask return multiple estimation slices.\n * This flag applies independent of infinite estimation, viz., this.estimationSlices === null .\n * @type {boolean}\n * @access public\n */\n this.greedyEstimation = false;\n /**\n * tunable parameters per job\n * @access public\n * @param {object} tuning \n * @param {string} tuning.kvin Encode the TypedArray into a string, trying multiple methods to determine optimum \n * size/performance. The this.tune variable affects the behavior of this code this:\n * @param {boolean} speed If true, only do naive encoding: floats get represented as byte-per-digit strings\n * @param {boolean} size If true, try the naive, ab8, and ab16 encodings; pick the smallest\n * If both are false try the naive encoding if under typedArrayPackThreshold and use if smaller\n * than ab8; otherwise, use ab8\n */\n this.tuning = {\n kvin: {\n size: false,\n speed: false,\n },\n }\n /* For API interface to end-users only */\n Object.defineProperty(this, 'id', {\n get: () => this.address,\n set: (id) => { this.address = id }\n });\n\n this.uuid = uuidv4(); /** @see {@link https://kingsds.atlassian.net/browse/DCP-1475?atlOrigin=eyJpIjoiNzg3NmEzOWE0OWI4NGZkNmI5NjU0MWNmZGY2OTYzZDUiLCJwIjoiaiJ9|Jira Issue} */\n this.dependencies = []; /* dependencies of the work function */\n this.requirePath = []; /* require path for dependencies */\n this.modulePath = []; /* path to module that invoked .exec() for job */\n this.connected = false; /* true when exec or resume called */\n this.results = new ResultHandle(this); /* result handle */\n this.collateResults = true; /* option to receive results as they are computed & ensure all are received on finish */\n this.contextId = null; /* optional string which is used to indicate to caching mechanisms different keystores with same name */ \n this.force100pctCPUDensity = false; /* tell scheduler to assume this job uses 100% cpu density */\n this.workerConsole = false; /* tell workers to log more information about failures in the evaluator this job causes */\n this.address = null; /* job address, created by scheduler during exec call. */\n this.paymentAccountKeystore = null; /* keystore for payment for job to come from */\n this.status = { /* job status details */\n runStatus: null,\n total: null,\n distributed: null,\n computed: null\n };\n\n // service locations\n this.scheduler = dcpConfig.scheduler.services.jobSubmit.location;\n this.bank = dcpConfig.bank.services.bankTeller.location;\n\n // Compute groups. Add to public compute group by default\n this.computeGroups = [ Object.assign({}, schedulerConstants.computeGroups.public) ];\n\n // A Worker object that describes the local worker if one is used for a job.\n this.localWorker = {\n /** @type {object[]} */\n origins: [],\n originManager: {\n /** \n * @param {string} origin\n * @param {string | null} purpose \n * @param {string | null} key\n */\n add: (origin, purpose, key = null) => {\n this.localWorker.origins.push({\n origin: origin,\n purpose: purpose,\n key: key,\n });\n },\n },\n };\n\n // Update the ready state as we go through job deployment\n this.readyState = sliceStatus.new;\n const that = this;\n this.readyStateChange = function job$$readyStateChange (readyState)\n {\n that.readyState = readyState;\n that.emit('readyStateChange', that.readyState);\n }\n }\n\n /**\n * Initialize the various event systems the job handle requires. These include:\n * - an internal event emitter (this.ee)\n * - an event emitter for any events emitted on `work.emit` within work functions (this.work)\n * - an event subscriber to subscribe (to receive) events from the scheduler (this.eventSubscriber)\n */\n initEventSystems ()\n {\n // Handle the various event-related things required in the constructor\n\n // Internal event emitter for events within job handle\n this.ee = new EventEmitter('Job Internal');\n\n /**\n * An EventEmitter for custom events dispatched by the work function.\n * @type {module:dcp/dcp-events.EventEmitter}\n * @access public\n * @example\n * // in work function\n * work.emit('myEventName', 1, [2], \"three\");\n * // client-side\n * job.work.on('myEventName', (num, arr, string) => { });\n */\n this.work = new EventEmitter('job.work');\n this.listenForCustomEvents = false;\n\n // Initialize the eventSubscriber so each job has unique eventSubscriber\n this.eventSubscriber = new ((__webpack_require__(/*! dcp/events/event-subscriber */ \"./src/events/event-subscriber.js\").EventSubscriber))(this);\n\n // Some events from the event subscriber can't be emitted immediately upon receipt without having \n // weird/wrong output due to things like serialization. We allow interceptors in the event subscriber\n // to handle this.\n const that = this\n var lastConsoleEv;\n var sameCounter = 1;\n const parseConsole = function deserializeConsoleMessage(ev) {\n if (tunedKvin)\n ev.message = tunedKvin.unmarshal(ev.message);\n else \n ev.message = kvin.unmarshal(ev.message);\n \n if (lastConsoleEv && ev.message[0] === lastConsoleEv.message[0] && ev.sliceNumber === lastConsoleEv.sliceNumber && ev.level === lastConsoleEv.level)\n ev.same = ++sameCounter;\n else\n sameCounter = 1;\n lastConsoleEv = ev;\n \n /* if we have the same message being logged (same sliceNumber, message, log level), the console event object will have the sole property same, nothing else */\n if (ev.same > 1)\n that.emit('console', { same: ev.same });\n else\n {\n delete ev.same;\n that.emit('console', ev);\n }\n }\n\n this.eventIntercepts = {\n result: (ev) => this.handleResult(ev),\n status: (ev) => this.handleStatus(ev),\n cancel: (ev) => this.ee.emit('stopped', ev),\n custom: (ev) => this.work.emit(ev.customEvent, ev),\n console: parseConsole,\n };\n \n this.eventTypes = (__webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\").eventTypes);\n\n this.work.on('newListener', (evt) => {\n this.listenForCustomEvents = true;\n });\n this.desiredEvents = []\n this.on('newListener', (evt) => {\n if (!this.connected && evt !== 'newListener')\n this.desiredEvents.push(evt);\n if (evt === 'cancel')\n this.listeningForCancel = true;\n });\n }\n \n /** \n * Cancel the job\n * @access public\n * @param {string} reason If provided, will be sent to client\n */\n async cancel (reason = undefined)\n {\n const response = await this.useDeployConnection('cancelJob', {\n job: this.address,\n owner: this.paymentAccountKeystore.address,\n reason,\n }, this.paymentAccountKeystore);\n\n return response.payload;\n }\n\n /** \n * Resume this job\n * @access public\n */\n async resume ()\n {\n // Careful -- this.schedulerConnection does not exist.\n const response = await this.schedulerConnection.request('resumeJob', {\n job: this.address,\n owner: this.paymentAccountKeystore.address,\n }, this.paymentAccountKeystore);\n\n return response.payload;\n }\n\n /**\n * Helper function for retrieving info about the job. The job must have already been deployed.\n * An alias for {@link module:dcp/compute.getJobInfo}.\n * @access public\n */\n getJobInfo ()\n {\n return (__webpack_require__(/*! ../compute */ \"./src/dcp-client/compute.js\").compute.getJobInfo)(this.address);\n }\n \n /**\n * Helper function for retrieving info about the job's slices. The job must have already been deployed.\n * An alias for {@link module:dcp/compute.getSliceInfo}.\n * @access public\n */\n getSliceInfo ()\n {\n return (__webpack_require__(/*! ../compute */ \"./src/dcp-client/compute.js\").compute.getSliceInfo)(this.address);\n }\n \n /** Escrow additional funds for this job\n * @access public\n * @param {number|BigNumber} fundsRequired - A number or BigNumber instance representing the funds to escrow for this job\n */\n async escrow (fundsRequired)\n {\n if ((typeof fundsRequired !== 'number' && !BigNumber.isBigNumber(fundsRequired))\n || fundsRequired <= 0 || !Number.isFinite(fundsRequired) || Number.isNaN(fundsRequired))\n throw new Error(`Job.escrow: fundsRequired must be a number greater than zero. (not ${fundsRequired})`);\n\n const bankConnection = new protocolV4.Connection(dcpConfig.bank.services.bankTeller);\n\n /*\n * escrow has been broken for an unknown amount of time. `feeStructureId` is not defined anywhere in the job class, and hasn't\n * for a period of time. When fixed, `this[INTERNAL_SYMBOL].payloadDetails.feeStructureId` will likely become just `this.feeStructureId`,\n * but it's being left alone until someone spends the time to fix escrow. / rs Jul 2022\n */\n const response = await bankConnection.request('embiggenFeeStructure', {\n feeStructureAddress: this[INTERNAL_SYMBOL].payloadDetails.feeStructureId,\n additionalEscrow: new BigNumber(fundsRequired),\n fromAddress: this.paymentAccountKeystore.address,\n }, this.paymentAccountKeystore);\n\n bankConnection.close();\n const escrowReceipt = response.payload;\n return escrowReceipt;\n }\n\n /**\n * create bundles for local dependencies\n */\n _pack ()\n {\n var retval = (__webpack_require__(/*! ./node-modules */ \"./src/dcp-client/job/node-modules.js\").createModuleBundle)(this.dependencies);\n return retval;\n }\n\n /** \n * Collect all of the dependencies together, throw them into a BravoJS\n * module which sideloads them as a side effect of declaration, and transmit\n * them to the package manager. Then we return the package descriptor object,\n * which is guaranteed to have only one file in it.\n *\n * @returns {object} with properties name and files[0]\n */\n async _publishLocalModules()\n {\n const dcpPublish = __webpack_require__(/*! dcp/common/dcp-publish */ \"./src/common/dcp-publish.js\");\n \n const { tempFile, hash, unresolved } = await this._pack();\n\n if (!tempFile) {\n return { unresolved };\n }\n\n const sideloaderFilename = tempFile.filename;\n const pkg = {\n name: `dcp-pkg-v1-localhost-${hash.toString('hex')}`,\n version: '1.0.0',\n files: {\n [sideloaderFilename]: `${sideloaderModuleIdentifier}.js`,\n },\n }\n\n await dcpPublish.publish(pkg);\n tempFile.remove();\n\n return { pkg, unresolved };\n }\n \n /**\n * This function specifies a module dependency (when the argument is a string)\n * or a list of dependencies (when the argument is an array) of the work\n * function. This function can be invoked multiple times before deployment.\n * @param {string | string[]} modulePaths - A string or array describing one\n * or more dependencies of the job.\n * @access public\n */\n requires (modulePaths)\n {\n if (typeof modulePaths !== 'string' && (!Array.isArray(modulePaths) || modulePaths.some((modulePath) => typeof modulePath !== 'string')))\n throw new TypeError('The argument to dependencies is not a string or an array of strings');\n else if (modulePaths.length === 0)\n throw new RangeError('The argument to dependencies cannot be an empty string or array');\n else if (Array.isArray(modulePaths) && modulePaths.some((modulePath) => modulePath.length === 0))\n throw new RangeError('The argument to dependencies cannot be an array containing an empty string');\n\n if (!Array.isArray(modulePaths))\n modulePaths = [modulePaths];\n\n for (const modulePath of modulePaths)\n {\n if (modulePath[0] !== '.' && modulePath.indexOf('/') !== -1)\n {\n const modulePrefixRegEx = /^(.*)\\/.*?$/;\n const [, modulePrefix] = modulePath.match(modulePrefixRegEx);\n if (modulePrefix && this.requirePath.indexOf(modulePrefix) === -1)\n this.requirePath.push(modulePrefix);\n }\n this.dependencies.push(modulePath);\n }\n }\n \n /** Set the account upon which funds will be drawn to pay for the job.\n * @param {module:dcp/wallet.BankAccountKeystore} keystore A keystore that representa a bank account.\n * @access public\n */\n setPaymentAccountKeystore (keystore)\n {\n if (this.address)\n {\n if (!keystore.address.eq(this.paymentAccountKeystore))\n {\n let message = 'Cannot change payment account after job has been deployed';\n this.emit('EPERM', message);\n throw new Error(`EPERM: ${message}`);\n }\n }\n \n if (!(keystore instanceof wallet.Keystore))\n throw new Error('Argument must be an instance of Keystore');\n this.paymentAccountKeystore = keystore;\n }\n \n /** Set the slice payment offer. This is equivalent to the first argument to exec.\n * @param {number} slicePaymentOffer - The number of DCC the user is willing to pay to compute one slice of this job\n */\n setSlicePaymentOffer (slicePaymentOffer)\n {\n this.slicePaymentOffer = new SlicePaymentOffer(slicePaymentOffer);\n }\n \n \n /**\n * @param {URL|DcpURL} locationUrl - A URL object\n * @param {object} postParams - An object with any parameters that a user would like to be passed to a \n * remote result location. This object is capable of carry API keys for S3, \n * DropBox, etc. These parameters are passed as parameters in an \n * application/x-www-form-urlencoded request.\n */\n setResultStorage (locationUrl, postParams)\n {\n if (locationUrl instanceof URL || locationUrl instanceof DcpURL)\n this.resultStorageDetails = locationUrl;\n else\n this.resultStorageDetails = new URL(locationUrl);\n\n // resultStorageParams contains any post params required for off-prem storage\n if (typeof postParams !== 'undefined' && typeof postParams === 'object' )\n this.resultStorageParams = postParams;\n else\n throw new Error('postParams must be an object');\n\n // Some type of object here\n this.resultStorageType = 'pattern';\n }\n \n /**\n * This function is identical to exec, except that the job is executed locally\n * in the client.\n * @async\n * @param {number} cores - the number of local cores in which to execute the job.\n * @param {...any} args - The remaining arguments are identical to the arguments of exec\n * @return {Promise<ResultHandle>} - resolves with the results of the job, rejects on an error\n * @access public\n */\n async localExec (cores = 1, ...args)\n {\n this.inLocalExec = true;\n this.estimationSlices = 0;\n this.greedyEstimation = false;\n this.isCI = false;\n this.localExecAllowedOrigins = {\n any: [],\n fetchData: [],\n fetchWorkFunctions: [],\n fetchArguments: [],\n sendResults: [],\n };\n\n let worker;\n this.on('accepted', () => {\n // Fill in localexec-specific requirements to config\n /** @type {SupervisorOptions} */\n const config = {\n trustComputeGroupOrigins: false,\n allowOrigins: this.localExecAllowedOrigins,\n paymentAddress: this.identityKeystore.address,\n allowConsoleAccess: true,\n jobAddresses: [this.address],\n cores: { cpu: cores, gpu: undefined },\n sandboxOptions: {\n ignoreNoProgress: true,\n SandboxConstructor: (DCP_ENV.platform === 'nodejs' && (__webpack_require__(/*! ../worker/evaluators */ \"./src/dcp-client/worker/evaluators/index.js\").nodeEvaluatorFactory)()),\n },\n };\n\n if (this.localWorker?.origins?.length)\n {\n for (let i = 0; i < this.localWorker.origins.length; i++) \n {\n const originToAdd = this.localWorker.origins[i];\n\n /* Push origins to localExecAllowedOrigins. If a null purpose is provided, \n push it onto the 'any' array of this.localExecAllowedOrigins */\n this.localExecAllowedOrigins[(originToAdd.purpose === null ? 'any' : originToAdd.purpose)].push(originToAdd.origin);\n }\n }\n\n config.cores = Worker.defaultCores(config);\n worker = new Worker(this.identityKeystore, config);\n\n // @ts-ignore\n this.localWorker = worker;\n\n worker.on('error', (error) => {\n console.error('LocalExec Error:', error);\n });\n\n worker.on('warning', (warning) => {\n console.warn('LocalExec Warning:', warning);\n });\n\n worker.start().catch((e) => {\n console.error('Failed to start worker for localExec:');\n console.error(e.message);\n });\n });\n\n if (DCP_ENV.platform === 'nodejs')\n {\n // Determine type of input data\n // marshalInputData actually returns the union of dataRange, dataValues, dataPattern, sliceCount,\n // but there isn't a simple way to express that in Javascript.\n const { dataRange, dataValues, dataPattern, sliceCount } = marshalInputData(this.jobInputData);\n \n const inputSetFiles = [];\n \n let inputSetURIs = [];\n let dataSet;\n \n if (dataValues)\n {\n for (let i = 0; i < dataValues.length; i++)\n {\n if (!(dataValues[i] instanceof URL))\n {\n let marshaledInputValue = kvinMarshal(dataValues[i]);\n let inputDataFile = createTempFile('dcp-localExec-sliceData-XXXXXXXXX', 'kvin');\n inputDataFile.writeSync(JSON.stringify(marshaledInputValue));\n inputSetFiles.push(inputDataFile);\n inputSetURIs.push(new URL('file://' + inputDataFile.filename));\n }\n else\n inputSetURIs.push(dataValues[i]);\n }\n dataSet = new RemoteDataSet(inputSetURIs);\n if (dataSet.length > 0)\n this.marshaledDataValues = dataSet;\n }\n if (dataRange)\n {\n inputSetFiles.push(createTempFile('dcp-localExec-sliceData-XXXXXXXXX', 'json'));\n let marshaledInputSet = JSON.stringify(dataRange);\n inputSetFiles[0].writeSync(marshaledInputSet)\n inputSetURIs.push(new URL('file://' + inputSetFiles[0].filename));\n dataSet = new RemoteDataSet(inputSetURIs);\n this.marshaledDataRange = dataSet;\n this.rangeLength = dataRange.length;\n }\n \n // For allowed origins of the localexec worker. Only allow the origins (files in this case) in this list.\n for (let i = 0; i < inputSetFiles.length; i++)\n {\n // Coerce into a URI to handle OS specific path separators.\n const inputFileURI = new URL(`file://${inputSetFiles[i].filename}`);\n this.localExecAllowedOrigins['fetchData'].push(inputFileURI.pathname);\n }\n \n // Save work function to disk if work function starts with data (ie not remote)\n if (this.workFunctionURI.startsWith('data:'))\n {\n const workFunctionFile = createTempFile('dcp-localExec-workFunction-XXXXXXXXX', 'js');\n // data:URI - dont care about origins\n const workFunction = await fetchURI(this.workFunctionURI, false);\n workFunctionFile.writeSync(workFunction);\n \n const workFunctionFileURL = new URL('file://' + workFunctionFile);\n this.workFunctionURI = workFunctionFileURL.href;\n this.localExecAllowedOrigins['fetchWorkFunctions'].push(workFunctionFileURL.pathname);\n }\n \n let encodedJobArgumentUris = [];\n if (this.jobArguments)\n {\n if (this.jobArguments instanceof RemoteDataPattern) /* Not supported */\n throw new DCPError('Cannot use RemoteDataPattern as work function arguments', 'EBADARG')\n if (this.jobArguments instanceof RemoteDataSet) /* Entire set is RemoteDataSet */\n {\n this.jobArguments.forEach((e) =>\n {\n encodedJobArgumentUris.push(new URL(e));\n });\n }\n else\n {\n for (let i = 0; i < this.jobArguments.length; i++)\n {\n if (this.jobArguments[i] instanceof URL)\n encodedJobArgumentUris.push(this.jobArguments[i]);\n else\n {\n if (this.jobArguments[i] instanceof RemoteDataSet) /* Member of set is RemoteDataSet */\n {\n this.jobArguments[i].forEach((e) =>\n {\n encodedJobArgumentUris.push(new URL(e));\n });\n }\n else /* Actual Value */\n {\n const localArgFile = createTempFile(`dcp-localExec-argument-${i}-XXXXXXXXX`, 'kvin');\n const localArgFileURI = new URL(`file://${localArgFile.filename}`);\n\n localArgFile.writeSync(JSON.stringify(kvinMarshal(this.jobArguments[i])));\n this.localExecAllowedOrigins['fetchArguments'].push(localArgFileURI.pathname);\n encodedJobArgumentUris.push(localArgFileURI);\n }\n }\n } \n }\n }\n this.marshaledArguments = kvinMarshal(encodedJobArgumentUris);\n }\n \n return this.exec(...args).finally(() => {\n if (worker) {\n const timerHandle = setTimeout(() => {\n // stop the worker\n worker.stop(true);\n }, 3000);\n // Allow workers and localExec to exit.\n if (timerHandle.unref)\n timerHandle.unref();\n }\n });\n }\n\n /**\n * Deploys the job to the scheduler.\n * @param {number | object} [slicePaymentOffer=compute.marketValue] - Amount\n * in DCC that the user is willing to pay per slice.\n * @param {Keystore} [paymentAccountKeystore=wallet.get] - An instance of the\n * Wallet API Keystore that's used as the payment account when executing the\n * job.\n * @param {object} [initialSliceProfile] - An object describing the cost the\n * user believes the average slice will incur.\n * @access public\n * @emits Job#accepted\n */\n async exec (slicePaymentOffer = (__webpack_require__(/*! ../compute */ \"./src/dcp-client/compute.js\").compute.marketValue), paymentAccountKeystore, initialSliceProfile)\n {\n\n if (this.connected)\n throw new Error(`Exec called twice on the same job handle (${this.address})`);\n \n if (this.estimationSlices === Infinity)\n this.estimationSlices = null;\n else if (this.estimationSlices < 0)\n throw new Error('Incorrect value for estimationSlices; it can be an integer or Infinity!');\n \n if (this.tuning.kvin.speed || this.tuning.kvin.size)\n {\n tunedKvin = new kvin.KVIN();\n tunedKvin.tune = 'size';\n if(this.tuning.kvin.speed)\n tunedKvin.tune = 'speed';\n // If both size and speed are true, kvin will optimize based on speed\n if(this.tuning.kvin.speed && this.tuning.kvin.size)\n console.log('Slices and arguments are being uploaded with speed optimization.');\n }\n \n /* slight optimization to ensure we don't send requirements that will be ignored in the job submitter. Make a copy of the client specified requirements for this so that we dont magically override something they manually set */\n const _DEFAULT_REQUIREMENTS = JSON.parse(JSON.stringify(DEFAULT_REQUIREMENTS));\n removeBadRequirements(this.requirements, _DEFAULT_REQUIREMENTS);\n \n this.readyStateChange('exec');\n if ((typeof slicePaymentOffer === 'number') || (typeof slicePaymentOffer === 'object')\n || ((this.slicePaymentOffer === null || this.slicePaymentOffer === undefined) && typeof slicePaymentOffer === 'function'))\n this.setSlicePaymentOffer(slicePaymentOffer);\n if (typeof initialSliceProfile !== 'undefined')\n this.initialSliceProfile = initialSliceProfile;\n \n if (typeof paymentAccountKeystore !== 'undefined')\n {\n /** XXX @todo deprecate use of ethereum wallet objects */\n if (typeof paymentAccountKeystore === 'object' && paymentAccountKeystore.hasOwnProperty('_privKey'))\n {\n console.warn('* deprecated API * - job.exec invoked with ethereum wallet object as paymentAccountKeystore') /* /wg oct 2019 */\n paymentAccountKeystore = paymentAccountKeystore._privKey\n }\n /** XXX @todo deprecate use of private keys */\n if (wallet.isPrivateKey(paymentAccountKeystore))\n {\n console.warn('* deprecated API * - job.exec invoked with private key as paymentAccountKeystore') /* /wg dec 2019 */\n paymentAccountKeystore = await new wallet.Keystore(paymentAccountKeystore, '');\n }\n\n this.setPaymentAccountKeystore(paymentAccountKeystore)\n }\n \n if (this.paymentAccountKeystore)\n // Throws if they fail to unlock, we allow this since the keystore was set programmatically. \n await this.paymentAccountKeystore.unlock(undefined, parseFloat(dcpConfig.job.maxDeployTime));\n else\n {\n // If not set programmatically, we keep trying to get an unlocked keystore ... forever.\n let locked = true;\n let safety = 0; // no while loop shall go unguarded\n let ks;\n do\n {\n ks = null;\n // custom message for the browser modal to denote the purpose of keystore submission\n // let msg = `This application is requesting a keystore file to execute ${this.public.description || this.public.name || 'this job'}.<br><br> Please upload the corresponding keystore file. If you upload a keystore file which has been encrypted with a passphrase, the application will not be able to use it until it prompts for a passphrase and you enter it.`;\n let msg = `Upload a DCP bank account keystore<br> containing compute credits.`;\n try\n {\n ks = await wallet.get({ contextId: this.contextId, jobName: this.public.name, msg});\n }\n catch (e)\n {\n if (e.code !== ClientModal.CancelErrorCode) throw e;\n };\n if (ks)\n {\n try\n {\n await ks.unlock(undefined, parseFloat(dcpConfig.job.maxDeployTime));\n locked = false;\n }\n catch (e)\n {\n // prompt user again if user enters password incorrectly, exit modal otherwise\n if (e.code !== wallet.unlockFailErrorCode) throw e;\n }\n }\n if (safety++ > 1000) throw new Error('EINFINITY: job.exec tried wallet.get more than 1000 times.')\n } while (locked);\n this.setPaymentAccountKeystore(ks)\n }\n \n // We either have a valid keystore + password or we have rejected by this point.\n if (!this.slicePaymentOffer)\n throw new Error('A payment profile must be assigned before executing the job');\n else\n this.feeStructure = this.slicePaymentOffer.toFeeStructure(this.jobInputData.length);\n\n if (!this.address)\n {\n try\n {\n this.readyStateChange('init');\n await this.deployJob();\n const listenersPromise = this.addInitialEvents();\n const computeGroupsPromise = this.joinComputeGroups();\n let uploadSlicePromise;\n // if job data is by value then upload data to the scheduler in a staggered fashion\n if (Array.isArray(this.dataValues) && !this.marshaledDataValues)\n {\n this.readyStateChange('uploading');\n uploadSlicePromise = addSlices(this.dataValues, this.address, tunedKvin)\n .then(() => {\n debugging('slice-upload') && console.info(`970: slice data uploaded, closing job...`);\n return this.close();\n });\n }\n \n // await all promises for operations that can be done after the job is deployed\n await Promise.all([listenersPromise, computeGroupsPromise, uploadSlicePromise]);\n \n this.readyStateChange('deployed');\n this.emit('accepted', this);\n this.#jobDeployRef = clearInterval(this.#jobDeployRef); /* Remove the reference to job deploy. */\n }\n catch (error)\n {\n if (ON_BROWSER)\n await ClientModal.alert(error, { title: 'Failed to deploy job!' });\n throw error;\n }\n }\n else\n {\n // reconnecting to an old job\n await this.addInitialEvents();\n this.readyStateChange('reconnected');\n }\n\n this.connected = true;\n\n return new Promise((resolve, reject) => {\n this.startEventBandaidWatchdog();\n \n const onComplete = () => {\n this.stopEventBandaidWatchdog();\n resolve(this.results); \n };\n\n const onCancel = (event) => {\n /**\n * FIXME(DCP-1150): Remove this since normal cancel event is noisy\n * enough to not need stopped event too.\n */\n if (ON_BROWSER && !this.listeningForCancel)\n ClientModal.alert('More details in console...', { title: 'Job Canceled' });\n this.emit('cancel', event);\n\n let errorMsg = event.reason;\n if (event.error && event.error !== 'undefined')\n errorMsg = errorMsg +`\\n Recent error message: ${event.error.message}`\n\n this.stopEventBandaidWatchdog();\n\n reject(new DCPError(errorMsg, event.code));\n };\n\n this.ee.once('stopped', async (stopEvent) => {\n // There is a chance the result submitter will emit finished > 1 time. Only handle it once.\n if (this.receivedStop)\n return;\n this.receivedStop = true;\n this.emit('stopped', stopEvent.runStatus);\n switch (stopEvent.runStatus) {\n case jobStatus.finished:\n if (this.collateResults)\n {\n let report = await this.getJobInfo();\n let allSliceNumbers = Array.from(Array(report.totalSlices)).map((e,i)=>i+1);\n let remainSliceNumbers = allSliceNumbers.filter((e) => !this.results.isAvailable(e));\n\n if (remainSliceNumbers.length)\n {\n const promises = remainSliceNumbers.map(sliceNumber => this.results.fetch([sliceNumber], true));\n await Promise.all(promises);\n }\n }\n\n this.emit('complete', this.results);\n onComplete();\n break;\n case jobStatus.cancelled:\n onCancel(stopEvent);\n break;\n default:\n /**\n * Asserting that we should never be able to reach here. The only\n * scheduler events that should trigger the Job's 'stopped' event\n * are jobStatus.cancelled, jobStatus.finished, and sliceStatus.paused.\n */\n reject(new Error(`Unknown event \"${stopEvent.runStatus}\" caused the job to be stopped.`));\n break;\n }\n });\n\n }).finally(() => {\n const handleErr = (e) => {\n console.error('Error while closing job connection:');\n console.error(e);\n }\n\n // Create an async IIFE to not block the promise chain\n (async () => {\n // delay to let last few events to be received\n await new Promise((resolve) => setTimeout(resolve, 1000));\n \n // close all of the connections so that we don't cause node processes to hang.\n this.closeDeployConnection();\n this.eventSubscriber.close();\n await computeGroups.closeServiceConnection().catch((err) => {\n console.error('Warning: could not close compute groups service connection', err);\n });\n })();\n\n /* Remove the reference to the Job. */\n this.unref();\n });\n }\n\n /**\n * Start the event-bandaid watchdog. Every [configurable] seconds, fetch\n * the job report and do appropriate things if the job is stopped.\n */\n startEventBandaidWatchdog()\n {\n if (this.eventBandaidHandle)\n this.stopEventBandaidWatchdog();\n\n this.eventBandaidHandle = setInterval(() => {\n this.eventBandaid();\n }, 60 * 1000);\n\n // if we are in a nodelike environment with unreffable timers, unref the\n // timer so it doesn't hold the process open. \n // \n // (n.b. while I understand that we _want_ the process to stay open while\n // the job lives, but this is Not The Way; the whole eventBandaid is a\n // temporary hack which will go away in time ~ER 2022-11-02)\n if (this.eventBandaidHandle.unref)\n this.eventBandaidHandle.unref();\n }\n\n /**\n * Stop and clean up the even-bandaid watchdog.\n */\n stopEventBandaidWatchdog()\n {\n if (this.eventBandaidHandle)\n clearInterval(this.eventBandaidHandle);\n\n this.eventBandaidHandle = false;\n }\n\n /**\n * Called on an interval, started by .exec and stopped by\n * onComplete/onCancel. Fetches the jobReport and verifies the job is\n * not in a terminal state; if the job is finished or cancelled, we\n * emit a synthetic internal stop event to trigger regular cleanup\n * (result retrieval, etc) and finalize the job handle.\n */\n eventBandaid()\n {\n this.getJobInfo()\n .then(jobInfo => {\n if ([jobStatus.finished, jobStatus.cancelled].includes(jobInfo.status))\n this.ee.emit('stopped', { runStatus: jobInfo.status });\n })\n .catch(error => {\n\n });\n }\n \n /**\n * job.addListeners(): Private function used to set up event listeners to the scheduler\n * before deploying the job.\n */\n async addInitialEvents ()\n {\n this.readyStateChange('listeners');\n\n // This is important: We need to flush the task queue before adding listeners\n // because we queue pending listeners by listening to the newListener event (in the constructor).\n // If we don't flush here, then the newListener events may fire after this function has run,\n // and the events won't be properly set up.\n await new Promise(resolve => setTimeout(resolve, 0));\n\n // @todo: Listen for an estimated cost, probably emit an \"estimated\" event when it comes in?\n // also @todo: Do the estimation task(s) on the scheduler and send an \"estimated\" event\n\n // Always listen to the stop event. It will resolve the work function promise, so is always needed.\n this.on('stop', (ev) => {this.ee.emit('stopped', ev)});\n\n // Connect listeners that were set up before exec\n if (this.desiredEvents.includes('result'))\n this.listeningForResults = true;\n await this.subscribeNewEvents(this.desiredEvents);\n\n // Connect listeners that are set up after exec\n this.on('newListener', (evt) => {\n if (evt === 'newListener' || this.desiredEvents.includes(evt))\n return;\n this.subscribeNewEvents([evt]);\n });\n \n // automatically add a listener for results if collateResults is on\n if (this.collateResults && !this.listeningForResults)\n this.on('result', () => {});\n\n debugging('dcp-client') && console.debug('subscribedEvents', this.desiredEvents);\n\n // If we have listeners for job.work, subscribe to custom events\n if (this.listenForCustomEvents)\n await this.subscribeCustomEvents();\n // Connect work event listeners that are set up after exec\n else\n this.work.on('newListener', () => this.subscribeCustomEvents());\n }\n \n /**\n * Subscribes to either reliable events or optional events. It is assumed that\n * any call to this function will include only new events.\n * @param {string[]} events \n */\n async subscribeNewEvents (events)\n {\n const reliableEvents = [];\n const optionalEvents = [];\n for (let eventName of events)\n {\n eventName = eventName.toLowerCase();\n if (this.eventTypes[eventName] && this.eventTypes[eventName].reliable)\n reliableEvents.push(eventName);\n else if (this.eventTypes[eventName] && !this.eventTypes[eventName].reliable)\n optionalEvents.push(eventName);\n else\n debugging('dcp-client') && console.debug(`Job handler has listener ${eventName} which isn't an event-router event.`);\n }\n if (debugging('dcp-client'))\n {\n console.debug('reliableEvents', reliableEvents);\n console.debug('optionalEvents', optionalEvents);\n }\n await this.eventSubscriber.subscribeManyEvents(reliableEvents, optionalEvents, { filter: { job: this.address } });\n }\n \n /**\n * Establishes listeners for worker events when requested by the client\n */\n async subscribeCustomEvents ()\n {\n if (!this.listeningForCustomEvents)\n await this.eventSubscriber.subscribeManyEvents([], ['custom'], { filter: { job: this.address } });\n this.listeningForCustomEvents = true\n }\n \n async joinComputeGroups ()\n {\n // localExec jobs are not entered in any compute group.\n if (!this.inLocalExec && this.computeGroups && this.computeGroups.length > 0)\n {\n this.readyStateChange('compute-groups');\n computeGroups.addRef(); // Just in case we're doing a Promise.all on multiple execs.\n\n // Add this job to its currently-defined compute groups (as well as public group, if included)\n let success;\n \n if (!Array.isArray(this.computeGroups)) \n throw new DCPError('Compute groups must be wrapped in an Array', 'DCPL-1101');\n\n for (let i = 0; i < this.computeGroups.length; i++)\n {\n let value = this.computeGroups[i];\n \n if (typeof value !== 'object')\n throw new DCPError(`This compute group: ${value[i]} must be an object`, 'DCPL-1102');\n \n if (value.joinKey && typeof value.joinKey !== 'string' && !(value.joinKey instanceof String))\n throw new DCPError(`This join key: ${value.joinKey} must be a string or a string literal`, 'DCPL-1103');\n else if (value.joinKeystore && !(value.joinKeystore instanceof wallet.Keystore))\n throw new DCPError(`This join Keystore: ${value.joinKeystore} must be an instance of wallet.Keystore`, 'DCPL-1104');\n else if (!value.joinKey && !value.joinKeystore)\n throw new DCPError('Compute group must contain a joinKey or a joinKeystore', 'DCPL-1105');\n }\n \n try\n {\n const cgPayload = await computeGroups.addJobToGroups(this.address, this.computeGroups);\n success = true; // To support older version of CG service where addJobToGroups had void/undefined return.\n if (cgPayload) success = cgPayload.success;\n debugging('dcp-client') && console.debug('job/index: addJobToGroups cgPayload:', cgPayload ? cgPayload : 'cgPayload is not defined; probably from legacy CG service.');\n }\n catch (e)\n {\n debugging('dcp-client') && console.debug('job/index: addJobToGroups threw exception:', e);\n success = false;\n }\n\n computeGroups.closeServiceConnection().catch((err) => {\n console.error('Warning: could not close compute groups service connection', err)\n });\n\n /* Could not put the job in any compute group, even though the user wanted it to run. Cancel the job. */\n if (!success)\n {\n await this.cancel('compute-groups::Unable to join any compute groups');\n throw new DCPError(`Access Denied::Failed to add job ${this.address} to any of the desired compute groups on ${this.scheduler}`, 'DCPL-1100');\n }\n }\n }\n \n /**\n * Takes result events as input, stores the result and fires off\n * events on the job handle as required. (result, duplicate-result)\n *\n * @param {object} ev - the event recieved from protocol.listen('/results/0xThisGenAdr')\n */\n async handleResult (ev)\n {\n if (this.results === null)\n // This should never happen - the onResult event should only be established/called\n // in addListeners which should also initialize the internal results array\n throw new Error('Job.onResult was invoked before initializing internal results');\n \n const { result: _result, time } = ev.result;\n debugging('dcp-client') && console.debug('handleResult', _result);\n /**\n * There is currently not way to access OAM instance on the job level.\n * This case is similar to the fetch case in result-handle.js.\n * The value to fetchURI is either a dataURI, RDS that user defined or scheduler disk location.\n */\n let result = await fetchURI(_result, false);\n\n if (this.results.isAvailable(ev.sliceNumber))\n {\n const changed = JSON.stringify(this.results[ev.sliceNumber]) !== JSON.stringify(result);\n this.emit('duplicate-result', { sliceNumber: ev.sliceNumber, changed });\n }\n \n this.results.newResult(result, ev.sliceNumber - 1);\n }\n \n /**\n * Receives status events from the scheduler, updates the local status object\n * and emits a 'status' event\n *\n * @param {object} ev - the status event received from\n * protocol.listen('/status/0xThisGenAdr')\n * @param {boolean} emitStatus - value indicating whether or not the status\n * event should be emitted\n */\n handleStatus ({ runStatus, total, distributed, computed }, emitStatus = true)\n {\n Object.assign(this.status, {\n runStatus,\n total,\n distributed,\n computed,\n });\n\n if (emitStatus)\n this.emit('status', { ...this.status, job: this.address });\n }\n \n /**\n * Sends a request to the scheduler to deploy the job.\n */\n async deployJob ()\n {\n var moduleDependencies; \n \n this.#jobDeployRef = setInterval(() => undefined, 271828); /* Create a reference to the deploy cycle. */\n \n /* Send sideloader bundle to the package server */\n if (DCP_ENV.platform === 'nodejs' && this.dependencies.length)\n {\n try\n {\n let { pkg, unresolved } = await this._publishLocalModules();\n\n moduleDependencies = unresolved;\n if (pkg)\n moduleDependencies.push(pkg.name + '/' + sideloaderModuleIdentifier); \n }\n catch(error)\n {\n throw new DCPError(`Error trying to communicate with package manager server: ${error}`);\n }\n }\n else\n moduleDependencies = this.dependencies;\n \n this.readyStateChange('preauth');\n\n const adhocId = this.uuid.slice(this.uuid.length - 6, this.uuid.length);\n const schedId = await dcpConfig.scheduler.identity;\n // The following check is needed for when using dcp-rtlink and loading the config through source, instead of using the dcp-client bundle\n let schedIdAddress = schedId;\n if(schedId.address)\n schedIdAddress = schedId.address;\n this.identityKeystore = await wallet.getId();\n const preauthToken = await bankUtil.preAuthorizePayment(schedIdAddress, this.maxDeployPayment, this.paymentAccountKeystore);\n const { dataRange, dataValues, dataPattern, sliceCount } = marshalInputData(this.jobInputData);\n if(dataValues)\n this.dataValues = dataValues;\n\n this.readyStateChange('deploying');\n\n /* Payload format is documented in scheduler-v4/libexec/job-submit/operations/submit.js */\n const submitPayload = {\n owner: this.identityKeystore.address,\n paymentAccount: this.paymentAccountKeystore.address,\n priority: 0, // @nyi\n\n workFunctionURI: this.workFunctionURI,\n uuid: this.uuid,\n mvMultSlicePayment: Number(this.feeStructure.marketValue) || 0, // @todo: improve feeStructure internals to better reflect v4\n absoluteSlicePayment: Number(this.feeStructure.maxPerRequest) || 0,\n requirePath: this.requirePath,\n dependencies: moduleDependencies,\n requirements: this.requirements, /* capex */\n localExec: this.inLocalExec,\n force100pctCPUDensity: this.force100pctCPUDensity,\n estimationSlices: this.estimationSlices,\n greedyEstimation: this.greedyEstimation,\n workerConsole: this.workerConsole,\n isCI: this.isCI,\n\n description: this.public.description || 'Discreetly making the world smarter',\n name: this.public.name || 'Ad-Hoc Job' + adhocId,\n link: this.public.link || '',\n\n preauthToken, // XXXwg/er @todo: validate this after fleshing out the stub(s)\n\n resultStorageType: this.resultStorageType, // @todo: implement other result types\n resultStorageDetails: this.resultStorageDetails, // Content depends on resultStorageType\n resultStorageParams: this.resultStorageParams, // Post params for off-prem storage\n dataRange,\n dataPattern,\n sliceCount,\n marshaledDataValues: this.marshaledDataValues,\n rangeLength: this.rangeLength\n };\n\n // Check if dataRange or dataPattern input is already marshaled\n if (this.marshaledDataRange)\n submitPayload.dataRange = this.marshaledDataRange;\n\n /* Determine composition of argument set and build payload */\n if (this.marshaledArguments)\n submitPayload.marshaledArguments = this.marshaledArguments;\n else if (this.jobArguments)\n {\n if (this.jobArguments instanceof RemoteDataPattern) /* Not supported */\n throw new DCPError('Cannot use RemoteDataPattern as work function arguments', 'EBADARG')\n if (this.jobArguments instanceof RemoteDataSet) /* Entire set is RemoteDataSet */\n this.jobArguments = this.jobArguments.map((e) => new URL(e));\n\n submitPayload.marshaledArguments = kvin.marshal(encodeJobValueList(this.jobArguments, 'jobArguments'));\n }\n\n // XXXpfr Excellent tracing.\n if (debugging('dcp-client'))\n {\n const { dumpObject } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n dumpObject(submitPayload, 'Submit: Job Index: submitPayload', 256);\n }\n debugging('dcp-client') && console.debug('submitPayload.marshaledArguments', JSON.stringify(submitPayload.marshaledArguments).length, JSON.stringify(submitPayload).length);\n\n // Deploy the job! If we get an error, try again a few times until threshold of errors is reached, then actually throw it\n let deployed\n let deployAttempts = 0;\n while (deployAttempts++ < (dcpConfig.job.deployAttempts || 10))\n {\n try\n {\n deployed = await this.useDeployConnection('submit', submitPayload, this.identityKeystore);\n /* if connection is lost during useDeployConnection, error message that gets returned is not thrown, so throw here so that we can reattempt deployment */\n if (deployed instanceof Error && deployed.code === 'DCPC-1013'){\n throw deployed;\n }\n break;\n }\n catch (e)\n {\n if (deployAttempts < 10)\n debugging('dcp-client') && console.debug('Error when trying to deploy job, trying again', e);\n else\n throw e;\n }\n }\n\n if (!deployed.success)\n {\n // close all of the connections so that we don't cause node processes to hang.\n const handleErr = (e) => {\n console.error('Error while closing job connection:');\n console.error(e);\n };\n \n this.closeDeployConnection();\n this.eventSubscriber.close();\n computeGroups.closeServiceConnection().catch(handleErr);\n this.#jobDeployRef = clearInterval(this.#jobDeployRef);\n \n // Yes, it is possible for deployed or deployed.payload to be undefined.\n if (deployed && deployed.payload)\n {\n if (deployed.payload.code === 'ENOTFOUND')\n throw new DCPError(`Failed to submit job; account ${submitPayload.paymentAccount} does not have sufficient balance (${deployed.payload.info.deployCost}⊇ needed to deploy this job)`, deployed.payload); \n throw new DCPError('Failed to submit job:' + deployed.payload?.message, deployed.payload.code);\n }\n throw new DCPError(`Failed to submit job (no payload; ${deployed || typeof deployed})`);\n }\n\n debugging('dcp-client') && console.debug('After Deploy', JSON.stringify(deployed));\n\n this.address = deployed.payload.job;\n this.deployCost = deployed.payload.deployCost;\n\n if (!this.status)\n this.status = {\n runStatus: null,\n total: 0,\n computed: 0,\n distributed: 0,\n };\n \n this.status.runStatus = deployed.payload.status;\n this.status.total = deployed.payload.lastSliceNumber;\n this.running = true;\n }\n \n /** close an open job to indicate we are done adding data so it is okay to finish\n * the job at the appropriate time\n */\n close ()\n {\n return this.useDeployConnection('closeJob', {\n job: this.address,\n });\n }\n \n /** Use the connection to job submit service. Will open a new connection if one does not exist,\n * and close the connection if it is idle for more than 10 seconds (tuneable).\n */\n useDeployConnection(operation, payload)\n {\n if (!this.useDeployConnection.uses)\n this.useDeployConnection.uses = 0;\n this.useDeployConnection.uses++;\n if (!this.deployConnection)\n {\n debugging('deploy-connection') && console.info(`1453: making a new deployConnection...`)\n this.deployConnection = new protocolV4.Connection(dcpConfig.scheduler.services.jobSubmit); \n this.deployConnection.on('end', () => { this.deployConnection = null; });\n }\n if (this.deployConnectionTimeout)\n clearTimeout(this.deployConnectionTimeout);\n\n debugging('deploy-connection') && console.info(`1460: sending ${operation} request...`);\n const deployPromise = this.deployConnection.request(operation, payload);\n \n deployPromise.finally(() => {\n this.useDeployConnection.uses--;\n\n debugging('deploy-connection') && console.info(`1462: deployConnection done ${operation} request, connection uses is ${this.useDeployConnection.uses}`)\n\n this.deployConnectionTimeout = setTimeout(() => {\n if (this.useDeployConnection.uses === 0 && this.deployConnection)\n {\n debugging('deploy-connection') && console.info(`1469: done with deployConn, closing...`);\n // if we're done w/ the connection, then remove its cleanup\n // function, close it, and clean up manually to make room for a\n // new conn when needed (ie, don't leave the closing conn where\n // someone could accidentally pick it up)\n this.deployConnection.removeAllListeners('end');\n this.deployConnection.close();\n this.deployConnection = null;\n }\n }, (dcpConfig.job.deployCloseTimeout || 10 * 1000));\n if (this.deployConnectionTimeout.unref)\n this.deployConnectionTimeout.unref();\n }); \n \n return deployPromise;\n }\n \n /**\n * Close the connection to the job submit (if it exists), and clear the close timeout (if needed).\n */\n closeDeployConnection()\n {\n if (this.deployConnection)\n this.deployConnection.close();\n if (this.deployConnectionTimeout)\n clearTimeout(this.deployConnectionTimeout);\n }\n\n unref()\n {\n if (this.#jobRef)\n this.#jobRef = clearInterval(this.#jobRef);\n if (this.#jobDeployRef)\n this.#jobDeployRef = clearInterval(this.#jobDeployRef);\n }\n\n ref()\n {\n this.#jobRef = setInterval(() => undefined, 1618033988);\n }\n}\n\n/** \n * Encode a value list for transmission to the job-submit daemon. This could be either job arguments\n * or the slice input set, if the input set was an Array-like object.\n *\n * @param {ArrayLike} valueList - The list of values to encode\n * @returns {Array<URL|{string: string, method:string, MIMEType: string }>} - URL or object with 3 properties:\n * string - serialization of value\n * method - how value was serialized (e.g. 'kvin', 'json')\n * MIMEType - MIME information corresponding to method (e.g. 'application/kvin', 'application/json')\n */\nfunction encodeJobValueList(valueList, valueKind)\n{\n var list = [];\n\n /*\n * We need to handle several different styles of datasets, and create the output array accordingly.\n *\n * 1. instance of RemoteDataSet or RemoteDataPattern => apply new URI(?)\n * 2. an Array-like objects => apply serializeJobValue\n * Values sent to the scheduler in payload are either URL or the result of calling serializeJobValue.\n */\n\n if (typeof valueList === 'undefined' || (typeof valueList === 'object' && valueList.length === 0))\n return list; /* empty set */\n\n if (typeof valueList !== 'object' || !valueList.hasOwnProperty('length'))\n throw new Error('value list must be an Array-like object');\n\n for (let i = 0; i < valueList.length; i++) /* Set is composed of values from potentially varying sources */\n {\n let value = valueList[i];\n if (value instanceof RemoteDataSet)\n value.forEach((el) => list.push(new URL(el)));\n else if (value instanceof RemoteDataPattern)\n {\n if (valueKind === jobValueKind.jobArguments)\n throw new DCPError('Cannot use RemoteDataPattern as work function arguments', 'EBADARG');\n else\n {\n let uri = valueList['pattern'];\n for (let sliceNum = 1; sliceNum <= valueList['sliceCount']; sliceNum++)\n list.push(new URL(uri.replace('{slice}', sliceNum)))\n }\n }\n else if (value instanceof RemoteValue)\n list.push(serializeJobValue(value.href));\n else\n list.push(serializeJobValue(value));\n }\n\n return list;\n} \n\n/**\n * Depending on the shape of the job's data, resolve it into a RangeObject, a\n * Pattern, or a values array, and return it in the appropriate property.\n *\n * @param {any} data Job's input data\n * @return {MarshaledInputData} An object with one of the following properties set:\n * - dataValues: job input is an array of arbitrary values \n * - dataPattern: job input is a URI pattern \n * - dataRange: job input is a RangeObject (and/or friends)\n */\nfunction marshalInputData (data)\n{\n if (!(data instanceof Object || data instanceof SuperRangeObject))\n throw new TypeError(`Invalid job data type: ${typeof data}`);\n\n /**\n * @type {MarshaledInputData}\n */\n const marshalledInputData = {};\n\n // TODO(wesgarland): Make this more robust.\n if (data instanceof SuperRangeObject ||\n (data.hasOwnProperty('ranges') && data.ranges instanceof MultiRangeObject) ||\n (data.hasOwnProperty('start') && data.hasOwnProperty('end')))\n marshalledInputData.dataRange = data;\n else if (Array.isArray(data))\n marshalledInputData.dataValues = data;\n else if (data instanceof URL || data instanceof DcpURL)\n marshalledInputData.dataPattern = String(data);\n else if(data instanceof RemoteDataSet)\n marshalledInputData.dataValues = data.map(e => new URL(e));\n else if(data instanceof RemoteDataPattern)\n {\n marshalledInputData.dataPattern = data['pattern'];\n marshalledInputData.sliceCount = data['sliceCount'];\n }\n\n debugging('job') && console.debug('marshalledInputData:', marshalledInputData);\n return marshalledInputData;\n}\n\n/**\n * marshal the value using kvin or instance of the kvin (tunedKvin)\n * tunedKvin is defined if job.tuning.kvin is specified.\n *\n * @param {any} value \n * @return {object} A marshaled object\n * \n */\nfunction kvinMarshal (value) {\n if (tunedKvin)\n return tunedKvin.marshal(value);\n\n return kvin.marshal(value);\n}\n\n\n\nexports.Job = Job;\nexports.SlicePaymentOffer = SlicePaymentOffer;\nexports.ResultHandle = ResultHandle;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/job/index.js?");
4349
+ eval("/**\n * @file job/index.js\n * @author Eddie Roosenmaallen, eddie@kingsds.network\n * Matthew Palma, mpalma@kingsds.network\n * Wes Garland, wes@kingsds.network\n * Paul, paul@kingsds.network\n * Ryan Saweczko, ryansaweczko@kingsds.network\n * @date November 2018\n * November 2018\n * February 2022\n * May 2022\n * Jun 2022\n *\n * This module implements the Compute API's Job Handle\n *\n */\n// @ts-check\n\n\nconst { BigNumber } = __webpack_require__(/*! bignumber.js */ \"./node_modules/bignumber.js/bignumber.js\");\nconst { v4: uuidv4 } = __webpack_require__(/*! uuid */ \"./node_modules/uuid/dist/esm-browser/index.js\");\nconst { EventEmitter, PropagatingEventEmitter } = __webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\");\nconst { RangeObject, MultiRangeObject, DistributionRange, SuperRangeObject, SparseRangeObject } = __webpack_require__(/*! dcp/dcp-client/range-object */ \"./src/dcp-client/range-object.js\");\nconst { fetchURI, encodeDataURI, createTempFile } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { serializeJobValue, RemoteValue } = __webpack_require__(/*! dcp/dcp-client/job-values */ \"./src/dcp-client/job-values.js\");\nconst wallet = __webpack_require__(/*! dcp/dcp-client/wallet */ \"./src/dcp-client/wallet/index.js\");\nconst protocolV4 = __webpack_require__(/*! dcp/protocol-v4 */ \"./src/protocol-v4/index.js\");\nconst { DcpURL } = __webpack_require__(/*! dcp/common/dcp-url */ \"./src/common/dcp-url.js\");\nconst ClientModal = __webpack_require__(/*! dcp/dcp-client/client-modal */ \"./src/dcp-client/client-modal/index.js\");\nconst { Worker } = __webpack_require__(/*! dcp/dcp-client/worker */ \"./src/dcp-client/worker/index.js\");\nconst { RemoteDataSet } = __webpack_require__(/*! dcp/dcp-client/remote-data-set */ \"./src/dcp-client/remote-data-set.js\");\nconst { RemoteDataPattern } = __webpack_require__(/*! dcp/dcp-client/remote-data-pattern */ \"./src/dcp-client/remote-data-pattern.js\");\nconst { ResultHandle } = __webpack_require__(/*! ./result-handle */ \"./src/dcp-client/job/result-handle.js\");\nconst { SlicePaymentOffer } = __webpack_require__(/*! ./slice-payment-offer */ \"./src/dcp-client/job/slice-payment-offer.js\");\nconst { addSlices } = __webpack_require__(/*! ./upload-slices */ \"./src/dcp-client/job/upload-slices.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst computeGroups = __webpack_require__(/*! dcp/dcp-client/compute-groups */ \"./src/dcp-client/compute-groups/index.js\");\nconst schedulerConstants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { DEFAULT_REQUIREMENTS, removeBadRequirements } = __webpack_require__(/*! dcp/common/job-requirements-defaults */ \"./src/common/job-requirements-defaults.js\");\nconst { sliceStatus, jobValueKind } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst { jobStatus } = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\nconst bankUtil = __webpack_require__(/*! dcp/dcp-client/bank-util */ \"./src/dcp-client/bank-util.js\");\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('dcp-client');\nconst kvin = __webpack_require__(/*! kvin */ \"./node_modules/kvin/kvin.js\");\nconst { setTimeout } = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nlet tunedKvin;\n\nconst log = (...args) => {\n if (debugging('job')) {\n console.debug('dcp-client:job', ...args);\n }\n};\n\nconst ON_BROWSER = DCP_ENV.isBrowserPlatform;\nconst sideloaderModuleIdentifier = 'sideloader-v1';\n\n/** @typedef {import('dcp/dcp-client/range-object').RangeLike} RangeLike */\n/** @typedef {import('dcp/dcp-client/wallet/keystore').Keystore} Keystore */\n/** @typedef {import('dcp/utils/jsdoc-types').SupervisorOptions} SupervisorOptions */\n/** @typedef {import('dcp/common/dcp-events').PropagatingEventEmitter} PropagatingEventEmitter */\n/** @typedef {import('scheduler-v4/libexec/job-submit/operations/submit').MarshaledInputData} MarshaledInputData */\n\n/**\n * Ensure input data is an appropriate format\n * @param {RangeObject | DistributionRange | RemoteDataSet | Array | RangeLike | Iterable}\n * inputData - A URI-shaped string, a [Multi]RangeObject-constructing value, or\n * an array of slice data\n * @return {RangeObject | DistributionRange | RemoteDataSet | Array | RangeLike | Iterable}\n * The coerced input in an appropriate format ([Multi]RangeObject,\n * DistributionRange, RemoteDataSet, or array)\n */\n const wrangleData = (inputData) => {\n\n if (RangeObject.isRangelike(inputData)) { return inputData }\n if (RangeObject.isRangeObject(inputData)) { return inputData }\n if (DistributionRange.isDistribution(inputData)) { return inputData }\n if (inputData instanceof SparseRangeObject) { return inputData }\n if (inputData instanceof MultiRangeObject) { return inputData }\n if (MultiRangeObject.isProtoMultiRangelike(inputData)) { return new MultiRangeObject(inputData) }\n if (RangeObject.isProtoRangelike(inputData)) { return new RangeObject(inputData) }\n if (DistributionRange.isProtoDistribution(inputData)) { return new DistributionRange(inputData) }\n if (RemoteDataSet.isRemoteDataSet(inputData)) { return inputData }\n if (RemoteDataPattern.isRemoteDataPattern(inputData)) { return inputData }\n\n return Array.isArray(inputData) ? inputData : [inputData];\n};\n\n/**\n * @classdesc The Compute API's Job Handle (see {@link https://docs.dcp.dev/specs/compute-api.html#job-handles|Compute API spec})\n * Job handles are objects which correspond to jobs. \n * They are created by some exports of the compute module, such as {@link module:dcp/compute.do|compute.do} and {@link module:dcp/compute.for|compute.for}.\n * @extends {PropagatingEventEmitter}\n * @hideconstructor\n * @access public\n */\nclass Job extends PropagatingEventEmitter\n{\n /* Private class fields */\n #jobDeployRef; /* Event loop reference to the job deploy cyle. Different from Job.ref() which will reference the job. */\n #jobRef; /* Keep the job alive. */\n\n /**\n * Fired when the job is accepted by the scheduler on deploy.\n * \n * @event Job#accepted\n * @access public\n * @type {object}\n *//**\n * Fired when the job is cancelled.\n * \n * @event Job#cancel\n * @access public\n *//**\n * Fired when a result is returned.\n * \n * @event Job#result\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {string} task ID of the task (slice) the result came from\n * @property {number} sort The index of the slice\n * @property {object} result\n * @property {string} result.request\n * @property {*} result.result The value returned from the work function\n *//**\n * Fired when the result handle is modified, either when a new `result` event is fired or when the results are populated with `results.fetch()`\n * \n * @event Job#resultsUpdated\n * @access public\n *//**\n * Fired when the job has been completed.\n * \n * @event Job#complete\n * @access public\n * @type {ResultHandle}\n *//**\n * Fired when the job's status changes.\n * \n * @event Job#status\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} total Total number of slices in the job\n * @property {number} distributed Number of slices that have been distributed\n * @property {number} computed Number of slices that have completed execution (returned a result)\n * @property {string} runStatus Current runStatus of the job\n *//**\n * Fired when a slice throws an error.\n * \n * @event Job#error\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} sliceIndex Index of the slice that threw the error\n * @property {string} message The error message\n * @property {string} stack The error stacktrace\n * @property {string} name The error type name\n *//**\n * Fired when a slice uses one of the console log functions.\n * \n * @event Job#console\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} sliceIndex The index of the slice that produced this event\n * @property {string} level The log level, one of `debug`, `info`, `log`, `warn`, or `error`\n * @property {string} message The console log message\n *//**\n * Fired when a slice is stopped for not calling progress. Contains information about how long the slice ran for, and about the last reported progress calls.\n * \n * @event Job#noProgress\n * @access public\n * @type {object}\n * @property {string} jobAddress Address of the job\n * @property {number} sliceIndex The index of the slice that failed due to no progress\n * @property {number} timestamp How long the slice ran before failing\n * @property {object} progressReports\n * @property {object} progressReports.last The last progress report received from the worker\n * @property {number} progressReports.last.timestamp Time since the start of the slice\n * @property {number} progressReports.last.progress Progress value reported\n * @property {*} progressReports.last.value The last value that was passed to the progress function\n * @property {number} progressReports.last.throttledReports Number of calls to progress that were throttled since the last report\n * @property {object} progressReports.lastUpdate The last determinate (update to the progress param) progress report received from the worker\n * @property {number} progressReports.lastUpdate.timestamp\n * @property {number} progressReports.lastUpdate.progress\n * @property {*} progressReports.lastUpdate.value\n * @property {number} progressReports.lastUpdate.throttledReports\n *//**\n @todo: is this in the spec? is there a no progress data? should there be?\n * Identical to `noProgress`, except that it also contains the data that the slice was executed with.\n * \n * @event Job#noProgressData\n * @access public\n * @type {object}\n * @property {*} data The data that the slice was executed with\n *//**\n * Fired when the job is paused due to running out of funds. The job can be resumed by escrowing more funds then resuming the job.\n * @todo: is this a thing, should it be a thing (the payload)\n * Event payload is the estimated funds required to complete the job\n * \n * @event Job#nofunds\n * @access public\n * @type {BigNumber}\n *//**\n * Fired when the job cannot be deployed due to no bank account / not enough balance to deploy the job\n * \n * @event Job#ENOFUNDS\n * @access public\n *//**\n * Fired when the job is cancelled due to the work function not calling the `progress` method frequently enough.\n * \n * @event Job#ENOPROGRESS\n * @access public\n *//**\n * The job was cancelled because scheduler has determined that individual tasks in this job exceed the maximum allowable execution time.\n * \n * @event Job#ESLICETOOSLOW\n * @access public\n *//**\n * Fired when the job is cancelled because too many work functions are terminating with uncaught exceptions.\n * \n * @event Job#ETOOMANYERRORS\n * @access public\n */\n\n /**\n * @form1 new Job('application_worker_address'[, data[, arguments]])\n * @form2a new Job('worker source'[, data[, arguments]])\n * @form2b new Job(worker_function[, data[, arguments]])\n */\n constructor ()\n {\n super('Job');\n\n /* A default reference the Job is set. */\n this.ref();\n\n if (typeof arguments[0] === 'function')\n arguments[0] = arguments[0].toString();\n\n if (typeof arguments[0] === 'string')\n {\n const { encodeDataURI } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n this.workFunctionURI = encodeDataURI(arguments[0], 'application/javascript');\n } \n else if (DcpURL.isURL(arguments[0]))\n this.workFunctionURI = arguments[0].href;\n\n this.jobInputData = wrangleData(arguments[1] || []);\n this.jobArguments = wrangleData(arguments[2] || []);\n \n log('num wrangledInputData:', this.jobInputData.length);\n log('num wrangledArguments:', this.jobArguments.length);\n\n this.initEventSystems();\n\n /**\n * An object describing the cost the user believes each the average slice will incur, in terms of CPU/GPU and I/O.\n * If defined, this object is used to provide initial scheduling hints and to calculate escrow amounts.\n *\n * @type {object}\n * @access public\n */\n this.initialSliceProfile = undefined;\n \n // The max value that the client is willing to spend to deploy\n // (list on the scheduler, doesn't include compute payment)\n // maxDeployPayment is the max the user is willing to pay to DCP (as a\n // Hold), in addition to the per-slice offer and associated scrape.\n // Currently calculated as `deployCost = costPerKB *\n // (JSON.stringify(job).length / 1024) // 1e-9 per kb`\n // @todo: figure this out / er nov 2018\n this.maxDeployPayment = 1;\n\n /**\n * An object describing the requirements that workers must have to be eligible for this job. The default values are set in the job-submitter, and only the client specified\n * requirements are sent over the wire. See {@link https://docs.dcp.dev/specs/compute-api.html#requirements-objects|Requirements Objects}.\n *\n * @type {object}\n * @access public\n */\n this.requirements = {};\n\n /**\n * A place to store public-facing attributes of the job. Anything stored on this object will be available inside the work \n * function (see {@link module:dcp/compute~sandboxEnv.work}). The properties documented here may be used by workers to display what jobs are currently being \n * worked on.\n * @access public\n * @property {string} name Public-facing name of this job.\n * @property {string} description Public-facing description for this job.\n * @property {string} link Public-facing link to external resource about this job.\n */\n this.public = {\n name: null,\n description: null,\n link: null,\n };\n \n /**\n * A cryptographic receipt indicating deployment of the job on the scheduler\n * @type {object}\n * @access public\n */\n this.receipt = null;\n \n /**\n * a SliceProfile object which contains the average costs for the slices which have been computed to date.\n * Until the first result is returned, this property is undefined.\n * @type {object}\n * @access public\n */\n this.meanSliceProfile = null;\n \n /**\n * A number (can be null, undefined, or infinity) describing the estimationSlicesRemaining in the jpd (dcp-2593)\n * @type {number}\n * @access public\n */\n this.estimationSlices = undefined;\n /**\n * When true, allows a job in estimation to have requestTask return multiple estimation slices.\n * This flag applies independent of infinite estimation, viz., this.estimationSlices === null .\n * @type {boolean}\n * @access public\n */\n this.greedyEstimation = false;\n /**\n * tunable parameters per job\n * @access public\n * @param {object} tuning \n * @param {string} tuning.kvin Encode the TypedArray into a string, trying multiple methods to determine optimum \n * size/performance. The this.tune variable affects the behavior of this code this:\n * @param {boolean} speed If true, only do naive encoding: floats get represented as byte-per-digit strings\n * @param {boolean} size If true, try the naive, ab8, and ab16 encodings; pick the smallest\n * If both are false try the naive encoding if under typedArrayPackThreshold and use if smaller\n * than ab8; otherwise, use ab8\n */\n this.tuning = {\n kvin: {\n size: false,\n speed: false,\n },\n }\n /* For API interface to end-users only */\n Object.defineProperty(this, 'id', {\n get: () => this.address,\n set: (id) => { this.address = id }\n });\n\n this.uuid = uuidv4(); /** @see {@link https://kingsds.atlassian.net/browse/DCP-1475?atlOrigin=eyJpIjoiNzg3NmEzOWE0OWI4NGZkNmI5NjU0MWNmZGY2OTYzZDUiLCJwIjoiaiJ9|Jira Issue} */\n this.dependencies = []; /* dependencies of the work function */\n this.requirePath = []; /* require path for dependencies */\n this.modulePath = []; /* path to module that invoked .exec() for job */\n this.connected = false; /* true when exec or resume called */\n this.results = new ResultHandle(this); /* result handle */\n this.collateResults = true; /* option to receive results as they are computed & ensure all are received on finish */\n this.contextId = null; /* optional string which is used to indicate to caching mechanisms different keystores with same name */ \n this.force100pctCPUDensity = false; /* tell scheduler to assume this job uses 100% cpu density */\n this.workerConsole = false; /* tell workers to log more information about failures in the evaluator this job causes */\n this.address = null; /* job address, created by scheduler during exec call. */\n this.paymentAccountKeystore = null; /* keystore for payment for job to come from */\n this.status = { /* job status details */\n runStatus: null,\n total: null,\n distributed: null,\n computed: null\n };\n\n // service locations\n this.scheduler = dcpConfig.scheduler.services.jobSubmit.location;\n this.bank = dcpConfig.bank.services.bankTeller.location;\n\n // Compute groups. Add to public compute group by default\n this.computeGroups = [ Object.assign({}, schedulerConstants.computeGroups.public) ];\n\n // A Worker object that describes the local worker if one is used for a job.\n this.localWorker = {\n /** @type {object[]} */\n origins: [],\n originManager: {\n /** \n * @param {string} origin\n * @param {string | null} purpose \n * @param {string | null} key\n */\n add: (origin, purpose, key = null) => {\n this.localWorker.origins.push({\n origin: origin,\n purpose: purpose,\n key: key,\n });\n },\n },\n };\n\n // Update the ready state as we go through job deployment\n this.readyState = sliceStatus.new;\n const that = this;\n this.readyStateChange = function job$$readyStateChange (readyState)\n {\n that.readyState = readyState;\n that.emit('readyStateChange', that.readyState);\n }\n }\n\n /**\n * Initialize the various event systems the job handle requires. These include:\n * - an internal event emitter (this.ee)\n * - an event emitter for any events emitted on `work.emit` within work functions (this.work)\n * - an event subscriber to subscribe (to receive) events from the scheduler (this.eventSubscriber)\n */\n initEventSystems ()\n {\n // Handle the various event-related things required in the constructor\n\n // Internal event emitter for events within job handle\n this.ee = new EventEmitter('Job Internal');\n\n /**\n * An EventEmitter for custom events dispatched by the work function.\n * @type {module:dcp/dcp-events.EventEmitter}\n * @access public\n * @example\n * // in work function\n * work.emit('myEventName', 1, [2], \"three\");\n * // client-side\n * job.work.on('myEventName', (num, arr, string) => { });\n */\n this.work = new EventEmitter('job.work');\n this.listenForCustomEvents = false;\n\n // Initialize the eventSubscriber so each job has unique eventSubscriber\n this.eventSubscriber = new ((__webpack_require__(/*! dcp/events/event-subscriber */ \"./src/events/event-subscriber.js\").EventSubscriber))(this);\n\n // Some events from the event subscriber can't be emitted immediately upon receipt without having \n // weird/wrong output due to things like serialization. We allow interceptors in the event subscriber\n // to handle this.\n const that = this\n var lastConsoleEv;\n var sameCounter = 1;\n const parseConsole = function deserializeConsoleMessage(ev) {\n if (tunedKvin)\n ev.message = tunedKvin.unmarshal(ev.message);\n else \n ev.message = kvin.unmarshal(ev.message);\n \n if (lastConsoleEv && ev.message[0] === lastConsoleEv.message[0] && ev.sliceNumber === lastConsoleEv.sliceNumber && ev.level === lastConsoleEv.level)\n ev.same = ++sameCounter;\n else\n sameCounter = 1;\n lastConsoleEv = ev;\n \n /* if we have the same message being logged (same sliceNumber, message, log level), the console event object will have the sole property same, nothing else */\n if (ev.same > 1)\n that.emit('console', { same: ev.same });\n else\n {\n delete ev.same;\n that.emit('console', ev);\n }\n }\n\n this.eventIntercepts = {\n result: (ev) => this.handleResult(ev),\n status: (ev) => this.handleStatus(ev),\n cancel: (ev) => this.ee.emit('stopped', ev),\n custom: (ev) => this.work.emit(ev.customEvent, ev),\n console: parseConsole,\n };\n \n this.eventTypes = (__webpack_require__(/*! dcp/common/dcp-events */ \"./src/common/dcp-events/index.js\").eventTypes);\n\n this.work.on('newListener', (evt) => {\n this.listenForCustomEvents = true;\n });\n this.desiredEvents = []\n this.on('newListener', (evt) => {\n if (!this.connected && evt !== 'newListener')\n this.desiredEvents.push(evt);\n if (evt === 'cancel')\n this.listeningForCancel = true;\n });\n }\n \n /** \n * Cancel the job\n * @access public\n * @param {string} reason If provided, will be sent to client\n */\n async cancel (reason = undefined)\n {\n const response = await this.useDeployConnection('cancelJob', {\n job: this.address,\n owner: this.paymentAccountKeystore.address,\n reason,\n }, this.paymentAccountKeystore);\n\n return response.payload;\n }\n\n /** \n * Resume this job\n * @access public\n */\n async resume ()\n {\n // Careful -- this.schedulerConnection does not exist.\n const response = await this.schedulerConnection.request('resumeJob', {\n job: this.address,\n owner: this.paymentAccountKeystore.address,\n }, this.paymentAccountKeystore);\n\n return response.payload;\n }\n\n /**\n * Helper function for retrieving info about the job. The job must have already been deployed.\n * An alias for {@link module:dcp/compute.getJobInfo}.\n * @access public\n */\n getJobInfo ()\n {\n return (__webpack_require__(/*! ../compute */ \"./src/dcp-client/compute.js\").compute.getJobInfo)(this.address);\n }\n \n /**\n * Helper function for retrieving info about the job's slices. The job must have already been deployed.\n * An alias for {@link module:dcp/compute.getSliceInfo}.\n * @access public\n */\n getSliceInfo ()\n {\n return (__webpack_require__(/*! ../compute */ \"./src/dcp-client/compute.js\").compute.getSliceInfo)(this.address);\n }\n \n /** Escrow additional funds for this job\n * @access public\n * @param {number|BigNumber} fundsRequired - A number or BigNumber instance representing the funds to escrow for this job\n */\n async escrow (fundsRequired)\n {\n if ((typeof fundsRequired !== 'number' && !BigNumber.isBigNumber(fundsRequired))\n || fundsRequired <= 0 || !Number.isFinite(fundsRequired) || Number.isNaN(fundsRequired))\n throw new Error(`Job.escrow: fundsRequired must be a number greater than zero. (not ${fundsRequired})`);\n\n const bankConnection = new protocolV4.Connection(dcpConfig.bank.services.bankTeller);\n\n /*\n * escrow has been broken for an unknown amount of time. `feeStructureId` is not defined anywhere in the job class, and hasn't\n * for a period of time. When fixed, `this[INTERNAL_SYMBOL].payloadDetails.feeStructureId` will likely become just `this.feeStructureId`,\n * but it's being left alone until someone spends the time to fix escrow. / rs Jul 2022\n */\n const response = await bankConnection.request('embiggenFeeStructure', {\n feeStructureAddress: this[INTERNAL_SYMBOL].payloadDetails.feeStructureId,\n additionalEscrow: new BigNumber(fundsRequired),\n fromAddress: this.paymentAccountKeystore.address,\n }, this.paymentAccountKeystore);\n\n bankConnection.close();\n const escrowReceipt = response.payload;\n return escrowReceipt;\n }\n\n /**\n * create bundles for local dependencies\n */\n _pack ()\n {\n var retval = (__webpack_require__(/*! ./node-modules */ \"./src/dcp-client/job/node-modules.js\").createModuleBundle)(this.dependencies);\n return retval;\n }\n\n /** \n * Collect all of the dependencies together, throw them into a BravoJS\n * module which sideloads them as a side effect of declaration, and transmit\n * them to the package manager. Then we return the package descriptor object,\n * which is guaranteed to have only one file in it.\n *\n * @returns {object} with properties name and files[0]\n */\n async _publishLocalModules()\n {\n const dcpPublish = __webpack_require__(/*! dcp/common/dcp-publish */ \"./src/common/dcp-publish.js\");\n \n const { tempFile, hash, unresolved } = await this._pack();\n\n if (!tempFile) {\n return { unresolved };\n }\n\n const sideloaderFilename = tempFile.filename;\n const pkg = {\n name: `dcp-pkg-v1-localhost-${hash.toString('hex')}`,\n version: '1.0.0',\n files: {\n [sideloaderFilename]: `${sideloaderModuleIdentifier}.js`,\n },\n }\n\n await dcpPublish.publish(pkg);\n tempFile.remove();\n\n return { pkg, unresolved };\n }\n \n /**\n * This function specifies a module dependency (when the argument is a string)\n * or a list of dependencies (when the argument is an array) of the work\n * function. This function can be invoked multiple times before deployment.\n * @param {string | string[]} modulePaths - A string or array describing one\n * or more dependencies of the job.\n * @access public\n */\n requires (modulePaths)\n {\n if (typeof modulePaths !== 'string' && (!Array.isArray(modulePaths) || modulePaths.some((modulePath) => typeof modulePath !== 'string')))\n throw new TypeError('The argument to dependencies is not a string or an array of strings');\n else if (modulePaths.length === 0)\n throw new RangeError('The argument to dependencies cannot be an empty string or array');\n else if (Array.isArray(modulePaths) && modulePaths.some((modulePath) => modulePath.length === 0))\n throw new RangeError('The argument to dependencies cannot be an array containing an empty string');\n\n if (!Array.isArray(modulePaths))\n modulePaths = [modulePaths];\n\n for (const modulePath of modulePaths)\n {\n if (modulePath[0] !== '.' && modulePath.indexOf('/') !== -1)\n {\n const modulePrefixRegEx = /^(.*)\\/.*?$/;\n const [, modulePrefix] = modulePath.match(modulePrefixRegEx);\n if (modulePrefix && this.requirePath.indexOf(modulePrefix) === -1)\n this.requirePath.push(modulePrefix);\n }\n this.dependencies.push(modulePath);\n }\n }\n \n /** Set the account upon which funds will be drawn to pay for the job.\n * @param {module:dcp/wallet.BankAccountKeystore} keystore A keystore that representa a bank account.\n * @access public\n */\n setPaymentAccountKeystore (keystore)\n {\n if (this.address)\n {\n if (!keystore.address.eq(this.paymentAccountKeystore))\n {\n let message = 'Cannot change payment account after job has been deployed';\n this.emit('EPERM', message);\n throw new Error(`EPERM: ${message}`);\n }\n }\n \n if (!(keystore instanceof wallet.Keystore))\n throw new Error('Argument must be an instance of Keystore');\n this.paymentAccountKeystore = keystore;\n }\n \n /** Set the slice payment offer. This is equivalent to the first argument to exec.\n * @param {number} slicePaymentOffer - The number of DCC the user is willing to pay to compute one slice of this job\n */\n setSlicePaymentOffer (slicePaymentOffer)\n {\n this.slicePaymentOffer = new SlicePaymentOffer(slicePaymentOffer);\n }\n \n \n /**\n * @param {URL|DcpURL} locationUrl - A URL object\n * @param {object} postParams - An object with any parameters that a user would like to be passed to a \n * remote result location. This object is capable of carry API keys for S3, \n * DropBox, etc. These parameters are passed as parameters in an \n * application/x-www-form-urlencoded request.\n */\n setResultStorage (locationUrl, postParams)\n {\n if (locationUrl instanceof URL || locationUrl instanceof DcpURL)\n this.resultStorageDetails = locationUrl;\n else\n this.resultStorageDetails = new URL(locationUrl);\n\n // resultStorageParams contains any post params required for off-prem storage\n if (typeof postParams !== 'undefined' && typeof postParams === 'object' )\n this.resultStorageParams = postParams;\n else\n throw new Error('postParams must be an object');\n\n // Some type of object here\n this.resultStorageType = 'pattern';\n }\n \n /**\n * This function is identical to exec, except that the job is executed locally\n * in the client.\n * @async\n * @param {number} cores - the number of local cores in which to execute the job.\n * @param {...any} args - The remaining arguments are identical to the arguments of exec\n * @return {Promise<ResultHandle>} - resolves with the results of the job, rejects on an error\n * @access public\n */\n async localExec (cores = 1, ...args)\n {\n this.inLocalExec = true;\n this.estimationSlices = 0;\n this.greedyEstimation = false;\n this.isCI = false;\n this.localExecAllowedOrigins = {\n any: [],\n fetchData: [],\n fetchWorkFunctions: [],\n fetchArguments: [],\n sendResults: [],\n };\n\n let worker;\n this.on('accepted', () => {\n // Fill in localexec-specific requirements to config\n /** @type {SupervisorOptions} */\n const config = {\n trustComputeGroupOrigins: false,\n allowOrigins: this.localExecAllowedOrigins,\n paymentAddress: this.identityKeystore.address,\n allowConsoleAccess: true,\n jobAddresses: [this.address],\n cores: { cpu: cores, gpu: undefined },\n sandboxOptions: {\n ignoreNoProgress: true,\n SandboxConstructor: (DCP_ENV.platform === 'nodejs' && (__webpack_require__(/*! ../worker/evaluators */ \"./src/dcp-client/worker/evaluators/index.js\").nodeEvaluatorFactory)()),\n },\n };\n\n if (this.localWorker?.origins?.length)\n {\n for (let i = 0; i < this.localWorker.origins.length; i++) \n {\n const originToAdd = this.localWorker.origins[i];\n\n /* Push origins to localExecAllowedOrigins. If a null purpose is provided, \n push it onto the 'any' array of this.localExecAllowedOrigins */\n this.localExecAllowedOrigins[(originToAdd.purpose === null ? 'any' : originToAdd.purpose)].push(originToAdd.origin);\n }\n }\n\n config.cores = Worker.defaultCores(config);\n worker = new Worker(this.identityKeystore, config);\n\n // @ts-ignore\n this.localWorker = worker;\n\n worker.on('error', (error) => {\n console.error('LocalExec Error:', error);\n });\n\n worker.on('warning', (warning) => {\n console.warn('LocalExec Warning:', warning);\n });\n\n worker.start().catch((e) => {\n console.error('Failed to start worker for localExec:');\n console.error(e.message);\n });\n });\n\n if (DCP_ENV.platform === 'nodejs')\n {\n // Determine type of input data\n // marshalInputData actually returns the union of dataRange, dataValues, dataPattern, sliceCount,\n // but there isn't a simple way to express that in Javascript.\n const { dataRange, dataValues, dataPattern, sliceCount } = marshalInputData(this.jobInputData);\n \n const inputSetFiles = [];\n \n let inputSetURIs = [];\n let dataSet;\n \n if (dataValues)\n {\n for (let i = 0; i < dataValues.length; i++)\n {\n if (!(dataValues[i] instanceof URL))\n {\n let marshaledInputValue = kvinMarshal(dataValues[i]);\n let inputDataFile = createTempFile('dcp-localExec-sliceData-XXXXXXXXX', 'kvin');\n inputDataFile.writeSync(JSON.stringify(marshaledInputValue));\n inputSetFiles.push(inputDataFile);\n inputSetURIs.push(new URL('file://' + inputDataFile.filename));\n }\n else\n inputSetURIs.push(dataValues[i]);\n }\n dataSet = new RemoteDataSet(inputSetURIs);\n if (dataSet.length > 0)\n this.marshaledDataValues = dataSet;\n }\n if (dataRange)\n {\n inputSetFiles.push(createTempFile('dcp-localExec-sliceData-XXXXXXXXX', 'json'));\n let marshaledInputSet = JSON.stringify(dataRange);\n inputSetFiles[0].writeSync(marshaledInputSet)\n inputSetURIs.push(new URL('file://' + inputSetFiles[0].filename));\n dataSet = new RemoteDataSet(inputSetURIs);\n this.marshaledDataRange = dataSet;\n this.rangeLength = dataRange.length;\n }\n \n // For allowed origins of the localexec worker. Only allow the origins (files in this case) in this list.\n for (let i = 0; i < inputSetFiles.length; i++)\n {\n // Coerce into a URI to handle OS specific path separators.\n const inputFileURI = new URL(`file://${inputSetFiles[i].filename}`);\n this.localExecAllowedOrigins['fetchData'].push(inputFileURI.pathname);\n }\n \n // Save work function to disk if work function starts with data (ie not remote)\n if (this.workFunctionURI.startsWith('data:'))\n {\n const workFunctionFile = createTempFile('dcp-localExec-workFunction-XXXXXXXXX', 'js');\n // data:URI - dont care about origins\n const workFunction = await fetchURI(this.workFunctionURI, false);\n workFunctionFile.writeSync(workFunction);\n \n const workFunctionFileURL = new URL('file://' + workFunctionFile);\n this.workFunctionURI = workFunctionFileURL.href;\n this.localExecAllowedOrigins['fetchWorkFunctions'].push(workFunctionFileURL.pathname);\n }\n \n let encodedJobArgumentUris = [];\n if (this.jobArguments)\n {\n if (this.jobArguments instanceof RemoteDataPattern) /* Not supported */\n throw new DCPError('Cannot use RemoteDataPattern as work function arguments', 'EBADARG')\n if (this.jobArguments instanceof RemoteDataSet) /* Entire set is RemoteDataSet */\n {\n this.jobArguments.forEach((e) =>\n {\n encodedJobArgumentUris.push(new URL(e));\n });\n }\n else\n {\n for (let i = 0; i < this.jobArguments.length; i++)\n {\n if (this.jobArguments[i] instanceof URL)\n encodedJobArgumentUris.push(this.jobArguments[i]);\n else\n {\n if (this.jobArguments[i] instanceof RemoteDataSet) /* Member of set is RemoteDataSet */\n {\n this.jobArguments[i].forEach((e) =>\n {\n encodedJobArgumentUris.push(new URL(e));\n });\n }\n else /* Actual Value */\n {\n const localArgFile = createTempFile(`dcp-localExec-argument-${i}-XXXXXXXXX`, 'kvin');\n const localArgFileURI = new URL(`file://${localArgFile.filename}`);\n\n localArgFile.writeSync(JSON.stringify(kvinMarshal(this.jobArguments[i])));\n this.localExecAllowedOrigins['fetchArguments'].push(localArgFileURI.pathname);\n encodedJobArgumentUris.push(localArgFileURI);\n }\n }\n } \n }\n }\n this.marshaledArguments = kvinMarshal(encodedJobArgumentUris);\n }\n \n return this.exec(...args).finally(() => {\n if (worker) {\n const timerHandle = setTimeout(() => {\n // stop the worker\n worker.stop(true);\n }, 3000);\n // Allow workers and localExec to exit.\n if (timerHandle.unref)\n timerHandle.unref();\n }\n });\n }\n\n /**\n * Deploys the job to the scheduler.\n * @param {number | object} [slicePaymentOffer=compute.marketValue] - Amount\n * in DCC that the user is willing to pay per slice.\n * @param {Keystore} [paymentAccountKeystore=wallet.get] - An instance of the\n * Wallet API Keystore that's used as the payment account when executing the\n * job.\n * @param {object} [initialSliceProfile] - An object describing the cost the\n * user believes the average slice will incur.\n * @access public\n * @emits Job#accepted\n */\n async exec (slicePaymentOffer = (__webpack_require__(/*! ../compute */ \"./src/dcp-client/compute.js\").compute.marketValue), paymentAccountKeystore, initialSliceProfile)\n {\n\n if (this.connected)\n throw new Error(`Exec called twice on the same job handle (${this.address})`);\n \n if (this.estimationSlices === Infinity)\n this.estimationSlices = null;\n else if (this.estimationSlices < 0)\n throw new Error('Incorrect value for estimationSlices; it can be an integer or Infinity!');\n \n if (this.tuning.kvin.speed || this.tuning.kvin.size)\n {\n tunedKvin = new kvin.KVIN();\n tunedKvin.tune = 'size';\n if(this.tuning.kvin.speed)\n tunedKvin.tune = 'speed';\n // If both size and speed are true, kvin will optimize based on speed\n if(this.tuning.kvin.speed && this.tuning.kvin.size)\n console.log('Slices and arguments are being uploaded with speed optimization.');\n }\n \n /* slight optimization to ensure we don't send requirements that will be ignored in the job submitter. Make a copy of the client specified requirements for this so that we dont magically override something they manually set */\n const _DEFAULT_REQUIREMENTS = JSON.parse(JSON.stringify(DEFAULT_REQUIREMENTS));\n removeBadRequirements(this.requirements, _DEFAULT_REQUIREMENTS);\n \n this.readyStateChange('exec');\n if ((typeof slicePaymentOffer === 'number') || (typeof slicePaymentOffer === 'object')\n || ((this.slicePaymentOffer === null || this.slicePaymentOffer === undefined) && typeof slicePaymentOffer === 'function'))\n this.setSlicePaymentOffer(slicePaymentOffer);\n if (typeof initialSliceProfile !== 'undefined')\n this.initialSliceProfile = initialSliceProfile;\n \n if (typeof paymentAccountKeystore !== 'undefined')\n {\n /** XXX @todo deprecate use of ethereum wallet objects */\n if (typeof paymentAccountKeystore === 'object' && paymentAccountKeystore.hasOwnProperty('_privKey'))\n {\n console.warn('* deprecated API * - job.exec invoked with ethereum wallet object as paymentAccountKeystore') /* /wg oct 2019 */\n paymentAccountKeystore = paymentAccountKeystore._privKey\n }\n /** XXX @todo deprecate use of private keys */\n if (wallet.isPrivateKey(paymentAccountKeystore))\n {\n console.warn('* deprecated API * - job.exec invoked with private key as paymentAccountKeystore') /* /wg dec 2019 */\n paymentAccountKeystore = await new wallet.Keystore(paymentAccountKeystore, '');\n }\n\n this.setPaymentAccountKeystore(paymentAccountKeystore)\n }\n \n if (this.paymentAccountKeystore)\n // Throws if they fail to unlock, we allow this since the keystore was set programmatically. \n await this.paymentAccountKeystore.unlock(undefined, parseFloat(dcpConfig.job.maxDeployTime));\n else\n {\n // If not set programmatically, we keep trying to get an unlocked keystore ... forever.\n let locked = true;\n let safety = 0; // no while loop shall go unguarded\n let ks;\n do\n {\n ks = null;\n // custom message for the browser modal to denote the purpose of keystore submission\n // let msg = `This application is requesting a keystore file to execute ${this.public.description || this.public.name || 'this job'}.<br><br> Please upload the corresponding keystore file. If you upload a keystore file which has been encrypted with a passphrase, the application will not be able to use it until it prompts for a passphrase and you enter it.`;\n let msg = `Upload a DCP bank account keystore<br> containing compute credits.`;\n try\n {\n ks = await wallet.get({ contextId: this.contextId, jobName: this.public.name, msg});\n }\n catch (e)\n {\n if (e.code !== ClientModal.CancelErrorCode) throw e;\n };\n if (ks)\n {\n try\n {\n await ks.unlock(undefined, parseFloat(dcpConfig.job.maxDeployTime));\n locked = false;\n }\n catch (e)\n {\n // prompt user again if user enters password incorrectly, exit modal otherwise\n if (e.code !== wallet.unlockFailErrorCode) throw e;\n }\n }\n if (safety++ > 1000) throw new Error('EINFINITY: job.exec tried wallet.get more than 1000 times.')\n } while (locked);\n this.setPaymentAccountKeystore(ks)\n }\n \n // We either have a valid keystore + password or we have rejected by this point.\n if (!this.slicePaymentOffer)\n throw new Error('A payment profile must be assigned before executing the job');\n else\n this.feeStructure = this.slicePaymentOffer.toFeeStructure(this.jobInputData.length);\n\n if (!this.address)\n {\n try\n {\n this.readyStateChange('init');\n await this.deployJob();\n const listenersPromise = this.addInitialEvents();\n const computeGroupsPromise = this.joinComputeGroups();\n let uploadSlicePromise;\n // if job data is by value then upload data to the scheduler in a staggered fashion\n if (Array.isArray(this.dataValues) && !this.marshaledDataValues)\n {\n this.readyStateChange('uploading');\n uploadSlicePromise = addSlices(this.dataValues, this.address, tunedKvin)\n .then(() => {\n debugging('slice-upload') && console.info(`970: slice data uploaded, closing job...`);\n return this.close();\n });\n }\n \n // await all promises for operations that can be done after the job is deployed\n await Promise.all([listenersPromise, computeGroupsPromise, uploadSlicePromise]);\n \n this.readyStateChange('deployed');\n this.emit('accepted', this);\n this.#jobDeployRef = clearInterval(this.#jobDeployRef); /* Remove the reference to job deploy. */\n }\n catch (error)\n {\n if (ON_BROWSER)\n await ClientModal.alert(error, { title: 'Failed to deploy job!' });\n throw error;\n }\n }\n else\n {\n // reconnecting to an old job\n await this.addInitialEvents();\n this.readyStateChange('reconnected');\n }\n\n this.connected = true;\n\n return new Promise((resolve, reject) => {\n this.startEventBandaidWatchdog();\n \n const onComplete = () => {\n this.stopEventBandaidWatchdog();\n resolve(this.results); \n };\n\n const onCancel = (event) => {\n if (ON_BROWSER && !this.listeningForCancel)\n ClientModal.alert('More details in console...', { title: 'Job Canceled' });\n this.emit('cancel', event);\n\n let errorMsg = event.reason;\n if (event.error && event.error !== 'undefined')\n errorMsg = errorMsg +`\\n Recent error message: ${event.error.message}`\n\n this.stopEventBandaidWatchdog();\n\n reject(new DCPError(errorMsg, event.code));\n };\n\n const onNoFunds = (event) => {\n let errorMsg = `Job paused due to ENOFUNDS. ${event.remainingSlices} remaining slices at ${event.slicePaymentAmount} DCCs per slice.`;\n let errorCode = 'ENOFUNDS';\n\n this.stopEventBandaidWatchdog();\n\n reject(new DCPError(errorMsg, errorCode));\n }\n\n this.ee.once('stopped', async (stopEvent) => {\n // There is a chance the result submitter will emit finished > 1 time. Only handle it once.\n if (this.receivedStop)\n return;\n this.receivedStop = true;\n this.emit('stopped', stopEvent.runStatus);\n switch (stopEvent.runStatus) {\n case jobStatus.finished:\n if (this.collateResults)\n {\n let report = await this.getJobInfo();\n let allSliceNumbers = Array.from(Array(report.totalSlices)).map((e,i)=>i+1);\n let remainSliceNumbers = allSliceNumbers.filter((e) => !this.results.isAvailable(e));\n\n if (remainSliceNumbers.length)\n {\n const promises = remainSliceNumbers.map(sliceNumber => this.results.fetch([sliceNumber], true));\n await Promise.all(promises);\n }\n }\n\n this.emit('complete', this.results);\n onComplete();\n break;\n case jobStatus.cancelled:\n onCancel(stopEvent);\n break;\n case jobStatus.paused:\n onNoFunds(stopEvent);\n break;\n default:\n /**\n * Asserting that we should never be able to reach here. The only\n * scheduler events that should trigger the Job's 'stopped' event\n * are jobStatus.cancelled, jobStatus.finished, and sliceStatus.paused.\n */\n reject(new Error(`Unknown event \"${stopEvent.runStatus}\" caused the job to be stopped.`));\n break;\n }\n });\n\n }).finally(() => {\n const handleErr = (e) => {\n console.error('Error while closing job connection:');\n console.error(e);\n }\n\n // Create an async IIFE to not block the promise chain\n (async () => {\n // delay to let last few events to be received\n await new Promise((resolve) => setTimeout(resolve, 1000));\n \n // close all of the connections so that we don't cause node processes to hang.\n this.closeDeployConnection();\n this.eventSubscriber.close();\n await computeGroups.closeServiceConnection().catch((err) => {\n console.error('Warning: could not close compute groups service connection', err);\n });\n })();\n\n /* Remove the reference to the Job. */\n this.unref();\n });\n }\n\n /**\n * Start the event-bandaid watchdog. Every [configurable] seconds, fetch\n * the job report and do appropriate things if the job is stopped.\n */\n startEventBandaidWatchdog()\n {\n if (this.eventBandaidHandle)\n this.stopEventBandaidWatchdog();\n\n this.eventBandaidHandle = setInterval(() => {\n this.eventBandaid();\n }, 60 * 1000);\n\n // if we are in a nodelike environment with unreffable timers, unref the\n // timer so it doesn't hold the process open. \n // \n // (n.b. while I understand that we _want_ the process to stay open while\n // the job lives, but this is Not The Way; the whole eventBandaid is a\n // temporary hack which will go away in time ~ER 2022-11-02)\n if (this.eventBandaidHandle.unref)\n this.eventBandaidHandle.unref();\n }\n\n /**\n * Stop and clean up the even-bandaid watchdog.\n */\n stopEventBandaidWatchdog()\n {\n if (this.eventBandaidHandle)\n clearInterval(this.eventBandaidHandle);\n\n this.eventBandaidHandle = false;\n }\n\n /**\n * Called on an interval, started by .exec and stopped by\n * onComplete/onCancel. Fetches the jobReport and verifies the job is\n * not in a terminal state; if the job is finished or cancelled, we\n * emit a synthetic internal stop event to trigger regular cleanup\n * (result retrieval, etc) and finalize the job handle.\n */\n eventBandaid()\n {\n this.getJobInfo()\n .then(jobInfo => {\n if ([jobStatus.finished, jobStatus.cancelled].includes(jobInfo.status))\n this.ee.emit('stopped', { runStatus: jobInfo.status });\n })\n .catch(error => {\n\n });\n }\n \n /**\n * job.addListeners(): Private function used to set up event listeners to the scheduler\n * before deploying the job.\n */\n async addInitialEvents ()\n {\n this.readyStateChange('listeners');\n\n // This is important: We need to flush the task queue before adding listeners\n // because we queue pending listeners by listening to the newListener event (in the constructor).\n // If we don't flush here, then the newListener events may fire after this function has run,\n // and the events won't be properly set up.\n await new Promise(resolve => setTimeout(resolve, 0));\n\n // @todo: Listen for an estimated cost, probably emit an \"estimated\" event when it comes in?\n // also @todo: Do the estimation task(s) on the scheduler and send an \"estimated\" event\n\n // Always listen to the stop event. It will resolve the work function promise, so is always needed.\n this.on('stop', (ev) => {this.ee.emit('stopped', ev)});\n\n this.on('nofunds', (ev) => {\n this.ee.emit('stopped', ev);\n })\n\n // Connect listeners that were set up before exec\n if (this.desiredEvents.includes('result'))\n this.listeningForResults = true;\n await this.subscribeNewEvents(this.desiredEvents);\n\n // Connect listeners that are set up after exec\n this.on('newListener', (evt) => {\n if (evt === 'newListener' || this.desiredEvents.includes(evt))\n return;\n this.subscribeNewEvents([evt]);\n });\n \n // automatically add a listener for results if collateResults is on\n if (this.collateResults && !this.listeningForResults)\n this.on('result', () => {});\n\n debugging('dcp-client') && console.debug('subscribedEvents', this.desiredEvents);\n\n // If we have listeners for job.work, subscribe to custom events\n if (this.listenForCustomEvents)\n await this.subscribeCustomEvents();\n // Connect work event listeners that are set up after exec\n else\n this.work.on('newListener', () => this.subscribeCustomEvents());\n }\n \n /**\n * Subscribes to either reliable events or optional events. It is assumed that\n * any call to this function will include only new events.\n * @param {string[]} events \n */\n async subscribeNewEvents (events)\n {\n const reliableEvents = [];\n const optionalEvents = [];\n for (let eventName of events)\n {\n eventName = eventName.toLowerCase();\n if (this.eventTypes[eventName] && this.eventTypes[eventName].reliable)\n reliableEvents.push(eventName);\n else if (this.eventTypes[eventName] && !this.eventTypes[eventName].reliable)\n optionalEvents.push(eventName);\n else\n debugging('dcp-client') && console.debug(`Job handler has listener ${eventName} which isn't an event-router event.`);\n }\n if (debugging('dcp-client'))\n {\n console.debug('reliableEvents', reliableEvents);\n console.debug('optionalEvents', optionalEvents);\n }\n await this.eventSubscriber.subscribeManyEvents(reliableEvents, optionalEvents, { filter: { job: this.address } });\n }\n \n /**\n * Establishes listeners for worker events when requested by the client\n */\n async subscribeCustomEvents ()\n {\n if (!this.listeningForCustomEvents)\n await this.eventSubscriber.subscribeManyEvents([], ['custom'], { filter: { job: this.address } });\n this.listeningForCustomEvents = true\n }\n \n async joinComputeGroups ()\n {\n // localExec jobs are not entered in any compute group.\n if (!this.inLocalExec && this.computeGroups && this.computeGroups.length > 0)\n {\n this.readyStateChange('compute-groups');\n computeGroups.addRef(); // Just in case we're doing a Promise.all on multiple execs.\n\n // Add this job to its currently-defined compute groups (as well as public group, if included)\n let success;\n \n if (!Array.isArray(this.computeGroups)) \n throw new DCPError('Compute groups must be wrapped in an Array', 'DCPL-1101');\n\n for (let i = 0; i < this.computeGroups.length; i++)\n {\n let value = this.computeGroups[i];\n \n if (typeof value !== 'object')\n throw new DCPError(`This compute group: ${value[i]} must be an object`, 'DCPL-1102');\n \n if (value.joinKey && typeof value.joinKey !== 'string' && !(value.joinKey instanceof String))\n throw new DCPError(`This join key: ${value.joinKey} must be a string or a string literal`, 'DCPL-1103');\n else if (value.joinKeystore && !(value.joinKeystore instanceof wallet.Keystore))\n throw new DCPError(`This join Keystore: ${value.joinKeystore} must be an instance of wallet.Keystore`, 'DCPL-1104');\n else if (!value.joinKey && !value.joinKeystore)\n throw new DCPError('Compute group must contain a joinKey or a joinKeystore', 'DCPL-1105');\n }\n \n try\n {\n const cgPayload = await computeGroups.addJobToGroups(this.address, this.computeGroups);\n success = true; // To support older version of CG service where addJobToGroups had void/undefined return.\n if (cgPayload) success = cgPayload.success;\n debugging('dcp-client') && console.debug('job/index: addJobToGroups cgPayload:', cgPayload ? cgPayload : 'cgPayload is not defined; probably from legacy CG service.');\n }\n catch (e)\n {\n debugging('dcp-client') && console.debug('job/index: addJobToGroups threw exception:', e);\n success = false;\n }\n\n computeGroups.closeServiceConnection().catch((err) => {\n console.error('Warning: could not close compute groups service connection', err)\n });\n\n /* Could not put the job in any compute group, even though the user wanted it to run. Cancel the job. */\n if (!success)\n {\n await this.cancel('compute-groups::Unable to join any compute groups');\n throw new DCPError(`Access Denied::Failed to add job ${this.address} to any of the desired compute groups on ${this.scheduler}`, 'DCPL-1100');\n }\n }\n }\n \n /**\n * Takes result events as input, stores the result and fires off\n * events on the job handle as required. (result, duplicate-result)\n *\n * @param {object} ev - the event recieved from protocol.listen('/results/0xThisGenAdr')\n */\n async handleResult (ev)\n {\n if (this.results === null)\n // This should never happen - the onResult event should only be established/called\n // in addListeners which should also initialize the internal results array\n throw new Error('Job.onResult was invoked before initializing internal results');\n \n const { result: _result, time } = ev.result;\n debugging('dcp-client') && console.debug('handleResult', _result);\n /**\n * There is currently not way to access OAM instance on the job level.\n * This case is similar to the fetch case in result-handle.js.\n * The value to fetchURI is either a dataURI, RDS that user defined or scheduler disk location.\n */\n let result = await fetchURI(_result, false);\n\n if (this.results.isAvailable(ev.sliceNumber))\n {\n const changed = JSON.stringify(this.results[ev.sliceNumber]) !== JSON.stringify(result);\n this.emit('duplicate-result', { sliceNumber: ev.sliceNumber, changed });\n }\n \n this.results.newResult(result, ev.sliceNumber - 1);\n }\n \n /**\n * Receives status events from the scheduler, updates the local status object\n * and emits a 'status' event\n *\n * @param {object} ev - the status event received from\n * protocol.listen('/status/0xThisGenAdr')\n * @param {boolean} emitStatus - value indicating whether or not the status\n * event should be emitted\n */\n handleStatus ({ runStatus, total, distributed, computed }, emitStatus = true)\n {\n Object.assign(this.status, {\n runStatus,\n total,\n distributed,\n computed,\n });\n\n if (emitStatus)\n this.emit('status', { ...this.status, job: this.address });\n }\n \n /**\n * Sends a request to the scheduler to deploy the job.\n */\n async deployJob ()\n {\n var moduleDependencies; \n \n this.#jobDeployRef = setInterval(() => undefined, 271828); /* Create a reference to the deploy cycle. */\n \n /* Send sideloader bundle to the package server */\n if (DCP_ENV.platform === 'nodejs' && this.dependencies.length)\n {\n try\n {\n let { pkg, unresolved } = await this._publishLocalModules();\n\n moduleDependencies = unresolved;\n if (pkg)\n moduleDependencies.push(pkg.name + '/' + sideloaderModuleIdentifier); \n }\n catch(error)\n {\n throw new DCPError(`Error trying to communicate with package manager server: ${error}`);\n }\n }\n else\n moduleDependencies = this.dependencies;\n \n this.readyStateChange('preauth');\n\n const adhocId = this.uuid.slice(this.uuid.length - 6, this.uuid.length);\n const schedId = await dcpConfig.scheduler.identity;\n // The following check is needed for when using dcp-rtlink and loading the config through source, instead of using the dcp-client bundle\n let schedIdAddress = schedId;\n if(schedId.address)\n schedIdAddress = schedId.address;\n this.identityKeystore = await wallet.getId();\n const preauthToken = await bankUtil.preAuthorizePayment(schedIdAddress, this.maxDeployPayment, this.paymentAccountKeystore);\n const { dataRange, dataValues, dataPattern, sliceCount } = marshalInputData(this.jobInputData);\n if(dataValues)\n this.dataValues = dataValues;\n\n this.readyStateChange('deploying');\n\n /* Payload format is documented in scheduler-v4/libexec/job-submit/operations/submit.js */\n const submitPayload = {\n owner: this.identityKeystore.address,\n paymentAccount: this.paymentAccountKeystore.address,\n priority: 0, // @nyi\n\n workFunctionURI: this.workFunctionURI,\n uuid: this.uuid,\n mvMultSlicePayment: Number(this.feeStructure.marketValue) || 0, // @todo: improve feeStructure internals to better reflect v4\n absoluteSlicePayment: Number(this.feeStructure.maxPerRequest) || 0,\n requirePath: this.requirePath,\n dependencies: moduleDependencies,\n requirements: this.requirements, /* capex */\n localExec: this.inLocalExec,\n force100pctCPUDensity: this.force100pctCPUDensity,\n estimationSlices: this.estimationSlices,\n greedyEstimation: this.greedyEstimation,\n workerConsole: this.workerConsole,\n isCI: this.isCI,\n\n description: this.public.description || 'Discreetly making the world smarter',\n name: this.public.name || 'Ad-Hoc Job' + adhocId,\n link: this.public.link || '',\n\n preauthToken, // XXXwg/er @todo: validate this after fleshing out the stub(s)\n\n resultStorageType: this.resultStorageType, // @todo: implement other result types\n resultStorageDetails: this.resultStorageDetails, // Content depends on resultStorageType\n resultStorageParams: this.resultStorageParams, // Post params for off-prem storage\n dataRange,\n dataPattern,\n sliceCount,\n marshaledDataValues: this.marshaledDataValues,\n rangeLength: this.rangeLength\n };\n\n // Check if dataRange or dataPattern input is already marshaled\n if (this.marshaledDataRange)\n submitPayload.dataRange = this.marshaledDataRange;\n\n /* Determine composition of argument set and build payload */\n if (this.marshaledArguments)\n submitPayload.marshaledArguments = this.marshaledArguments;\n else if (this.jobArguments)\n {\n if (this.jobArguments instanceof RemoteDataPattern) /* Not supported */\n throw new DCPError('Cannot use RemoteDataPattern as work function arguments', 'EBADARG')\n if (this.jobArguments instanceof RemoteDataSet) /* Entire set is RemoteDataSet */\n this.jobArguments = this.jobArguments.map((e) => new URL(e));\n\n submitPayload.marshaledArguments = kvin.marshal(encodeJobValueList(this.jobArguments, 'jobArguments'));\n }\n\n // XXXpfr Excellent tracing.\n if (debugging('dcp-client'))\n {\n const { dumpObject } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\n dumpObject(submitPayload, 'Submit: Job Index: submitPayload', 256);\n }\n debugging('dcp-client') && console.debug('submitPayload.marshaledArguments', JSON.stringify(submitPayload.marshaledArguments).length, JSON.stringify(submitPayload).length);\n\n // Deploy the job! If we get an error, try again a few times until threshold of errors is reached, then actually throw it\n let deployed\n let deployAttempts = 0;\n while (deployAttempts++ < (dcpConfig.job.deployAttempts || 10))\n {\n try\n {\n deployed = await this.useDeployConnection('submit', submitPayload, this.identityKeystore);\n /* if connection is lost during useDeployConnection, error message that gets returned is not thrown, so throw here so that we can reattempt deployment */\n if (deployed instanceof Error && deployed.code === 'DCPC-1013'){\n throw deployed;\n }\n break;\n }\n catch (e)\n {\n if (deployAttempts < 10)\n debugging('dcp-client') && console.debug('Error when trying to deploy job, trying again', e);\n else\n throw e;\n }\n }\n\n if (!deployed.success)\n {\n // close all of the connections so that we don't cause node processes to hang.\n const handleErr = (e) => {\n console.error('Error while closing job connection:');\n console.error(e);\n };\n \n this.closeDeployConnection();\n this.eventSubscriber.close();\n computeGroups.closeServiceConnection().catch(handleErr);\n this.#jobDeployRef = clearInterval(this.#jobDeployRef);\n \n // Yes, it is possible for deployed or deployed.payload to be undefined.\n if (deployed && deployed.payload)\n {\n if (deployed.payload.code === 'ENOTFOUND')\n throw new DCPError(`Failed to submit job; account ${submitPayload.paymentAccount} does not have sufficient balance (${deployed.payload.info.deployCost}⊇ needed to deploy this job)`, deployed.payload); \n throw new DCPError('Failed to submit job:' + deployed.payload?.message, deployed.payload.code);\n }\n throw new DCPError(`Failed to submit job (no payload; ${deployed || typeof deployed})`);\n }\n\n debugging('dcp-client') && console.debug('After Deploy', JSON.stringify(deployed));\n\n this.address = deployed.payload.job;\n this.deployCost = deployed.payload.deployCost;\n\n if (!this.status)\n this.status = {\n runStatus: null,\n total: 0,\n computed: 0,\n distributed: 0,\n };\n \n this.status.runStatus = deployed.payload.status;\n this.status.total = deployed.payload.lastSliceNumber;\n this.running = true;\n }\n \n /** close an open job to indicate we are done adding data so it is okay to finish\n * the job at the appropriate time\n */\n close ()\n {\n return this.useDeployConnection('closeJob', {\n job: this.address,\n });\n }\n \n /** Use the connection to job submit service. Will open a new connection if one does not exist,\n * and close the connection if it is idle for more than 10 seconds (tuneable).\n */\n useDeployConnection(operation, payload)\n {\n if (!this.useDeployConnection.uses)\n this.useDeployConnection.uses = 0;\n this.useDeployConnection.uses++;\n if (!this.deployConnection)\n {\n debugging('deploy-connection') && console.info(`1453: making a new deployConnection...`)\n this.deployConnection = new protocolV4.Connection(dcpConfig.scheduler.services.jobSubmit); \n this.deployConnection.on('end', () => { this.deployConnection = null; });\n }\n if (this.deployConnectionTimeout)\n clearTimeout(this.deployConnectionTimeout);\n\n debugging('deploy-connection') && console.info(`1460: sending ${operation} request...`);\n const deployPromise = this.deployConnection.request(operation, payload);\n \n deployPromise.finally(() => {\n this.useDeployConnection.uses--;\n\n debugging('deploy-connection') && console.info(`1462: deployConnection done ${operation} request, connection uses is ${this.useDeployConnection.uses}`)\n\n this.deployConnectionTimeout = setTimeout(() => {\n if (this.useDeployConnection.uses === 0 && this.deployConnection)\n {\n debugging('deploy-connection') && console.info(`1469: done with deployConn, closing...`);\n // if we're done w/ the connection, then remove its cleanup\n // function, close it, and clean up manually to make room for a\n // new conn when needed (ie, don't leave the closing conn where\n // someone could accidentally pick it up)\n this.deployConnection.removeAllListeners('end');\n this.deployConnection.close();\n this.deployConnection = null;\n }\n }, (dcpConfig.job.deployCloseTimeout || 10 * 1000));\n if (this.deployConnectionTimeout.unref)\n this.deployConnectionTimeout.unref();\n }); \n \n return deployPromise;\n }\n \n /**\n * Close the connection to the job submit (if it exists), and clear the close timeout (if needed).\n */\n closeDeployConnection()\n {\n if (this.deployConnection)\n this.deployConnection.close();\n if (this.deployConnectionTimeout)\n clearTimeout(this.deployConnectionTimeout);\n }\n\n unref()\n {\n if (this.#jobRef)\n this.#jobRef = clearInterval(this.#jobRef);\n if (this.#jobDeployRef)\n this.#jobDeployRef = clearInterval(this.#jobDeployRef);\n }\n\n ref()\n {\n this.#jobRef = setInterval(() => undefined, 1618033988);\n }\n}\n\n/** \n * Encode a value list for transmission to the job-submit daemon. This could be either job arguments\n * or the slice input set, if the input set was an Array-like object.\n *\n * @param {ArrayLike} valueList - The list of values to encode\n * @returns {Array<URL|{string: string, method:string, MIMEType: string }>} - URL or object with 3 properties:\n * string - serialization of value\n * method - how value was serialized (e.g. 'kvin', 'json')\n * MIMEType - MIME information corresponding to method (e.g. 'application/kvin', 'application/json')\n */\nfunction encodeJobValueList(valueList, valueKind)\n{\n var list = [];\n\n /*\n * We need to handle several different styles of datasets, and create the output array accordingly.\n *\n * 1. instance of RemoteDataSet or RemoteDataPattern => apply new URI(?)\n * 2. an Array-like objects => apply serializeJobValue\n * Values sent to the scheduler in payload are either URL or the result of calling serializeJobValue.\n */\n\n if (typeof valueList === 'undefined' || (typeof valueList === 'object' && valueList.length === 0))\n return list; /* empty set */\n\n if (typeof valueList !== 'object' || !valueList.hasOwnProperty('length'))\n throw new Error('value list must be an Array-like object');\n\n for (let i = 0; i < valueList.length; i++) /* Set is composed of values from potentially varying sources */\n {\n let value = valueList[i];\n if (value instanceof RemoteDataSet)\n value.forEach((el) => list.push(new URL(el)));\n else if (value instanceof RemoteDataPattern)\n {\n if (valueKind === jobValueKind.jobArguments)\n throw new DCPError('Cannot use RemoteDataPattern as work function arguments', 'EBADARG');\n else\n {\n let uri = valueList['pattern'];\n for (let sliceNum = 1; sliceNum <= valueList['sliceCount']; sliceNum++)\n list.push(new URL(uri.replace('{slice}', sliceNum)))\n }\n }\n else if (value instanceof RemoteValue)\n list.push(serializeJobValue(value.href));\n else\n list.push(serializeJobValue(value));\n }\n\n return list;\n} \n\n/**\n * Depending on the shape of the job's data, resolve it into a RangeObject, a\n * Pattern, or a values array, and return it in the appropriate property.\n *\n * @param {any} data Job's input data\n * @return {MarshaledInputData} An object with one of the following properties set:\n * - dataValues: job input is an array of arbitrary values \n * - dataPattern: job input is a URI pattern \n * - dataRange: job input is a RangeObject (and/or friends)\n */\nfunction marshalInputData (data)\n{\n if (!(data instanceof Object || data instanceof SuperRangeObject))\n throw new TypeError(`Invalid job data type: ${typeof data}`);\n\n /**\n * @type {MarshaledInputData}\n */\n const marshalledInputData = {};\n\n // TODO(wesgarland): Make this more robust.\n if (data instanceof SuperRangeObject ||\n (data.hasOwnProperty('ranges') && data.ranges instanceof MultiRangeObject) ||\n (data.hasOwnProperty('start') && data.hasOwnProperty('end')))\n marshalledInputData.dataRange = data;\n else if (Array.isArray(data))\n marshalledInputData.dataValues = data;\n else if (data instanceof URL || data instanceof DcpURL)\n marshalledInputData.dataPattern = String(data);\n else if(data instanceof RemoteDataSet)\n marshalledInputData.dataValues = data.map(e => new URL(e));\n else if(data instanceof RemoteDataPattern)\n {\n marshalledInputData.dataPattern = data['pattern'];\n marshalledInputData.sliceCount = data['sliceCount'];\n }\n\n debugging('job') && console.debug('marshalledInputData:', marshalledInputData);\n return marshalledInputData;\n}\n\n/**\n * marshal the value using kvin or instance of the kvin (tunedKvin)\n * tunedKvin is defined if job.tuning.kvin is specified.\n *\n * @param {any} value \n * @return {object} A marshaled object\n * \n */\nfunction kvinMarshal (value) {\n if (tunedKvin)\n return tunedKvin.marshal(value);\n\n return kvin.marshal(value);\n}\n\n\n\nexports.Job = Job;\nexports.SlicePaymentOffer = SlicePaymentOffer;\nexports.ResultHandle = ResultHandle;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/job/index.js?");
4350
4350
 
4351
4351
  /***/ }),
4352
4352
 
@@ -4447,7 +4447,7 @@ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_mod
4447
4447
  \*************************************************/
4448
4448
  /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
4449
4449
 
4450
- eval("/**\n * @file /src/schedmsg/schedmsg-web.js\n * @author Ryan Rossiter, ryan@kingsds.network\n * @date March 2020\n *\n * This is the SchedMsg implementation for commands that are browser-specific\n * or have browser-specific behaviour.\n */\n\nconst { SchedMsg } = __webpack_require__(/*! ./schedmsg */ \"./src/dcp-client/schedmsg/schedmsg.js\");\n\nclass SchedMsgWeb extends SchedMsg {\n constructor(worker) {\n super(worker);\n\n this.registerHandler('announce', this.onAnnouncement.bind(this));\n this.registerHandler('openPopup', this.onOpenPopup.bind(this));\n this.registerHandler('reload', this.onReload.bind(this));\n }\n\n onAnnouncement({ message }) {\n \n window.alert('DCP Worker Announcement: ' + message);\n\n }\n\n onOpenPopup({ href }) {\n window.open(href);\n }\n\n onReload() {\n const hash = window.location.hash;\n\n let newUrl = window.location.href.replace(/#.*/, '');\n newUrl += (newUrl.indexOf('?') === -1 ? '?' : '&');\n newUrl += 'dcp=03556f0a9576ead0784808e7f17268e43af253bb,' + Date.now() + hash;\n\n window.location.replace(newUrl);\n }\n}\n\nObject.assign(module.exports, {\n SchedMsgWeb\n});\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/schedmsg/schedmsg-web.js?");
4450
+ eval("/**\n * @file /src/schedmsg/schedmsg-web.js\n * @author Ryan Rossiter, ryan@kingsds.network\n * @date March 2020\n *\n * This is the SchedMsg implementation for commands that are browser-specific\n * or have browser-specific behaviour.\n */\n\nconst { SchedMsg } = __webpack_require__(/*! ./schedmsg */ \"./src/dcp-client/schedmsg/schedmsg.js\");\n\nclass SchedMsgWeb extends SchedMsg {\n constructor(worker) {\n super(worker);\n\n this.registerHandler('announce', this.onAnnouncement.bind(this));\n this.registerHandler('openPopup', this.onOpenPopup.bind(this));\n this.registerHandler('reload', this.onReload.bind(this));\n }\n\n onAnnouncement({ message }) {\n \n window.alert('DCP Worker Announcement: ' + message);\n\n }\n\n onOpenPopup({ href }) {\n window.open(href);\n }\n\n onReload() {\n const hash = window.location.hash;\n\n let newUrl = window.location.href.replace(/#.*/, '');\n newUrl += (newUrl.indexOf('?') === -1 ? '?' : '&');\n newUrl += 'dcp=050c7461ed59f387f7f585e05615ff7ae0eef059,' + Date.now() + hash;\n\n window.location.replace(newUrl);\n }\n}\n\nObject.assign(module.exports, {\n SchedMsgWeb\n});\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/schedmsg/schedmsg-web.js?");
4451
4451
 
4452
4452
  /***/ }),
4453
4453
 
@@ -4604,7 +4604,7 @@ eval("/**\n * @file node-localExec.js Node-specific support for cre
4604
4604
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4605
4605
 
4606
4606
  "use strict";
4607
- eval("/**\n * @file worker/index.js This module implements the Worker API, used to create workers for \n * earning DCCs. \n *\n * To use Supervisor2 set the environment variable `USE_SUPERVISOR2` or \n * set dcpConfig.rollout.supervisor=2.\n *\n * @author Ryan Rossiter <ryan@kingsds.network>\n * Paul <paul@distributive.network>\n * @date May 2020\n * June, July 2022, Feb-April 2023\n * \n * @module dcp/worker\n * @access public\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n// @ts-check\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst { SchedMsg } = __webpack_require__(/*! dcp/dcp-client/schedmsg */ \"./src/dcp-client/schedmsg/index.js\");\nconst EventEmitter = __webpack_require__(/*! events */ \"./node_modules/events/events.js\");\nconst { localStorage } = __webpack_require__(/*! dcp/common/dcp-localstorage */ \"./src/common/dcp-localstorage.js\");\nconst { confirmPrompt } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { Address } = __webpack_require__(/*! dcp/dcp-client/wallet/eth */ \"./src/dcp-client/wallet/eth.js\");\nconst { Keystore } = __webpack_require__(/*! dcp/dcp-client/wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\");\nconst dcpEnv = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst dcp_timers = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst DISABLE_WORKER_CACHE_KEY = 'disable_worker';\nvar Supervisor; /* Supervisor ctor - this will die when we kill sup1 */\n\n// Supervisor2 typedefs\n/** @typedef {import('./supervisor2/slice2').Slice} Slice */\n/** @typedef {import('./supervisor2/sandbox2').Sandbox} Sandbox */\n/** @typedef {import('./supervisor2/options').Options} Options */\n\n// Supervisor1 typedefs\n// /** @typedef {import('./sandbox').Sandbox} Sandbox */\n// /** @typedef {import('./slice').Slice} Slice */\n\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {import('dcp/dcp-client/wallet/eth').Address} Address */\n/** @typedef {import('dcp/dcp-client/wallet/keystore').Keystore} Keystore */\n/** @typedef {import('dcp/utils/jsdoc-types').SandboxOptions} SandboxOptions */\n/** @typedef {import('dcp/utils/jsdoc-types').SupervisorOptions} SupervisorOptions */\n/** @typedef {import('dcp/dcp-client/worker/origin-access-manager').OriginAccessManager} OriginAccessManager */\n\nfunction disableWorker ()\n{\n localStorage.setItem(DISABLE_WORKER_CACHE_KEY, true);\n}\n\n/** XXXwg remove this when we kill sup 1 */\nfunction determineSupervisorType()\n{\n if (Supervisor)\n return;\n\n if ( false\n || (__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").getenv)('USE_SUPERVISOR2')\n || (Number(dcpConfig.rollout?.supervisor) === 2))\n Supervisor = (__webpack_require__(/*! ./supervisor2 */ \"./src/dcp-client/worker/supervisor2/index.js\").Supervisor);\n else\n Supervisor = (__webpack_require__(/*! ./supervisor */ \"./src/dcp-client/worker/supervisor.js\").Supervisor);\n}\n\n/**\n * Fired when the worker begins fetching slices from the scheduler.\n * @access public\n */\nclass Worker extends EventEmitter\n{\n /** Private class fields */\n #supervisor;\n #hasRef; /* event loop reference; undefined=>auto, false=>never, timer=>holding */\n\n /**\n * Returns a new Worker instance.\n * This constructor may alter the workerOptions object, for version-based polyfilling and/or providing\n * key API properties, such as the cores object.\n *\n * @access public\n * @param {Keystore} identity - identity of the Worker; used for signing messages to\n * task distributor etc.\n * @param {SupervisorOptions} workerOptions - options specified for the worker; may be mutated\n */\n constructor(identity, workerOptions)\n {\n super({ captureRejections: false });\n\n if (typeof workerOptions !== 'object')\n throw new Error('workerOptions argument must be object; is ' + typeof workerOptions);\n if (typeof identity !== 'object' || !(identity instanceof Keystore))\n throw new Error(`identity argument must be Keystore; is ${typeof identity === 'object' ? identity.constructor.name : typeof identity}`);\n\n determineSupervisorType();\n\n /**\n * @type {boolean}\n * @access public\n */\n this.working = false;\n /**\n * @type {SchedMsg}\n * @access public\n */\n this.schedMsg = new SchedMsg(this);\n /**\n * It is important that dynamic changes in workerOptions are reflected in Supervisor operations. In the case where\n * cores are derived solely from defaultCoreDensity, defaultCores provides an object which stays up to date with\n * defaultCoreDensity changes until a value is explicitly set.\n *\n * @type {SupervisorOptions}\n * @access public\n */\n this.workerOptions = workerOptions;\n if (!this.workerOptions.hasOwnProperty('cores') || !this.workerOptions.cores.hasOwnProperty('cpu'))\n this.workerOptions.cores = Worker.defaultCores(this.workerOptions);\n else\n Worker.defaultInitialization(this.workerOptions);\n if (this.workerOptions.hasOwnProperty('maxWorkingSandboxes') && this.workerOptions.maxWorkingSandboxes !== undefined)\n Worker.handleMaxWorkingSandboxes(this.workerOptions); /** XXXwg deprecate this API once all apps use sup2 conf */\n\n /* Correct cores object is now plumbed in; patently-absurd configs are changed to have 0 cores so users can debug */\n if (!(this.workerOptions.cores.cpu >= 0))\n this.workerOptions.cores.cpu = 0;\n if (!(this.workerOptions.cores.gpu >= 0))\n this.workerOptions.cores.gpu = 0;\n\n /**\n * @todo XXXpfr Rip out the sup2/sup1 special-casing when we finally kill sup1.\n * @type {Supervisor}\n * @access public\n */\n this.#supervisor = new Supervisor(this, identity, this.workerOptions);\n\n debugging() && console.debug(`Worker options(isSup2:${this.#supervisor.isSupervisor2}):`, this.workerOptions.cores.cpu, this.workerOptions.cores.gpu, this.workerOptions);\n }\n\n get supervisorVersion() { return this.#supervisor.version };\n\n /**\n * Disables worker instances from being started. The user will need to manually intervene to re-enable workers.\n * @access public\n */\n static disableWorker ()\n {\n disableWorker();\n }\n\n /**\n * Starts the worker.\n * @access public\n */\n async start()\n {\n debugging() && console.debug('Starting worker');\n if (this.working)\n {\n debugging() && console.debug('Worker already working, ignoring start request');\n return;\n }\n\n if (localStorage.getItem(DISABLE_WORKER_CACHE_KEY))\n {\n await confirmPrompt(`Worker has been disabled by the DCP Security Team; check the @DCP_Protocol Twitter feed for more information before continuing.`)\n if (await confirmPrompt('Are you sure you would like to restart the worker?'))\n {\n localStorage.removeItem(DISABLE_WORKER_CACHE_KEY);\n console.log(\"Starting worker...\");\n }\n else\n return;\n }\n \n try\n {\n let dontStart = false;\n\n this.emit('beforeStart', function cancel() { dontStart = true });\n if (dontStart)\n return;\n\n this.#supervisor.work();\n this.working = true;\n this.schedMsg.start();\n if (this.#hasRef !== false)\n this.ref();\n this.emit('start');\n }\n catch(error)\n {\n debugging() && console.error(error);\n this.emit('error', error);\n this.stop(true);\n }\n }\n\n /**\n * Stops the worker.\n * The reason for this.#hasRef === false state is for\n * await start\n * stop <-- #end isn't called until after the 2nd start\n * start\n * <-- #end is called here because #supervisor.stopWork is placed on the microtask queue.\n * @access public\n * @param {boolean} [forceTerminate=false] Whether the worker should stop imediately or allow the current slices to finish.\n */\n async stop(forceTerminate = false)\n {\n if (!this.working) \n {\n debugging() && console.debug('Worker already stopping, ignoring stop request');\n return;\n }\n this.working = false;\n\n try \n {\n // We're using synchronous event emitters, so if the abort function is called immediately,\n // then #supervisor.stopWork(forceTerminate) will be called with forceTerminate === true.\n this.emit('stop', function abort() { forceTerminate = true });\n\n debugging('shutdown') && console.debug(`Worker.stop(${forceTerminate}): stopping schedMsg...`);\n await this.schedMsg.stop();\n debugging('shutdown') && console.debug(`Worker.stop(${forceTerminate}): schedmsg stopped ok; stopping supervisor...`);\n\n return this.#supervisor.stopWork(forceTerminate).finally(() => {\n debugging('shutdown') && console.debug(`Worker.stop(${forceTerminate}): supervisor stopped ok`);\n this.#end();\n });\n }\n catch (error) \n {\n debugging() && console.error(error);\n this.emit('error', error);\n }\n }\n\n #end()\n {\n if (this.#hasRef !== false)\n {\n this.unref();\n this.ref();\n }\n this.emit('end');\n }\n \n /**\n * Set the worker's event loop reference to \"auto\" - the default behaviour - which holds a reference as\n * long as we are working.\n */\n ref()\n {\n if (this.#hasRef === false)\n this.#hasRef = undefined;\n if (this.#hasRef === undefined && this.working)\n {\n this.#hasRef = dcp_timers.setInterval(function workerRef(){}, 314159);\n debugging('refs') && console.debug('worker - making event-loop reference');\n }\n }\n\n /**\n * Tell the worker to not hold an event loop reference, whether it is working or not.\n */\n unref()\n {\n if (this.#hasRef)\n {\n dcp_timers.clearInterval(this.#hasRef);\n debugging('refs') && console.debug('worker - clearing event-loop reference');\n }\n this.#hasRef = false;\n }\n\n /**\n * We have been handed a v1-style configuration object, so we use it to generate v2-style info. Then\n * we add a getter/setter on maxWorkingSandboxes so that it always reflects cores.cpu, and so that its\n * mutations (from the worker-app) update cores.cpu.\n *\n * @deprecated\n * Get rid of this when all the workers are updated so that worker options do not have property maxWorkingSandboxes.\n * Please use options.defaultCoreDensity or options.cores instead.\n * @param {SupervisorOptions} options\n */\n static handleMaxWorkingSandboxes (options)\n {\n function translateMWStoCores(maxWorkingSandboxes)\n {\n maxWorkingSandboxes = Number(maxWorkingSandboxes);\n if (typeof maxWorkingSandboxes !== 'undefined')\n options.cores.cpu = maxWorkingSandboxes;\n debugging('maxWorkingSandboxes') && console.debug(`translateMWStoCores: options.cores.cpu = ${options.cores.cpu}`);\n }\n\n translateMWStoCores(options.maxWorkingSandboxes);\n\n /* Slide in a maxWorkingSandboxes property whose backing store is v2-style configuration info. This property\n * is used by Worker applications that don't understand v2-style configuration yet.\n */\n Object.defineProperty(options, 'maxWorkingSandboxes', {\n get: () => options.cores.cpu,\n set: (value) => translateMWStoCores(value),\n configurable: true,\n enumerable: true,\n });\n }\n\n /** @type {OriginAccessManager} */\n get originManager () { return this.#supervisor.originManager; }\n //\n // This is the official supported interface for accessing supervisor.\n // When Sup1 dies, it will be simplified.\n //\n /** @type {Sandbox[]} */\n get sandboxes () { return this.#supervisor.sandboxes; }\n /** @type {Sandbox[]} */\n get workingSandboxes () { return this.#supervisor.workingSandboxes; }\n /** @type {Slice[]} */\n get slices () { return this.#supervisor.slices; }\n /** @type {Slice[]} */\n get queuedSlices () { return this.#supervisor.queuedSlices; }\n /** @type {Slice[]} */\n get workingSlices () { return this.#supervisor.workingSlices; }\n /** @type {Keystore} */\n get identityKeystore () { return this.#supervisor.identityKeystore; }\n /** @type {Keystore} */\n set identityKeystore (ks)\n {\n if (!(ks instanceof Keystore))\n throw new Error('Worker.identityKeystore: must be an instance of Keystore.');\n this.#supervisor.identityKeystore = ks;\n this.emit('identityChange', ks);\n }\n\n /** \n * @deprecated - use options.paymentAddress\n * @type {Address} \n */\n get paymentAddress () { return this.workerOptions.paymentAddress; }\n\n /** \n * @deprecated - use options.paymentAddress\n * @type {Address} \n */\n set paymentAddress (addr)\n {\n if (!(addr instanceof Address))\n throw new Error('Worker.paymentAddress: must be an instance of Address.');\n this.workerOptions.paymentAddress = addr;\n this.emit('paymentAddressChange', addr);\n }\n\n /** @type {opaqueId} */\n get workerId () { return this.#supervisor.workerId; }\n /** @type {opaqueId} */\n set workerId (id) { this.#supervisor.workerId = id; }\n /** @type {boolean} */\n get isSupervisor1 () { return this.#supervisor.isSupervisor1; } // This will die when we kill sup1\n /** @type {boolean} */\n get isSupervisor2 () { return this.#supervisor.isSupervisor2; } // This will die when we kill sup1\n \n /**\n * @deprecated\n * Please do not use this.\n * @type {Supervisor}\n */\n get badSupervisorBackdoor () { return this.#supervisor; }\n /** @type {SupervisorOptions|Options} */\n get options () { return this.#supervisor.options; }\n /**\n * Set the debug flag on the event emitter superclass of both WOrker and this.supervisor.\n * @type {boolean}\n */\n set enableDebugEvents (value) { this.debug = this.#supervisor.debug = value; }\n /**\n * Tries to fetch a task from TD (task distributer).\n * @todo Get rid of this (XXXpfr)\n * @param {number} [numCores]\n * @returns {Promise<any>}\n */\n fetchTask (numCores)\n {\n return this.#supervisor.fetchTask();\n }\n /**\n * Remove sandbox from supervisor and terminate it.\n * @param {Sandbox} sandbox\n */\n returnSandbox (sandbox)\n {\n return this.#supervisor.returnSandbox(sandbox);\n }\n /**\n * Remove sandbox from supervisor and return it to the scheduler.\n * @param {Slice} slice\n * @param {string} reason\n * @returns {Promise<any>}\n */\n returnSlice (slice, reason = 'unknown')\n {\n return this.#supervisor.returnSlice(slice, reason);\n }\n /**\n * Listen to eventName with eventHandler.\n * @param {*} eventName\n * @param {*} eventHandler\n */\n addSupervisorEventListener (eventName, eventHandler)\n {\n this.#supervisor.addListener(eventName, eventHandler);\n }\n /**\n * Stop listenting to eventName with eventHandler.\n * @param {*} eventName\n * @param {*} eventHandler\n */\n removeSupervisorEventListener (eventName, eventHandler)\n {\n this.#supervisor.removeListener(eventName, eventHandler);\n }\n /**\n * Get the job descriptor for jobAddress;\n * viz., the object value corresponding to the key jobAddress,\n * in the object returned by getJobsForTask in task-jobs.js.\n * @param {string} jobAddress\n * @returns {object}\n */\n jobDescriptor (jobAddress)\n {\n return this.#supervisor.jobDescriptor(jobAddress);\n }\n\n /**\n * Build an object suitable for use as options.cores based on the presumed number of logical cores in\n * the machine, and options.defaultCoreDensity. This is the only place options.defaultCoreDensity\n * should be used, all other calculations throughout the worker should be based on options.cores. The\n * default defaultCoreDensity is usually supplied via dcpConfig.worker in dcp-worker repo's\n * etc/dcp-worker-config.js \n *\n * options.hardware can be used to override the detected number of cores\n *\n * @note We current assume the machine has one GPU of infinite capability, and use evaluator capability\n * detection to use or not use this amazing GPU. That will obviously change as our capabilities \n * evolves.\n *\n * @note There are very important notes re core-count selection in How-supervisor-works.md.\n *\n * @note This is a static property of Worker because it is based on the physical properties of the\n * machine and the options object and does not have any relationship to a particular instance\n * of Worker.\n *\n * @param {SupervisorOptions} options\n * @returns {{ cpu: number, gpu: number }}\n */\n static defaultCores (options)\n {\n Worker.defaultInitialization(options);\n\n /** Create an \"active\" return object suitable for use as options.cores. Any writes to this object\n * persist as-is, but in the usual case, reading from the object causes the values to change\n * when hardware.vCores and/or defaultCoreDensity change.\n * @type {{ cpu: number, gpu: number }}\n */\n var ret = { cpu: 0, gpu: 0 };\n\n Object.defineProperty(ret, 'cpu', {\n get: () => Math.floor(options.defaultCoreDensity.cpu * options.hardware.vCores),\n set: (value) => {\n delete ret.cpu;\n ret.cpu = value;\n },\n configurable: true,\n enumerable: true,\n });\n\n Object.defineProperty(ret, 'gpu', {\n get: () => options.defaultCoreDensity.gpu * 1, /* one GPU until we know better */\n set: (value) => {\n delete ret.gpu;\n ret.gpu = value;\n },\n configurable: true,\n enumerable: true,\n });\n\n if (options.cores?.cpu)\n ret.cpu = options.cores.cpu;\n if (options.cores?.gpu)\n ret.gpu = options.cores.gpu;\n\n return ret;\n }\n \n /**\n * Required initialization.\n * @param {SupervisorOptions} options\n */\n static defaultInitialization (options)\n {\n if (!options.defaultCoreDensity)\n options.defaultCoreDensity = { cpu: 1, gpu: 0.75 };\n if (!options.hardware)\n options.hardware = { vCores: 0 };\n if (!options.hardware.vCores)\n {\n switch(dcpEnv.platform)\n {\n case 'nodejs':\n options.hardware.vCores = requireNative('os').cpus().length;\n break;\n case 'vanilla-web':\n case 'bravojs':\n options.hardware.vCores = navigator.hardwareConcurrency; /* can be less than actual hardware capabilities */\n\n if (options.hardware.vCores > 2 && /(Android).*(Chrome|Chromium)/.exec(navigator.userAgent))\n options.hardware.vCores = 2; /* Restricted on Android-Chrome due to bad OOM-handling; see DCP-3241 */\n\n if (navigator?.deviceMemory)\n options.hardware.vCores = Math.min(options.hardware.vCores, navigator.deviceMemory * 4); /* Restrict to max one vCore per 256MB RAM */\n break;\n default:\n options.hardware.vCores = 1;\n }\n }\n }\n\n /**\n * Legacy from Sup1.\n * @todo XXXpfr Kill when Sup1 dies.\n * @returns {void|Promise<void>}\n */\n setDefaultIdentityKeystore ()\n {\n if (this.isSupervisor1)\n return this.#supervisor.setDefaultIdentityKeystore();\n }\n}\n\nexports.Worker = Worker;\nexports.disableWorker = disableWorker;\n\nexports.version = {\n api: '1.0.0',\n provides: '1.0.0' /* dcpConfig.scheduler.compatibility.operations.work */\n};\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/index.js?");
4607
+ eval("/**\n * @file worker/index.js This module implements the Worker API, used to create workers for \n * earning DCCs. \n *\n * To use Supervisor2 set the environment variable `USE_SUPERVISOR2` or \n * set dcpConfig.rollout.supervisor=2.\n *\n * @author Ryan Rossiter <ryan@kingsds.network>\n * Paul <paul@distributive.network>\n * @date May 2020\n * June, July 2022, Feb-April 2023\n * \n * @module dcp/worker\n * @access public\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n// @ts-check\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst { SchedMsg } = __webpack_require__(/*! dcp/dcp-client/schedmsg */ \"./src/dcp-client/schedmsg/index.js\");\nconst EventEmitter = __webpack_require__(/*! events */ \"./node_modules/events/events.js\");\nconst { localStorage } = __webpack_require__(/*! dcp/common/dcp-localstorage */ \"./src/common/dcp-localstorage.js\");\nconst { confirmPrompt } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { Address } = __webpack_require__(/*! dcp/dcp-client/wallet/eth */ \"./src/dcp-client/wallet/eth.js\");\nconst { Keystore } = __webpack_require__(/*! dcp/dcp-client/wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\");\nconst dcpEnv = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\nconst dcp_timers = __webpack_require__(/*! dcp/common/dcp-timers */ \"./src/common/dcp-timers.js\");\nconst DISABLE_WORKER_CACHE_KEY = 'disable_worker';\nvar Supervisor; /* Supervisor ctor - this will die when we kill sup1 */\n\n// Supervisor2 typedefs\n/** @typedef {import('./supervisor2/slice2').Slice} Slice */\n/** @typedef {import('./supervisor2/sandbox2').Sandbox} Sandbox */\n/** @typedef {import('./supervisor2/options').Options} Options */\n\n// Supervisor1 typedefs\n// /** @typedef {import('./sandbox').Sandbox} Sandbox */\n// /** @typedef {import('./slice').Slice} Slice */\n\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {import('dcp/dcp-client/wallet/eth').Address} Address */\n/** @typedef {import('dcp/dcp-client/wallet/keystore').Keystore} Keystore */\n/** @typedef {import('dcp/utils/jsdoc-types').SandboxOptions} SandboxOptions */\n/** @typedef {import('dcp/utils/jsdoc-types').SupervisorOptions} SupervisorOptions */\n/** @typedef {import('dcp/dcp-client/worker/origin-access-manager').OriginAccessManager} OriginAccessManager */\n\nfunction disableWorker ()\n{\n localStorage.setItem(DISABLE_WORKER_CACHE_KEY, true);\n}\n\n/** XXXwg remove this when we kill sup 1 */\nfunction determineSupervisorType()\n{\n if (Supervisor)\n return;\n\n if ( false\n || (__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").getenv)('USE_SUPERVISOR1')\n || (Number(dcpConfig.rollout?.supervisorVersion) === 1))\n Supervisor = (__webpack_require__(/*! ./supervisor */ \"./src/dcp-client/worker/supervisor.js\").Supervisor);\n else\n Supervisor = (__webpack_require__(/*! ./supervisor2 */ \"./src/dcp-client/worker/supervisor2/index.js\").Supervisor);\n}\n\n/**\n * Fired when the worker begins fetching slices from the scheduler.\n * @access public\n */\nclass Worker extends EventEmitter\n{\n /** Private class fields */\n #supervisor;\n #hasRef; /* event loop reference; undefined=>auto, false=>never, timer=>holding */\n\n /**\n * Returns a new Worker instance.\n * This constructor may alter the workerOptions object, for version-based polyfilling and/or providing\n * key API properties, such as the cores object.\n *\n * @access public\n * @param {Keystore} identity - identity of the Worker; used for signing messages to\n * task distributor etc.\n * @param {SupervisorOptions} workerOptions - options specified for the worker; may be mutated\n */\n constructor(identity, workerOptions)\n {\n super({ captureRejections: false });\n\n if (typeof workerOptions !== 'object')\n throw new Error('workerOptions argument must be object; is ' + typeof workerOptions);\n if (typeof identity !== 'object' || !(identity instanceof Keystore))\n throw new Error(`identity argument must be Keystore; is ${typeof identity === 'object' ? identity.constructor.name : typeof identity}`);\n\n determineSupervisorType();\n\n /**\n * @type {boolean}\n * @access public\n */\n this.working = false;\n /**\n * @type {SchedMsg}\n * @access public\n */\n this.schedMsg = new SchedMsg(this);\n /**\n * It is important that dynamic changes in workerOptions are reflected in Supervisor operations. In the case where\n * cores are derived solely from defaultCoreDensity, defaultCores provides an object which stays up to date with\n * defaultCoreDensity changes until a value is explicitly set.\n *\n * @type {SupervisorOptions}\n * @access public\n */\n this.workerOptions = workerOptions;\n if (!this.workerOptions.hasOwnProperty('cores') || !this.workerOptions.cores.hasOwnProperty('cpu'))\n this.workerOptions.cores = Worker.defaultCores(this.workerOptions);\n else\n Worker.defaultInitialization(this.workerOptions);\n if (this.workerOptions.hasOwnProperty('maxWorkingSandboxes') && this.workerOptions.maxWorkingSandboxes !== undefined)\n Worker.handleMaxWorkingSandboxes(this.workerOptions); /** XXXwg deprecate this API once all apps use sup2 conf */\n\n /* Correct cores object is now plumbed in; patently-absurd configs are changed to have 0 cores so users can debug */\n if (!(this.workerOptions.cores.cpu >= 0))\n this.workerOptions.cores.cpu = 0;\n if (!(this.workerOptions.cores.gpu >= 0))\n this.workerOptions.cores.gpu = 0;\n\n /**\n * @todo XXXpfr Rip out the sup2/sup1 special-casing when we finally kill sup1.\n * @type {Supervisor}\n * @access public\n */\n this.#supervisor = new Supervisor(this, identity, this.workerOptions);\n\n debugging() && console.debug(`Worker options(isSup2:${this.#supervisor.isSupervisor2}):`, this.workerOptions.cores.cpu, this.workerOptions.cores.gpu, this.workerOptions);\n }\n\n get supervisorVersion() { return this.#supervisor.version };\n\n /**\n * Disables worker instances from being started. The user will need to manually intervene to re-enable workers.\n * @access public\n */\n static disableWorker ()\n {\n disableWorker();\n }\n\n /**\n * Starts the worker.\n * @access public\n */\n async start()\n {\n debugging() && console.debug('Starting worker');\n if (this.working)\n {\n debugging() && console.debug('Worker already working, ignoring start request');\n return;\n }\n\n if (localStorage.getItem(DISABLE_WORKER_CACHE_KEY))\n {\n await confirmPrompt(`Worker has been disabled by the DCP Security Team; check the @DCP_Protocol Twitter feed for more information before continuing.`)\n if (await confirmPrompt('Are you sure you would like to restart the worker?'))\n {\n localStorage.removeItem(DISABLE_WORKER_CACHE_KEY);\n console.log(\"Starting worker...\");\n }\n else\n return;\n }\n \n try\n {\n let dontStart = false;\n\n this.emit('beforeStart', function cancel() { dontStart = true });\n if (dontStart)\n return;\n\n this.#supervisor.work();\n this.working = true;\n this.schedMsg.start();\n if (this.#hasRef !== false)\n this.ref();\n this.emit('start');\n }\n catch(error)\n {\n debugging() && console.error(error);\n this.emit('error', error);\n this.stop(true);\n }\n }\n\n /**\n * Stops the worker.\n * The reason for this.#hasRef === false state is for\n * await start\n * stop <-- #end isn't called until after the 2nd start\n * start\n * <-- #end is called here because #supervisor.stopWork is placed on the microtask queue.\n * @access public\n * @param {boolean} [forceTerminate=false] Whether the worker should stop imediately or allow the current slices to finish.\n */\n async stop(forceTerminate = false)\n {\n if (!this.working) \n {\n debugging() && console.debug('Worker already stopping, ignoring stop request');\n return;\n }\n this.working = false;\n\n try \n {\n // We're using synchronous event emitters, so if the abort function is called immediately,\n // then #supervisor.stopWork(forceTerminate) will be called with forceTerminate === true.\n this.emit('stop', function abort() { forceTerminate = true });\n\n debugging('shutdown') && console.debug(`Worker.stop(${forceTerminate}): stopping schedMsg...`);\n await this.schedMsg.stop();\n debugging('shutdown') && console.debug(`Worker.stop(${forceTerminate}): schedmsg stopped ok; stopping supervisor...`);\n\n return this.#supervisor.stopWork(forceTerminate).finally(() => {\n debugging('shutdown') && console.debug(`Worker.stop(${forceTerminate}): supervisor stopped ok`);\n this.#end();\n });\n }\n catch (error) \n {\n debugging() && console.error(error);\n this.emit('error', error);\n }\n }\n\n #end()\n {\n if (this.#hasRef !== false)\n {\n this.unref();\n this.ref();\n }\n this.emit('end');\n }\n \n /**\n * Set the worker's event loop reference to \"auto\" - the default behaviour - which holds a reference as\n * long as we are working.\n */\n ref()\n {\n if (this.#hasRef === false)\n this.#hasRef = undefined;\n if (this.#hasRef === undefined && this.working)\n {\n this.#hasRef = dcp_timers.setInterval(function workerRef(){}, 314159);\n debugging('refs') && console.debug('worker - making event-loop reference');\n }\n }\n\n /**\n * Tell the worker to not hold an event loop reference, whether it is working or not.\n */\n unref()\n {\n if (this.#hasRef)\n {\n dcp_timers.clearInterval(this.#hasRef);\n debugging('refs') && console.debug('worker - clearing event-loop reference');\n }\n this.#hasRef = false;\n }\n\n /**\n * We have been handed a v1-style configuration object, so we use it to generate v2-style info. Then\n * we add a getter/setter on maxWorkingSandboxes so that it always reflects cores.cpu, and so that its\n * mutations (from the worker-app) update cores.cpu.\n *\n * @deprecated\n * Get rid of this when all the workers are updated so that worker options do not have property maxWorkingSandboxes.\n * Please use options.defaultCoreDensity or options.cores instead.\n * @param {SupervisorOptions} options\n */\n static handleMaxWorkingSandboxes (options)\n {\n function translateMWStoCores(maxWorkingSandboxes)\n {\n maxWorkingSandboxes = Number(maxWorkingSandboxes);\n if (typeof maxWorkingSandboxes !== 'undefined')\n options.cores.cpu = maxWorkingSandboxes;\n debugging('maxWorkingSandboxes') && console.debug(`translateMWStoCores: options.cores.cpu = ${options.cores.cpu}`);\n }\n\n translateMWStoCores(options.maxWorkingSandboxes);\n\n /* Slide in a maxWorkingSandboxes property whose backing store is v2-style configuration info. This property\n * is used by Worker applications that don't understand v2-style configuration yet.\n */\n Object.defineProperty(options, 'maxWorkingSandboxes', {\n get: () => options.cores.cpu,\n set: (value) => translateMWStoCores(value),\n configurable: true,\n enumerable: true,\n });\n }\n\n /** @type {OriginAccessManager} */\n get originManager () { return this.#supervisor.originManager; }\n //\n // This is the official supported interface for accessing supervisor.\n // When Sup1 dies, it will be simplified.\n //\n /** @type {Sandbox[]} */\n get sandboxes () { return this.#supervisor.sandboxes; }\n /** @type {Sandbox[]} */\n get workingSandboxes () { return this.#supervisor.workingSandboxes; }\n /** @type {Slice[]} */\n get slices () { return this.#supervisor.slices; }\n /** @type {Slice[]} */\n get queuedSlices () { return this.#supervisor.queuedSlices; }\n /** @type {Slice[]} */\n get workingSlices () { return this.#supervisor.workingSlices; }\n /** @type {Keystore} */\n get identityKeystore () { return this.#supervisor.identityKeystore; }\n /** @type {Keystore} */\n set identityKeystore (ks)\n {\n if (!(ks instanceof Keystore))\n throw new Error('Worker.identityKeystore: must be an instance of Keystore.');\n this.#supervisor.identityKeystore = ks;\n this.emit('identityChange', ks);\n }\n\n /** \n * @deprecated - use options.paymentAddress\n * @type {Address} \n */\n get paymentAddress () { return this.workerOptions.paymentAddress; }\n\n /** \n * @deprecated - use options.paymentAddress\n * @type {Address} \n */\n set paymentAddress (addr)\n {\n if (!(addr instanceof Address))\n throw new Error('Worker.paymentAddress: must be an instance of Address.');\n this.workerOptions.paymentAddress = addr;\n this.emit('paymentAddressChange', addr);\n }\n\n /** @type {opaqueId} */\n get workerId () { return this.#supervisor.workerId; }\n /** @type {opaqueId} */\n set workerId (id) { this.#supervisor.workerId = id; }\n /** @type {boolean} */\n get isSupervisor1 () { return this.#supervisor.isSupervisor1; } // This will die when we kill sup1\n /** @type {boolean} */\n get isSupervisor2 () { return this.#supervisor.isSupervisor2; } // This will die when we kill sup1\n \n /**\n * @deprecated\n * Please do not use this.\n * @type {Supervisor}\n */\n get badSupervisorBackdoor () { return this.#supervisor; }\n /** @type {SupervisorOptions|Options} */\n get options () { return this.#supervisor.options; }\n /**\n * Set the debug flag on the event emitter superclass of both WOrker and this.supervisor.\n * @type {boolean}\n */\n set enableDebugEvents (value) { this.debug = this.#supervisor.debug = value; }\n /**\n * Tries to fetch a task from TD (task distributer).\n * @todo Get rid of this (XXXpfr)\n * @param {number} [numCores]\n * @returns {Promise<any>}\n */\n fetchTask (numCores)\n {\n return this.#supervisor.fetchTask();\n }\n /**\n * Remove sandbox from supervisor and terminate it.\n * @param {Sandbox} sandbox\n */\n returnSandbox (sandbox)\n {\n return this.#supervisor.returnSandbox(sandbox);\n }\n /**\n * Remove sandbox from supervisor and return it to the scheduler.\n * @param {Slice} slice\n * @param {string} reason\n * @returns {Promise<any>}\n */\n returnSlice (slice, reason = 'unknown')\n {\n return this.#supervisor.returnSlice(slice, reason);\n }\n /**\n * Listen to eventName with eventHandler.\n * @param {*} eventName\n * @param {*} eventHandler\n */\n addSupervisorEventListener (eventName, eventHandler)\n {\n this.#supervisor.addListener(eventName, eventHandler);\n }\n /**\n * Stop listenting to eventName with eventHandler.\n * @param {*} eventName\n * @param {*} eventHandler\n */\n removeSupervisorEventListener (eventName, eventHandler)\n {\n this.#supervisor.removeListener(eventName, eventHandler);\n }\n /**\n * Get the job descriptor for jobAddress;\n * viz., the object value corresponding to the key jobAddress,\n * in the object returned by getJobsForTask in task-jobs.js.\n * @param {string} jobAddress\n * @returns {object}\n */\n jobDescriptor (jobAddress)\n {\n return this.#supervisor.jobDescriptor(jobAddress);\n }\n\n /**\n * Build an object suitable for use as options.cores based on the presumed number of logical cores in\n * the machine, and options.defaultCoreDensity. This is the only place options.defaultCoreDensity\n * should be used, all other calculations throughout the worker should be based on options.cores. The\n * default defaultCoreDensity is usually supplied via dcpConfig.worker in dcp-worker repo's\n * etc/dcp-worker-config.js \n *\n * options.hardware can be used to override the detected number of cores\n *\n * @note We current assume the machine has one GPU of infinite capability, and use evaluator capability\n * detection to use or not use this amazing GPU. That will obviously change as our capabilities \n * evolves.\n *\n * @note There are very important notes re core-count selection in How-supervisor-works.md.\n *\n * @note This is a static property of Worker because it is based on the physical properties of the\n * machine and the options object and does not have any relationship to a particular instance\n * of Worker.\n *\n * @param {SupervisorOptions} options\n * @returns {{ cpu: number, gpu: number }}\n */\n static defaultCores (options)\n {\n Worker.defaultInitialization(options);\n\n /** Create an \"active\" return object suitable for use as options.cores. Any writes to this object\n * persist as-is, but in the usual case, reading from the object causes the values to change\n * when hardware.vCores and/or defaultCoreDensity change.\n * @type {{ cpu: number, gpu: number }}\n */\n var ret = { cpu: 0, gpu: 0 };\n\n Object.defineProperty(ret, 'cpu', {\n get: () => Math.floor(options.defaultCoreDensity.cpu * options.hardware.vCores),\n set: (value) => {\n delete ret.cpu;\n ret.cpu = value;\n },\n configurable: true,\n enumerable: true,\n });\n\n Object.defineProperty(ret, 'gpu', {\n get: () => options.defaultCoreDensity.gpu * 1, /* one GPU until we know better */\n set: (value) => {\n delete ret.gpu;\n ret.gpu = value;\n },\n configurable: true,\n enumerable: true,\n });\n\n if (options.cores?.cpu)\n ret.cpu = options.cores.cpu;\n if (options.cores?.gpu)\n ret.gpu = options.cores.gpu;\n\n return ret;\n }\n \n /**\n * Required initialization.\n * @param {SupervisorOptions} options\n */\n static defaultInitialization (options)\n {\n if (!options.defaultCoreDensity)\n options.defaultCoreDensity = { cpu: 1, gpu: 0.75 };\n if (!options.hardware)\n options.hardware = { vCores: 0 };\n if (!options.hardware.vCores)\n {\n switch(dcpEnv.platform)\n {\n case 'nodejs':\n options.hardware.vCores = requireNative('os').cpus().length;\n break;\n case 'vanilla-web':\n case 'bravojs':\n options.hardware.vCores = navigator.hardwareConcurrency; /* can be less than actual hardware capabilities */\n\n if (options.hardware.vCores > 2 && /(Android).*(Chrome|Chromium)/.exec(navigator.userAgent))\n options.hardware.vCores = 2; /* Restricted on Android-Chrome due to bad OOM-handling; see DCP-3241 */\n\n if (navigator?.deviceMemory)\n options.hardware.vCores = Math.min(options.hardware.vCores, navigator.deviceMemory * 4); /* Restrict to max one vCore per 256MB RAM */\n break;\n default:\n options.hardware.vCores = 1;\n }\n }\n }\n\n /**\n * Legacy from Sup1.\n * @todo XXXpfr Kill when Sup1 dies.\n * @returns {void|Promise<void>}\n */\n setDefaultIdentityKeystore ()\n {\n if (this.isSupervisor1)\n return this.#supervisor.setDefaultIdentityKeystore();\n }\n}\n\nexports.Worker = Worker;\nexports.disableWorker = disableWorker;\n\nexports.version = {\n api: '1.0.0',\n provides: '1.0.0' /* dcpConfig.scheduler.compatibility.operations.work */\n};\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/index.js?");
4608
4608
 
4609
4609
  /***/ }),
4610
4610
 
@@ -4735,7 +4735,7 @@ eval("/**\n * @file dcp/src/dcp-client/worker/supervisor2/index.js\n *
4735
4735
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4736
4736
 
4737
4737
  "use strict";
4738
- eval("/**\n * @file dcp-client/worker/supervisor2/job-manager.js\n *\n * A support class for Supervisor2.\n * It is a wrapper for the job object returned from the requestTask,\n * along with tracking slices and sandboxes associated with the job. \n *\n * @author Wes Garland, wes@distributive.network,\n * Paul, paul@distributive.network,\n * @date Dec 2020,\n * June 2022, Jan-April 2023\n * @module job-manager\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n// @ts-check\n\n\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {string} address */ // String(Address)\n/** @typedef {import('./sandbox2').Sandbox} Sandbox */\n/** @typedef {import('./index').Supervisor} Supervisor */\n/** @typedef {import('dcp/utils/jsdoc-types').SliceMessage} SliceMessage */\n/** @typedef {import('dcp/utils/jsdoc-types').Auth} Auth */\n/** @typedef {import('dcp/utils/jsdoc-types').Signature} Signature */\n/** @typedef {import('dcp/utils/jsdoc-types').AuthMessage} AuthMessage */\n/** @typedef {import('dcp/dcp-client/worker/origin-access-manager').OriginAccessManager} OriginAccessManager */\n\nconst inspect = Symbol.for('nodejs.util.inspect.custom');\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst EventEmitter = __webpack_require__(/*! events */ \"./node_modules/events/events.js\");\nconst { Inventory } = __webpack_require__(/*! dcp/utils/inventory */ \"./src/utils/inventory.js\");\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { rehydrateRange } = __webpack_require__(/*! dcp/dcp-client/range-object */ \"./src/dcp-client/range-object.js\");\nconst { fetchURIAndLength } = __webpack_require__(/*! dcp/utils/fetch-uri */ \"./src/utils/fetch-uri.js\");\nconst utils = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { stringify, a$sleepMs, constructAlpha, nextEma } = utils;\nconst { allowOriginsPurposes } = __webpack_require__(/*! dcp/common/worker-constants */ \"./src/common/worker-constants.js\");\n\nconst { Slice } = __webpack_require__(/*! ./slice2 */ \"./src/dcp-client/worker/supervisor2/slice2.js\");\nconst { Promise_any } = __webpack_require__(/*! ./promise_any */ \"./src/dcp-client/worker/supervisor2/promise_any.js\");\nconst { Statistics } = __webpack_require__(/*! ./rolling-statistics */ \"./src/dcp-client/worker/supervisor2/rolling-statistics.js\");\nconst { SandboxError, UncaughtExceptionError } = __webpack_require__(/*! ./sandbox2 */ \"./src/dcp-client/worker/supervisor2/sandbox2.js\");\nconst { updateGPUStatistics } = __webpack_require__(/*! ./gpu_support */ \"./src/dcp-client/worker/supervisor2/gpu_support.js\");\nconst common = __webpack_require__(/*! ./common */ \"./src/dcp-client/worker/supervisor2/common.js\");\nconst { selectiveDebug, selectiveDebug2, debugBuild } = common;\n\nconst INITIAL = 'initial';\nconst READY = 'ready';\nconst STOP = 'stop';\nconst REFUSE = 'refuse';\nconst BROKEN = 'broken';\n\nclass BadOriginError extends SandboxError { constructor(msg) { super('EPERM_ORIGIN', msg); } }\nclass RemoteFetchError extends SandboxError { constructor(msg) { super('EFETCH', msg); } }\n\n/**\n * Public event emitter.\n * https://gitlab.com/Distributed-Compute-Protocol/dcp-docs-wes/-/blob/wip/worker/worker-events.md\n */\nclass JobHandle extends EventEmitter\n{\n /** @type {JobManager} */\n #jobManager;\n \n /**\n * @constructor\n * @param {JobManager} jobManager\n */\n constructor (jobManager)\n {\n super({ captureRejections: false });\n this.#jobManager = jobManager;\n }\n /** @type {string} */\n get address () { return this.#jobManager.address; }\n /** @type {string} */\n get name () { return this.#jobManager.public ? this.#jobManager.public.name : '<unknown>'; }\n /** @type {string} */\n get description () { return this.#jobManager.public ? this.#jobManager.public.description : '<unknown>'; }\n /** @type {string} */\n get link () { return this.#jobManager.public ? this.#jobManager.public.link : '<unknown>'; }\n}\nexports.JobHandle = JobHandle;\n\n//\n// Index to functionality -- search for '_Idx' to toggle through the index.\n//\n// 1) class JobManager\n// 2) addSlices, fetchDependencies, updateStatistics, update, predictLoad\n// 3) reserveOneSlice, resetSlices, destroy, evaluatorDownCleanup\n// 4) assignSandbox, runSlice, runSliceOnSandbox\n// 5) removeSlice\n// 6) fetchError, fetchJob, fetchSliceData\n// 7) jobEmit, sandboxEmit, error, warning. countSliceStr, dumpSlices.\n//\n\n// _Idx\n//\n// class JobManager\n// A JobManager handles all scheduling of slices for a given job.\n// It's also responsible for fetching slice data, work functions and arguments.\n// And it collects statistics about slice completion times and resource usage.\n// All functionality across jobs is handled by Supervisor.\n//\n\n/**\n * JobManager Constructor. An instance of JobManager knows everything about a given\n * job within the supervisor, including:\n * - work function code\n * - work function arguments\n * - all the SliceManager instances that go with this job\n * - how long a slice of this job is expected to run\n *\n * Instances of JobManager emit the following events:\n * - addSlice(sliceHandle)\n * - deleteSlice(sliceHandle)\n * - statusChange(new state, old state);\n *\n * JobManager States\n *\n * Start state:\n * initial\n *\n * Intermediate states:\n * ready\n *\n * Terminal states:\n * broken - job could not be initialized\n * refuse - for some reason, we have decided that we don't want work for this job\n * stop - job manager has been stopped\n *\n * Valid transitions:\n * initial -> broken\n * initial -> ready\n * initial -> ready -> stop\n * \n * NOTE: If you ever use a property with a leading underscore you are probably making a mistake.\n * But if you must, please ask paul, yarn, bryan or eddie for a CR.\n */\nclass JobManager\n{\n /** @type {Supervisor} */\n #supervisor;\n /** @type {Slice[]} */\n #sliceInventory;\n /** @type {Synchronizer} */\n #state;\n /** @type {*} */ // Figure out the type\n #jobMessage;\n /** @type {string} */\n #address;\n /** @type {Statistics} */\n #statistics;\n /** @type {Promise<*>} */\n #jobPromise;\n\n /**\n * @constructor\n * @param {Supervisor} parent - Owning Supervisor.\n * @param {object} jobMessage - Job Descriptor from getJobsForTask.\n * @param {SliceMessage[]} sliceMessages - Messages from task distributor describing slices.\n * @param {AuthMessage} authorizationMessage - The signature that shipped with the task authorizing this worker.\n */\n constructor(parent, jobMessage, sliceMessages, authorizationMessage)\n {\n this.#supervisor = parent;\n this.#sliceInventory = []; // All slices for this.address.\n this.#state = new Synchronizer(INITIAL, [ INITIAL, READY, STOP, REFUSE, BROKEN ]);\n this.#jobMessage = { ...jobMessage };\n this.#address = String(this.jobMessage.address);\n this.#statistics = new Statistics(0.5); // Effective period of 12 or 13\n this.#jobPromise = null;\n\n /** @type {{CPUTime: number, GPUTime: number, TotalTime: number, CPUDensity: number, GPUDensity: number, InDataSize: number, OutDataSize: number }} */\n this.localMetrics = null;\n /** @type {string[]} */\n this.rejectedJobReasons = [];\n /** @type {boolean} */\n this.isEstimation = true;\n /** @type {number} */\n this.inputDataSize = 0;\n /**\n * jobMan.evaluatorDownCleaup returns this.evaluatorDownHandle=a$sleepMs which is\n * interruptible by calling this.evaluatorDownHandle.intr().\n * @type {Promise<void>}\n */\n this.evaluatorDownHandle = null;\n\n /*\n * Make sure this.metrics contains valid data.\n * With old legacy schedulers, this may not be so.\n */\n this.fixUpMetrics();\n\n /** \n * Start loading dependencies in the background. Once these are loaded, this.state will \n * transition to 'ready' and the job will be ready for work.\n */\n this.#jobPromise = this.fetchDependencies(sliceMessages, authorizationMessage);\n\n /* Check for GPU support. */\n const _env = this.requirements?.environment;\n /** @type {boolean} */\n this.useGPU = _env ? (_env.webgpu || _env.offscreenCanvas) : false;\n \n /**\n * Event emitter containing info that describes the job.\n * @type {JobHandle}\n */\n this.jobHandle = new JobHandle(this);\n\n /**\n * Emit the 'job' event on the worker event emitter.\n */\n this.supervisor.safeEmit(this.supervisor.worker, 'job', this.jobHandle);\n }\n\n /** @type {*} */ // Figure out the type\n get jobMessage () { return this.#jobMessage; }\n /** @type {string} */\n get address () { return this.#address; }\n /** @type {string} */\n get uuid () { return this.jobMessage.uuid; }\n /** @type {boolean} */\n get workerConsole () { return this.jobMessage.workerConsole; }\n /** @type {object} */\n get requirements () { return this.jobMessage.requirements; }\n /** @type {boolean} */\n get almostDone () { return this.jobMessage.almostDone; }\n\n // These 3 properties have type object.\n get mro () { return this.jobMessage.mro; }\n get arguments () { return this.jobMessage.arguments; }\n get workFunction () { return this.jobMessage.workFunction; }\n\n /** @type {{computeGroups: Array<{opaqueId: string, name: string, description: string}>, name: string, description: string, link: string}} */\n get public () { return this.jobMessage.public; }\n /** @type {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number, sliceInDataSize: number, sliceOutDataSize: number, lastSliceNumber: number, measuredSlices: number, alpha: number}} */\n get metrics () { return this.jobMessage.metrics; }\n /** @type {Supervisor} */\n get supervisor () { return this.#supervisor; }\n /** @type {Synchronizer} */\n get state () { return this.#state; }\n\n /** @type {boolean} */\n get ready () { return this.state.is(READY); }\n\n // These 4 aren't needed yet.\n ///** @type {boolean} */\n //get initial () { return this.state.is(INITIAL); }\n ///** @type {boolean} */\n //get stopped () { return this.state.is(STOP); }\n ///** @type {boolean} */\n //get refuse () { return this.state.is(REFUSE); }\n ///** @type {boolean} */\n //get broken () { return this.state.is(BROKEN); }\n\n /** @type {Sandbox[]} */\n get sandboxInventory () { return this.supervisor.sandboxInventory.filter((sandbox) => sandbox.jobAddress === this.#address && !sandbox.isTerminated); }\n /** @type {Sandbox[]} */\n get assignedSandboxes () { return this.supervisor.sandboxInventory.filter((sandbox) => sandbox.jobAddress === this.#address && sandbox.isAssigned); }\n /** @type {Sandbox[]} */\n get workingSandboxes () { return this.supervisor.sandboxInventory.filter((sandbox) => sandbox.jobAddress === this.#address && sandbox.isWorking); }\n /** @type {Slice[]} */\n get sliceInventory () { return this.#sliceInventory; }\n /** @type {Slice[]} */\n get readySlices () { return this.sliceInventory.filter((slice) => slice.isReady); }\n /** @type {Slice[]} */\n get queuedSlices () { return this.sliceInventory.filter((slice) => slice.isQueued); } // Ready and soon-to-be-ready\n /** @type {Slice[]} */\n get reservedSlices () { return this.sliceInventory.filter((slice) => slice.isReserved); }\n /** @type {Slice[]} */\n get activeSlices () { return this.sliceInventory.filter((slice) => slice.isActive); } // Reserved, working, workdone and complete\n /** @type {Slice[]} */\n get workingSlices () { return this.sliceInventory.filter((slice) => slice.isWorking || slice.isReserved); } // Working and soon-to-be-working\n /** @type {Slice[]} */\n get unFinishedSlices () { return this.sliceInventory.filter((slice) => !slice.isFinished); }\n\n /** @type {Statistics} */\n get statistics () { return this.#statistics; }\n /** @type {Promise<*>} */\n get jobPromise () { return this.#jobPromise; }\n /** @type {number} */\n get globalTime () { return utils.globalTime(this.metrics); }\n /** @type {number} */\n get emaSliceTime () { return this.localMetrics ? utils.localTime(this.localMetrics) : (utils.globalTime(this.metrics) * this.supervisor.defaultQuanta); }\n /** @type {number} */\n get emaTotalTime () { return this.localMetrics?.TotalTime || (utils.globalTotalTime(this.metrics) * this.supervisor.defaultQuanta); }\n /** @type {number} */\n get adjSliceTime () { return Math.min(this.emaSliceTime, this.supervisor.options.targetTaskDuration); }\n /** @type {string} */\n get identifier () { return `${common.truncateAddress(this.address)}.${this.state}`; }\n /** @type {string} */\n get sliceState () { return `${this.queuedSlices.length}.${this.workingSliceCount}.${this.sliceInventory.length}.${this.workRemaining}`; }\n /** @type {string} */\n get [inspect] () { return `[Object JobManager <${this.public.name}::${common.truncateAddress(this.address)}::${this.state}>]`; }\n /** @type {OriginAccessManager} */\n get originManager() { return this.supervisor.originManager; }\n /**\n * Expected density of a slice.\n * When we get the GPU stuff codified, the definition might be\n * { cpu: this.localMetrics?.CPUDensity ?? 1.0, gpu: this.localMetrics?.GPUDensity ?? 1.0}\n * @type {number}\n */\n get estimateDensity () { return this.localMetrics?.CPUDensity || utils.globalDensity(this.metrics); }\n /** @type {number} */\n get estimateGPUDensity () { return this.localMetrics?.GPUDensity || utils.globalGPUDensity(this.metrics); }\n /** @type {number} */\n get cpuTime () { return this.localMetrics?.CPUTime || this.metrics?.sliceCPUTime || 0; }\n /**\n * Estimate the number of milliseconds of total execution time of the job.\n * @type {number}\n */\n get workRemaining ()\n {\n const _adjSliceTime = this.adjSliceTime;\n const queuedTime = _adjSliceTime * this.queuedSlices.length;\n let workingTime = 0;\n for (const slice of this.workingSlices)\n {\n workingTime += Math.max(0, _adjSliceTime - (Date.now() - slice.startTime));\n selectiveDebug2() && console.debug(`JobManager.workRemaining: _adjSliceTime ${_adjSliceTime}, slice-time-elapsed ${Date.now() - slice.startTime}`);\n }\n selectiveDebug() && console.debug(`JobManager.workRemaining: queuedTime ${queuedTime}, queuedCount ${this.queuedSlices.length}, workingTime ${workingTime}, workingCount ${this.workingSliceCount}`);\n return workingTime + queuedTime;\n }\n /**\n * Are there no working slices?\n * @type {boolean}\n */\n get isNotWorking ()\n {\n for (const slice of this.sliceInventory)\n if (slice.isReserved || slice.isWorking)\n return false;\n return true;\n }\n /**\n * Estimated count of working (or reserved) slices.\n * @type {number}\n */\n get workingSliceCount ()\n {\n let count = 0;\n for (const slice of this.sliceInventory)\n if (slice.isReserved || slice.isWorking)\n ++count;\n return count;\n }\n /**\n * Estimated density of working (or reserved) slices.\n * @type {number}\n */\n get workingSliceDensity () { return this.workingSliceCount * this.estimateDensity; }\n /**\n * Estimated density of working (or reserved) slices.\n * @type {number}\n */\n get workingGPUDensity () { return this.workingSliceCount * this.estimateGPUDensity; }\n /**\n * Always display max info under debug builds, otherwise maximal error\n * messages are displayed to the worker, only if both worker and client agree.\n * When displayMaxDiagInfo is true, display stack trace and other enhanced diag info.\n * @type {boolean}\n **/\n get displayMaxDiagInfo ()\n {\n return common.displayMaxDiagInfo() || this.workerConsole && this.supervisor.options.allowConsoleAccess;\n }\n\n // _Idx\n //\n // addSlices, fetchDependencies, updateStatistics, update, predictLoad\n //\n\n /**\n * Add slices to the job manager's inventory.\n *\n * @param {SliceMessage[]} sliceMessages - Messages from task distributor describing slices.\n * @param {AuthMessage} authorizationMessage - The signature that shipped with the task authorizing this worker.\n * @returns {Promise<*>}\n */\n addSlices (sliceMessages, authorizationMessage)\n {\n /** @type {Array<Promise<void>>} */\n const slicePromises = [];\n sliceMessages.forEach((sliceMessage) => {\n const slice = new Slice(this, sliceMessage, authorizationMessage);\n if (!slice.isEstimation)\n this.isEstimation = false;\n this.sliceInventory.push(slice);\n slicePromises.push(slice.fetchSliceDataPromise);\n });\n return Promise_any(slicePromises);\n }\n\n /**\n * Fetch dependencies.\n * @param {SliceMessage[]} sliceMessages - Messages from task distributor describing slices.\n * @param {AuthMessage} authorizationMessage - The signature that shipped with the task authorizing this worker.\n * @returns {Promise<*>}\n */\n fetchDependencies (sliceMessages, authorizationMessage)\n {\n return this.fetchJob(this.jobMessage)\n .then (() => {\n debugging('supervisor') && console.debug('JobManager is transitioning to READY', this.identifier, Date.now() - this.supervisor.lastTime);\n this.state.set(INITIAL, READY);\n return this.addSlices(sliceMessages, authorizationMessage);\n }, (error) => {\n selectiveDebug() && console.error('fetchJob has failed', error);\n this.state.set(INITIAL, BROKEN);\n throw error;\n });\n }\n\n /** XXXpfr @todo Factor out statistics and EMA into another class. */\n /**\n * @param {number} x\n * @param {boolean} isDensity\n * @returns {boolean}\n */\n static isValid (x, isDensity = false)\n {\n return Number.isFinite(x) && !(x < 0) && !(isDensity && x > 1);\n }\n\n /**\n * Old legacy DCP schedulers can send a corrupt this.metrics .\n * We should get rid of this when we don't support old legacy DCP schedulers.\n */\n fixUpMetrics ()\n {\n if (!this.metrics)\n this.jobMessage.metrics = { sliceCPUTime: 0, sliceCPUDensity: 1, sliceGPUTime: 0, sliceGPUDensity: 0, sliceInDataSize: 0, sliceOutDataSize: 0 };\n else\n {\n if (!(JobManager.isValid(this.metrics.sliceCPUTime) && JobManager.isValid(this.metrics.sliceCPUDensity, true)))\n {\n this.metrics.sliceCPUTime = 1;\n this.metrics.sliceCPUDensity = 0.5;\n }\n if (!(JobManager.isValid(this.metrics.sliceGPUTime) && JobManager.isValid(this.metrics.sliceGPUDensity, true)))\n {\n this.metrics.sliceGPUTime = 1;\n this.metrics.sliceGPUDensity = 0.5;\n }\n if (!JobManager.isValid(this.metrics.sliceInDataSize))\n this.metrics.sliceInDataSize = 0;\n if (!JobManager.isValid(this.metrics.sliceOutDataSize))\n this.metrics.sliceOutDataSize = 0;\n }\n }\n\n /**\n * @param {Slice} slice\n * @param {Sandbox} sandbox\n * @returns {{CPUTime: number, GPUTime: number, CPUDensity: number, GPUDensity: number, InDataSize: number, OutDataSize: number}}\n */\n updateStatistics (slice, sandbox)\n {\n /**\n * @todo XXXpfr Make this only check when debugBuild, but not until the Sup2 churn has stopped.\n * @param {{CPUTime: number, GPUTime: number, CPUDensity: number, GPUDensity: number, InDataSize: number, OutDataSize: number}} metrics\n * @returns {boolean}\n */\n const isValidMetrics = (metrics) => {\n return JobManager.isValid(metrics.CPUTime) && JobManager.isValid(metrics.CPUDensity, true)\n && JobManager.isValid(metrics.GPUTime) && JobManager.isValid(metrics.GPUDensity, true)\n && JobManager.isValid(metrics.InDataSize) && JobManager.isValid(metrics.OutDataSize);\n }\n\n const timeReport = slice.timeReport;\n const dataReport = slice.dataReport;\n selectiveDebug() && console.debug('updateStatistics', slice.identifier, timeReport, dataReport);\n\n // Sanity\n if (!(timeReport.CPU >= 1))\n {\n timeReport.CPU = 1;\n timeReport.total += 1;\n }\n if (!(timeReport.total >= 1))\n timeReport.total = 1;\n\n // Construct metrics.\n const GPUTime = timeReport.webGPU + timeReport.webGL;\n if (this.useGPU)\n updateGPUStatistics(GPUTime);\n const metrics = {\n CPUTime: timeReport.CPU,\n GPUTime,\n CPUDensity: timeReport.CPU / timeReport.total,\n GPUDensity: GPUTime / timeReport.total,\n InDataSize: dataReport.InDataSize,\n OutDataSize: dataReport.OutDataSize,\n };\n //console.debug('JM.updateStatistics metrics:', metrics, timeReport.total);\n\n // Create the measurement object which will be passed to the metrics event.\n const eventMeasurements = {\n elapsed: timeReport.total / 1000,\n CPU: metrics.CPUTime / 1000,\n GPU: metrics.GPUTime / 1000,\n in: metrics.InDataSize,\n out: metrics.OutDataSize,\n };\n\n // Emit metrics event for both jobHandle and sandboxHandle\n this.jobEmit( 'metrics', slice.sliceNumber, eventMeasurements);\n this.sandboxEmit(sandbox, 'metrics', slice.sliceNumber, eventMeasurements);\n selectiveDebug() && console.debug(`updateStatistics: slice ${slice.identifier}, eventMeasurements ${stringify(eventMeasurements)}`);\n\n if (!isValidMetrics(metrics))\n throw new Error(`JobManager.updateStatistics: metrics are in an inconsistent state ${JSON.stringify(metrics)}`);\n\n if (!this.localMetrics)\n this.localMetrics = { CPUTime: 0, GPUTime: 0, TotalTime: 0, CPUDensity: 0, GPUDensity: 0, InDataSize: 0, OutDataSize: 0 };\n\n // measuredSlices+1 because the current slice hasn't been measured yet in RS.updateMetrics.\n const alpha = this.metrics?.lastSliceNumber\n ? constructAlpha(this.metrics.lastSliceNumber, this.metrics.measuredSlices + 1)\n : 0.5;\n selectiveDebug2() && console.debug(`updateStatistics: local alpha ${alpha}, global alpha ${this.metrics?.alpha}`);\n this.localMetrics.CPUTime = nextEma(this.localMetrics.CPUTime, metrics.CPUTime, alpha);\n this.localMetrics.GPUTime = nextEma(this.localMetrics.GPUTime, metrics.GPUTime, alpha);\n this.localMetrics.TotalTime = nextEma(this.localMetrics.TotalTime, timeReport.total, alpha);\n this.localMetrics.CPUDensity = nextEma(this.localMetrics.CPUDensity, metrics.CPUDensity, alpha);\n this.localMetrics.GPUDensity = nextEma(this.localMetrics.GPUDensity, metrics.GPUDensity, alpha);\n this.localMetrics.InDataSize = nextEma(this.localMetrics.InDataSize, metrics.InDataSize, alpha); // Don't need this yet.\n this.localMetrics.OutDataSize = nextEma(this.localMetrics.OutDataSize, metrics.OutDataSize, alpha); // Don't need this yet.\n\n if (common.debugQuanta())\n {\n this.supervisor.dbg.addRawLocal(slice, metrics, alpha);\n this.supervisor.dbg.addLocal(slice, this.emaSliceTime, alpha);\n }\n\n if (!isValidMetrics(this.localMetrics))\n throw new Error(`JobManager.updateStatistics: localMetrics are in an inconsistent state ${JSON.stringify(this.localMetrics)}`);\n\n debugging('supervisor') && console.debug('updateStatistics: ema', this.statistics.ema, 'emaSliceTime', this.emaSliceTime, 'estimateDensity', this.estimateDensity, 'metrics', metrics, 'g-metrics', this.metrics, 'alpha', alpha);\n //if (!Number.isFinite(this.statistics.ma))\n // console.log(this.statistics.x);\n return metrics;\n }\n\n /**\n * Update jobMessage, add some slices to inventory and possibly update the initial seed of the statistics.\n * @param {object} jobMessage - Job Descriptor from getJobsForTask.\n * @param {SliceMessage[]} [sliceMessages] - Messages from task distributor describing slices.\n * @param {AuthMessage} [authorizationMessage] - The signature that shipped with the task authorizing this worker.\n * @returns {Promise<*>}\n */\n update (jobMessage, sliceMessages, authorizationMessage)\n {\n Object.assign(this.jobMessage, jobMessage);\n /*\n * Make sure this.metrics contains valid data.\n * With old legacy schedulers, this may not be so.\n */\n this.fixUpMetrics();\n selectiveDebug2() && console.debug('JM.update: new this.jobMessage', this.jobMessage.metrics);\n\n if (sliceMessages)\n return (this.#jobPromise = this.addSlices(sliceMessages, authorizationMessage));\n }\n\n /**\n * Predict the total reduction in density of working sandboxes timeSpanMs from now.\n * This function is called right before fetchTask, in order to calculate how much space is available.\n * In pratice, the returned property queued will have length <= 1, because when it is more we won't call fetchWork.\n * @param {number} timeSpanMs\n * @returns {{ queued: Slice[], working: number }}\n */\n predictLoad (timeSpanMs)\n {\n let queuedCount = this.queuedSlices.length;\n // Optimize to short-circuit when queued > 1, because we won't call fetchWork in that case.\n if (queuedCount > this.workingSliceCount + 1)\n {\n selectiveDebug() && console.debug('JM.predictLoad: bail early', queuedCount, this.workingSliceCount);\n return { queued: [null, null], working: this.workingSliceDensity };\n }\n let space = 0;\n // 1) Predict the current working slices that will finish in timeSpanMs.\n // 2) Compensate for queued slices that may begin working.\n for (const slice of this.sliceInventory)\n {\n if (!slice.isReserved && !slice.isWorking)\n continue;\n const remainingTime = Math.max(0, this.emaSliceTime - (Date.now() - slice.startTime));\n selectiveDebug2() && console.debug('JM.predictLoad: predicting...', queuedCount, space, this.emaSliceTime, remainingTime, this.emaSliceTime <= timeSpanMs - remainingTime);\n // If slice will end before timeSpanMs elapses, it may open up space.\n if (remainingTime <= timeSpanMs)\n {\n // Check to see whether a new queued slices can start running and finish in the remaining time.\n // Somewhat inaccurate because it's possible multiple slices could finish in the remaining time.\n // e.g. Suppose timeSpanMs = 5 * 1000 and this.emaSliceTime is 1000, then there's room\n // to successively schedule 5 slices, one after another.\n /** XXXpfr @todo Fix to incorporate successive scheduling. */\n if (queuedCount > 0)\n {\n --queuedCount;\n // If the new slice finishes in time, count it as opening up space.\n if (this.emaSliceTime <= timeSpanMs - remainingTime)\n ++space;\n }\n else // No queued slice can run so space has opened up.\n ++space;\n }\n }\n // Optimize to short-circuit when queued > 1, because we won't call fetchWork in that case.\n let queued = [null, null];\n if (queuedCount < 2)\n queued = (queuedCount === 1) ? [this.queuedSlices[0]] : [];\n return { queued, working: (this.workingSliceCount - space) * this.estimateDensity };\n }\n\n // _Idx\n //\n // reserveOneSlice, resetSlices, destroy, evaluatorDownCleanup\n //\n\n /**\n * Find one ready slice, mark it as working and return.\n * If there are no ready slices return undefined.\n * @returns {Slice}\n */\n reserveOneSlice ()\n {\n for (const slice of this.sliceInventory)\n if (slice.isReady)\n return slice.markAsReserved();\n return null;\n }\n\n /**\n * Error recovery of slices.\n * When evaluator is down and it goes up again, there may be\n * working slices from working sandboxes that were terminated.\n * @param {string} [tag]\n */\n resetSlices (tag)\n {\n this.sliceInventory.forEach((slice) => {\n if (this.evaluatorDownHandle)\n this.evaluatorDownHandle.intr(); // Interrupt a$sleep in evaluatorDownCleanup\n this.evaluatorDownHandle = null;\n if (slice.isWorking || slice.isFailed)\n slice.resetState();\n });\n //this.dumpSlices(tag); // SAVE\n }\n\n /**\n * Destructor.\n * @returns {Promise<*>}\n */\n destroy ()\n {\n selectiveDebug() && console.debug(`JobManager.destroy: terminating sandboxes and returning slices to scheduler for job manager ${this.identifier}.`);\n this.sandboxInventory.forEach((sandbox) => { this.supervisor.returnSandbox(sandbox); });\n const reason = 'JobManager destroy';\n return this.supervisor.returnSlices(this.unFinishedSlices, reason)\n .finally (() => {\n this.#sliceInventory = [];\n this.state.removeAllListeners();\n if (this.state.isNot(BROKEN))\n this.state.set([ INITIAL, READY, STOP, REFUSE ], BROKEN);\n this.jobEmit('flush');\n });\n }\n\n /**\n * Destructor.\n * Called when the evaluator goes down (or screensaver goes up), in the sandbox 'terminated' event handler in Supervisor.\n * A slice marked as COMPLETE but not FINISHED means it still needs to submit results.\n * This function allows such slices to still submit results.\n * Because sometimes a screensaver only goes down for a minute or less (e.g. the user needs to look at something quick),\n * we delay an average of 90 (or 1.5 * delay) seconds before returning non-complete slices.\n * param {number} [delay=60] - Average delay is 1.5 * delay seconds.\n */\n evaluatorDownCleanup (delay = 60)\n {\n selectiveDebug() && console.debug(`JobManager.evaluatorDownCleanup: delay returning non-complete slices for on average ${1.5 * delay} seconds, ${this.identifier}.`);\n if (this.sandboxInventory.length > 0)\n this.warning('JobManager.evaluatorDownCleanup: When the evaluator is down there should not be any non-terminated sandboxes left.');\n // Call retVal.intr() to interrupt and cancel the timer inside a$sleepMs.\n return (this.evaluatorDownHandle = a$sleepMs(delay * (1.0 + Math.random()) * 1000)\n .finally(() => {\n const slicesToReturn = this.sliceInventory.filter((slice) => !(slice.isComplete || slice.isFinished));\n const reason = 'JobManager evaluatorDownCleanup';\n return this.supervisor.returnSlices(slicesToReturn, reason);\n }));\n }\n\n // _Idx\n //\n // assignSandbox, runSlice, runSliceOnSandbox\n // Functions chain and return non-awaited promises.\n //\n\n /**\n * Create a Sandbox.\n * Start it. Assign it. Add it to jobManager inventory.\n * @param {Slice} slice\n * @returns {Promise<Sandbox>}\n */\n async assignSandbox (slice)\n {\n try\n {\n // We want to await createSandbox so we don't create a large number of sandboxes all at once.\n const sandbox = await this.supervisor.createSandbox()\n .catch((error) => {\n // Catches exception from createSandbox.\n // Shound be rare unless evaluator or screensaver goes down.\n selectiveDebug2() && !this.supervisor.evaluator.down && console.debug('assignSandbox: createSandbox error', slice.identifier, error);\n slice.unReserve(); // reuse slice\n throw error;\n });\n selectiveDebug2() && console.debug('assignSandbox', slice.identifier);\n sandbox.slice = slice; // Must be before sandbox.assign; cf. fetchModule exception in handleRing2Message.\n return sandbox.assign(this)\n .then((assignedSandbox) => {\n selectiveDebug2() && console.debug('assignSandbox: success', sandbox.identifier);\n this.supervisor.sandboxInventory.push(assignedSandbox);\n return assignedSandbox;\n })\n .catch((error) => {\n // Exception is from sandbox.assign.\n // It could be a string coming from evaluator.\n this.error(`Failed to assign job to sandbox ${sandbox.identifier}`, error);\n slice.unReserve(); // reuse slice\n throw error;\n });\n }\n catch (error)\n {\n this.error(`JobManager.assignSandbox failed to find sandbox to run slice ${slice.identifier}`, error);\n throw error;\n }\n }\n\n /**\n * Create or reuse a sandbox and run a slice on it.\n * @param {Slice} slice - The slice to execute on a sandbox.\n * @returns {Promise<any>}\n */\n runSlice (slice)\n {\n try\n {\n selectiveDebug() && console.debug('runSlice:', slice.identifier, 'assigned/working', this.assignedSandboxes.length, this.workingSandboxes.length);\n\n if (this.supervisor.sliceTiming)\n slice['queueingDelta'] = Date.now();\n\n const _assignedSandboxes = this.assignedSandboxes;\n if (_assignedSandboxes.length > 0)\n {\n const sandbox = _assignedSandboxes[0];\n selectiveDebug2() && console.debug(`runSlice(assigned): sandbox ${sandbox.identifier}`, Date.now() - this.supervisor.lastTime);\n // When evaluator goes down, all sandboxes are terminated.\n // A ready slice has a finite lifetime and if exceeded may transition back to READY, in which\n // case we bail since it might already be in the process of being selected in roundRobinSlices.\n if (!slice.markAsWorking() || sandbox.isTerminated)\n return Promise.resolve();\n sandbox.markAsWorking();\n sandbox.slice = slice; // Reusing assigned sandbox, so set the slice.\n return this.runSliceOnSandbox(slice, sandbox)\n .catch((error) => {\n // Catches exception from runSliceOnSandbox; s.b. rare.\n this.error(`JobManager.runSlice: Failure trying to run slice ${slice.identifier} on a assigned sandbox.`, error);\n return this.supervisor.returnSlice(slice, 'JobManager.runSlice');\n });\n }\n\n return this.assignSandbox(slice)\n .then((sandbox) => {\n selectiveDebug2() && console.debug(`runSlice: sandbox ${sandbox.identifier}`, Date.now() - this.supervisor.lastTime);\n // When evaluator goes down, all sandboxes are terminated.\n // A ready slice has a finite lifetime and if exceeded may transition back to READY, in which\n // case we bail since it might already be in the process of being selected in roundRobinSlices.\n if (!slice.markAsWorking() || sandbox.isTerminated)\n return Promise.resolve();\n sandbox.markAsWorking();\n return this.runSliceOnSandbox(slice, sandbox);\n }, (error) => {\n // Catches exception from assignSandbox for error control flow.\n selectiveDebug() && console.error(`JobManager.runSlice: Failure from assignSandbox when trying to run slice ${slice.identifier} on a sandbox.`, error);\n })\n .catch((error) => {\n // Catches exception from runSliceOnSandbox; s.b. rare.\n this.error(`JobManager.runSlice: Failure trying to run slice ${slice.identifier} on a sandbox.`, error);\n return this.supervisor.returnSlice(slice, 'JobManager.runSlice');\n });\n }\n catch (error)\n {\n this.error(`JobManager.runSlice failed to run slice ${slice.identifier}`, error);\n return this.supervisor.returnSlice(slice, 'JobManager.runSlice_static_catch');\n }\n }\n\n /**\n * Execute slice on sandbox, collect results or handle errors, and clean up.\n * @param {Slice} slice - The slice to execute on the sandbox.\n * @param {Sandbox} sandbox - The sandbox on which to execute the slice.\n * @returns {Promise<any>}\n */\n runSliceOnSandbox (slice, sandbox)\n {\n const handleError = (error, tag) => {\n if (sandbox.isTerminated)\n {\n this.error(`The screensaver or evaluator may be down: slice ${slice.identifier}`, error);\n return this.supervisor.returnSlice(slice, `${tag}: sandbox terminated`);\n }\n else\n {\n this.error(`Failure in ${tag} for slice ${slice.identifier}`, error);\n const reason = (error.code === 'EPERM_ORIGIN' || error.code === 'EFETCH_BAD_ORIGIN' || error.code === 'EFETCH') ? error.code : tag;\n this.supervisor.returnSlice(slice, reason);\n }\n };\n debugging('supervisor') && console.debug(`runSliceOnSandbox ${sandbox.identifier} #(r/rsv/w/wsbx/sbx), ${this.supervisor.dbg.workingSliceSandboxStr}, #assigned, ${this.assignedSandboxes.length}, #localSBs, ${this.sandboxInventory.map(s => Number(s.id)).sort((x,y)=>x-y)}`);\n selectiveDebug() && console.debug('runSliceOnSandbox', sandbox.identifier, this.workingSliceCount, Date.now() - this.supervisor.lastTime);\n\n if (sandbox.isTerminated) // When evaluator goes down, all sandboxes are terminated.\n return Promise.resolve();\n\n this.sandboxEmit(sandbox, 'slice', slice.sliceNumber);\n\n if (this.supervisor.sliceTiming)\n {\n slice['queueingDelta'] = Date.now() - slice['queueingDelta'];\n slice['executionDelta'] = Date.now();\n }\n slice.startTime = Date.now();\n\n return sandbox.work()\n .finally(() => {\n if (this.supervisor.sliceTiming)\n {\n slice['executionDelta'] = Date.now() - slice['executionDelta'];\n slice['resultDelta'] = Date.now();\n }\n })\n .then((result) => {\n selectiveDebug2() && console.debug(`runSliceOnSandbox: sandbox${sandbox.id} - success: ${slice.identifier}`, Boolean(result), Date.now() - slice.startTime);\n\n if (sandbox.isTerminated)\n throw new Error(`Slice ${slice.identifier} completed work, but the sandbox ${sandbox.id} is terminated.`);\n\n // Allow reuse of sandbox up to a limit of dcpConfig.supervisor.sandbox.maxSandboxUse .\n sandbox.checkSandboxReUse();\n // Transition slice to COMPLETE\n slice.collectResult(result, true /*success*/);\n\n // Record result.\n return this.supervisor.recordResult(slice, sandbox)\n .catch((error) => {\n // Catches exception from supervisor.recordResult .\n return handleError(error, 'recordResult');\n });\n\n }, (error) => {\n // Catches exception from sandbox.work.\n // Kills sandbox and either retries the slice or transitions slice to FAILED and returns to scheduler.\n const reason = this.supervisor.handleSandboxWorkError(sandbox, slice, error);\n selectiveDebug() && console.error(`runSliceOnSandbox: Failure executing slice on sandbox: ${sandbox.identifier}`, reason, error);\n })\n .catch((error) => {\n // Catches exception from sandbox.work or then-clause.\n return handleError(error, 'runSliceOnSandbox');\n });\n }\n\n // _Idx\n //\n // removeSlice\n //\n\n /**\n * Remove slice from this.sliceInventory.\n * @param {Slice} slice - The slice to remove from this.sliceInventory.\n * @return {boolean} - False when slice is not in this.sliceInventory.\n */\n removeSlice (slice)\n {\n debugging('supervisor') && console.debug(`removeSlice slice ${slice.sliceNumber}`);\n return common.removeElement(this.sliceInventory, slice);\n }\n\n // _Idx\n //\n // fetchError, fetchJob, fetchSliceData\n //\n\n fetchError (error)\n {\n if (error.code === 'EPERM_ORIGIN')\n throw new BadOriginError(error);\n if (error.code === 'EFETCH')\n throw new RemoteFetchError(error);\n throw new UncaughtExceptionError(error);\n }\n\n /**\n * Fetch work function, work function arguments and possibly the range object describing the jobs slices..\n * @param {object} mpe - messagePayloadElement: job object returned by task-jobs.js . \n * @returns {Promise<*>}\n */\n fetchJob (mpe)\n {\n debugging('supervisor') && utils.dumpObject(mpe, 'JobManager.fetchJob: mpe', 512);\n\n const promises = [];\n try\n {\n // Get workFn.\n if (!mpe.workFunction)\n {\n const workFunctionPromise = this.fetchWorkFunctions(mpe.codeLocation)\n .then((workFunction) => {\n this.inputDataSize += workFunction.size;\n mpe.workFunction = workFunction.data;\n if (mpe.requirements.useStrict)\n mpe.useStrict = true;\n delete mpe.codeLocation;\n }, (error) => {\n this.fetchError(error);\n });\n promises.push(workFunctionPromise);\n }\n\n if (!mpe.mro && mpe.MROLocation)\n {\n const mroPromise = this.fetchData(mpe.MROLocation)\n .then((mro) => {\n // It's not ready for computing the inputDataSize yet -- that's done in JobManager.fetchSliceData .\n mpe.mro = mro.data;\n delete mpe.MROLocation;\n }, (error) => {\n this.fetchError(error);\n });\n promises.push(mroPromise)\n }\n\n // Get workFn args.\n if (!mpe.arguments && mpe.argumentsLocation)\n {\n mpe.arguments = new Array(mpe.argumentsLocation.length);\n for (let k = 0; k < mpe.argumentsLocation.length; k++)\n promises.push(this.fetchArguments(mpe.argumentsLocation[k].value)\n .then((arg) => {\n this.inputDataSize += arg.size;\n mpe.arguments[k] = arg.data;\n }, (error) => {\n this.fetchError(error);\n })\n );\n }\n\n return Promise.all(promises)\n .then(() => {\n if (mpe.argumentsLocation)\n delete mpe.argumentsLocation;\n return mpe;\n }, (error) => {\n this.fetchError(error);\n });\n }\n catch (error)\n {\n this.fetchError(error);\n }\n }\n\n /**\n * Look up slice.datumUri or use the range object this.mro, constructed by fetchJob.\n * @param {Slice} slice \n * @returns {Promise<{ data: *, size: number }>}\n */\n fetchSliceData (datumUri, slice)\n {\n try\n {\n if (!datumUri)\n {\n if (!this.mro)\n throw new Error('Must complete call to JobManager.fetchJob before calling JobManager.fetchSliceData.');\n /** XXXpfr @todo Inefficient, we're rehydrating the whole range for a single slice datum. */\n const ro = rehydrateRange(this.mro);\n // Slice numbers start at 1.\n const sliceDatum = ro[slice.sliceNumber - 1];\n debugging('supervisor') && console.debug(`Fetched mro datum: ${stringify(sliceDatum, 512)}`);\n return Promise.resolve({ data: sliceDatum, size: JSON.stringify(sliceDatum).length });\n }\n\n return this.fetchData(datumUri)\n .catch((error) => {\n throw this.fetchError(error);\n });\n }\n catch (error)\n {\n this.fetchError(error);\n }\n }\n\n // _Idx\n //\n // jobEmit, sandboxEmit, error, warning. countSliceStr, dumpSlices\n //\n\n /**\n * Safe event emitter on jobHandle.\n * @param {string} event\n * @param {...any} args\n */\n jobEmit(event, ...args)\n {\n this.supervisor.safeEmit(this.jobHandle, event, ...args);\n }\n\n /**\n * Safe event emitter on sandboxHandle.\n * @param {Sandbox} sandbox\n * @param {string} event\n * @param {...any} args\n */\n sandboxEmit(sandbox, event, ...args)\n {\n this.supervisor.safeEmit(sandbox.sandboxHandle, event, ...args);\n }\n\n /**\n * Error feedback to user.\n * @param {string} message\n * @param {Array<Error>|Error|string} [coreError]\n * @param {string} [additionalInfo]\n */\n error (message, coreError, additionalInfo)\n {\n this.supervisor.error(message, coreError, additionalInfo);\n }\n\n /**\n * Warning feedback to user.\n * @param {string[]} messages\n */\n warning (...messages)\n {\n this.supervisor.warning(...messages);\n }\n\n /**\n * Debugging helper.\n * @param {string} [tag='']\n * @returns {string}\n */\n countSliceStr (tag)\n {\n const { unassigned, ready, reserved, working, workdone, complete, failed, finished } = this.dumpSlices (tag, false, false);\n return `${tag}: u/r/rsv/w/wd/c/f/fsh ${unassigned}/${ready}/${reserved}/${working}/${workdone}/${complete}/${failed}/${finished}`;\n }\n\n /**\n * @param {URL|string} uri\n * @returns {Promise<*>}\n */\n fetchData (uri) { return fetchURIAndLength(uri, this.originManager, allowOriginsPurposes.fetchData); }\n /**\n * @param {URL|string} uri\n * @returns {Promise<*>}\n */\n fetchArguments (uri) { return fetchURIAndLength(uri, this.originManager, allowOriginsPurposes.fetchArguments); }\n /**\n * @param {URL|string} uri\n * @returns {Promise<*>}\n */\n fetchWorkFunctions (uri) { return fetchURIAndLength(uri, this.originManager, allowOriginsPurposes.fetchWorkFunctions); }\n\n /**\n * Debugging helper.\n * @param {string} [tag='']\n * @param {boolean} [details=false]\n * @param {boolean} [display=true]\n * @returns {{ ready, reserved, working, workdone, complete, failed, finished, unassigned }}\n */\n dumpSlices (tag = '', details = false, display = true)\n {\n if (display)\n console.log(`dumpSlices(${tag}): ${this.identifier}, sliceCount ${this.sliceInventory.length}, sandboxCount ${this.sandboxInventory.length}`, Date.now() - this.supervisor.lastTime);\n if (this.sliceInventory.length < 1) return { unassigned:0, ready:0, reserved:0, working:0, workdone:0, complete:0, failed:0, finished:0 };\n if (details)\n {\n // Detailed classification of slices.\n const unassigned = [], ready = [], reserved = [], working = [], workdone = [], complete = [], failed = [], finished = [];\n for (const slice of this.sliceInventory)\n {\n if (slice.isReady)\n ready.push(slice);\n else if (slice.isReserved)\n reserved.push(slice);\n else if (slice.isWorking)\n working.push(slice);\n else if (slice.isWorkDone)\n workdone.push(slice);\n else if (slice.isComplete)\n complete.push(slice);\n else if (slice.isFailed)\n failed.push(slice);\n else if (slice.isFinished)\n finished.push(slice);\n else if (slice.isUnassigned)\n unassigned.push(slice);\n else\n throw new Error(`dumpSlices: Unexpected kind a slice; ${slice.identifier}`);\n }\n if (display)\n {\n const dmpSlices = (name, slices) => {\n console.log(`-----${name}(${slices.length})-----------------------------------------------------------------`);\n for (const slice of slices)\n console.log(slice.identifier);\n }\n dmpSlices('unassigned', unassigned);\n dmpSlices('ready', ready);\n dmpSlices('reserved', reserved);\n dmpSlices('working', working);\n dmpSlices('workdone', workdone);\n dmpSlices('complete', complete);\n dmpSlices('failed', failed);\n dmpSlices('finished', finished);\n console.log('-----------------------------------------------------------------------------------');\n }\n return {\n ready: ready.length,\n reserved: reserved.length,\n working: working.length,\n workdone: workdone.length,\n complete: complete.length,\n failed: failed.length,\n finished: finished.length,\n unassigned: unassigned.length,\n };\n }\n else\n {\n // Quick classification of slices.\n const { ready,\n reserved,\n working,\n workdone,\n complete,\n failed,\n finished,\n unassigned } = common.sliceCounts(this.sliceInventory);\n if (display)\n {\n const dumpCount = (name, count) => {\n console.log(`-----${name}(${count})-----------------------------------------------------------------`);\n }\n dumpCount('unassigned', unassigned);\n dumpCount('ready', ready);\n dumpCount('reserved', reserved);\n dumpCount('working', working);\n dumpCount('workdone', workdone);\n dumpCount('complete', complete);\n dumpCount('failed', failed);\n dumpCount('finished', finished);\n console.log('-----------------------------------------------------------------------------------');\n }\n return { ready, reserved, working, workdone, complete, failed, finished, unassigned };\n }\n }\n\n}\nexports.JobManager = JobManager;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor2/job-manager.js?");
4738
+ eval("/**\n * @file dcp-client/worker/supervisor2/job-manager.js\n *\n * A support class for Supervisor2.\n * It is a wrapper for the job object returned from the requestTask,\n * along with tracking slices and sandboxes associated with the job. \n *\n * @author Wes Garland, wes@distributive.network,\n * Paul, paul@distributive.network,\n * @date Dec 2020,\n * June 2022, Jan-April 2023\n * @module job-manager\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n// @ts-check\n\n\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {string} address */ // String(Address)\n/** @typedef {import('./sandbox2').Sandbox} Sandbox */\n/** @typedef {import('./index').Supervisor} Supervisor */\n/** @typedef {import('dcp/utils/jsdoc-types').SliceMessage} SliceMessage */\n/** @typedef {import('dcp/utils/jsdoc-types').Auth} Auth */\n/** @typedef {import('dcp/utils/jsdoc-types').Signature} Signature */\n/** @typedef {import('dcp/utils/jsdoc-types').AuthMessage} AuthMessage */\n/** @typedef {import('dcp/dcp-client/worker/origin-access-manager').OriginAccessManager} OriginAccessManager */\n\nconst inspect = Symbol.for('nodejs.util.inspect.custom');\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst EventEmitter = __webpack_require__(/*! events */ \"./node_modules/events/events.js\");\nconst { Inventory } = __webpack_require__(/*! dcp/utils/inventory */ \"./src/utils/inventory.js\");\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { rehydrateRange } = __webpack_require__(/*! dcp/dcp-client/range-object */ \"./src/dcp-client/range-object.js\");\nconst { fetchURIAndLength } = __webpack_require__(/*! dcp/utils/fetch-uri */ \"./src/utils/fetch-uri.js\");\nconst utils = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { stringify, a$sleepMs, constructAlpha, nextEma } = utils;\nconst { allowOriginsPurposes } = __webpack_require__(/*! dcp/common/worker-constants */ \"./src/common/worker-constants.js\");\n\nconst { Slice } = __webpack_require__(/*! ./slice2 */ \"./src/dcp-client/worker/supervisor2/slice2.js\");\nconst { Promise_any } = __webpack_require__(/*! ./promise_any */ \"./src/dcp-client/worker/supervisor2/promise_any.js\");\nconst { Statistics } = __webpack_require__(/*! ./rolling-statistics */ \"./src/dcp-client/worker/supervisor2/rolling-statistics.js\");\nconst { SandboxError, UncaughtExceptionError } = __webpack_require__(/*! ./sandbox2 */ \"./src/dcp-client/worker/supervisor2/sandbox2.js\");\nconst { updateGPUStatistics } = __webpack_require__(/*! ./gpu_support */ \"./src/dcp-client/worker/supervisor2/gpu_support.js\");\nconst common = __webpack_require__(/*! ./common */ \"./src/dcp-client/worker/supervisor2/common.js\");\nconst { selectiveDebug, selectiveDebug2, debugBuild } = common;\n\nconst INITIAL = 'initial';\nconst READY = 'ready';\nconst STOP = 'stop';\nconst REFUSE = 'refuse';\nconst BROKEN = 'broken';\n\nclass BadOriginError extends SandboxError { constructor(msg) { super('EPERM_ORIGIN', msg); } }\nclass RemoteFetchError extends SandboxError { constructor(msg) { super('EFETCH', msg); } }\n\n/**\n * Public event emitter.\n * https://gitlab.com/Distributed-Compute-Protocol/dcp-docs-wes/-/blob/wip/worker/worker-events.md\n */\nclass JobHandle extends EventEmitter\n{\n /** @type {JobManager} */\n #jobManager;\n \n /**\n * @constructor\n * @param {JobManager} jobManager\n */\n constructor (jobManager)\n {\n super({ captureRejections: false });\n this.#jobManager = jobManager;\n }\n /** @type {string} */\n get address () { return this.#jobManager.address; }\n /** @type {string} */\n get name () { return this.#jobManager.public ? this.#jobManager.public.name : '<unknown>'; }\n /** @type {string} */\n get description () { return this.#jobManager.public ? this.#jobManager.public.description : '<unknown>'; }\n /** @type {string} */\n get link () { return this.#jobManager.public ? this.#jobManager.public.link : '<unknown>'; }\n}\nexports.JobHandle = JobHandle;\n\n//\n// Index to functionality -- search for '_Idx' to toggle through the index.\n//\n// 1) class JobManager\n// 2) addSlices, fetchDependencies, updateStatistics, update, predictLoad\n// 3) reserveOneSlice, resetSlices, destroy, evaluatorDownCleanup\n// 4) assignSandbox, runSlice, runSliceOnSandbox\n// 5) removeSlice\n// 6) fetchError, fetchJob, fetchSliceData\n// 7) jobEmit, sandboxEmit, error, warning. countSliceStr, dumpSlices.\n//\n\n// _Idx\n//\n// class JobManager\n// A JobManager handles all scheduling of slices for a given job.\n// It's also responsible for fetching slice data, work functions and arguments.\n// And it collects statistics about slice completion times and resource usage.\n// All functionality across jobs is handled by Supervisor.\n//\n\n/**\n * JobManager Constructor. An instance of JobManager knows everything about a given\n * job within the supervisor, including:\n * - work function code\n * - work function arguments\n * - all the SliceManager instances that go with this job\n * - how long a slice of this job is expected to run\n *\n * Instances of JobManager emit the following events:\n * - addSlice(sliceHandle)\n * - deleteSlice(sliceHandle)\n * - statusChange(new state, old state);\n *\n * JobManager States\n *\n * Start state:\n * initial\n *\n * Intermediate states:\n * ready\n *\n * Terminal states:\n * broken - job could not be initialized\n * refuse - for some reason, we have decided that we don't want work for this job\n * stop - job manager has been stopped\n *\n * Valid transitions:\n * initial -> broken\n * initial -> ready\n * initial -> ready -> stop\n * \n * NOTE: If you ever use a property with a leading underscore you are probably making a mistake.\n * But if you must, please ask paul, yarn, bryan or eddie for a CR.\n */\nclass JobManager\n{\n /** @type {Supervisor} */\n #supervisor;\n /** @type {Slice[]} */\n #sliceInventory;\n /** @type {Synchronizer} */\n #state;\n /** @type {*} */ // Figure out the type\n #jobMessage;\n /** @type {string} */\n #address;\n /** @type {Statistics} */\n #statistics;\n /** @type {Promise<*>} */\n #jobPromise;\n\n /**\n * @constructor\n * @param {Supervisor} parent - Owning Supervisor.\n * @param {object} jobMessage - Job Descriptor from getJobsForTask.\n * @param {SliceMessage[]} sliceMessages - Messages from task distributor describing slices.\n * @param {AuthMessage} authorizationMessage - The signature that shipped with the task authorizing this worker.\n */\n constructor(parent, jobMessage, sliceMessages, authorizationMessage)\n {\n this.#supervisor = parent;\n this.#sliceInventory = []; // All slices for this.address.\n this.#state = new Synchronizer(INITIAL, [ INITIAL, READY, STOP, REFUSE, BROKEN ]);\n this.#jobMessage = { ...jobMessage };\n this.#address = String(this.jobMessage.address);\n this.#statistics = new Statistics(0.5); // Effective period of 12 or 13\n this.#jobPromise = null;\n\n /** @type {{CPUTime: number, GPUTime: number, TotalTime: number, CPUDensity: number, GPUDensity: number, InDataSize: number, OutDataSize: number }} */\n this.localMetrics = null;\n /** @type {string[]} */\n this.rejectedJobReasons = [];\n /** @type {boolean} */\n this.isEstimation = true;\n /** @type {number} */\n this.inputDataSize = 0;\n /**\n * jobMan.evaluatorDownCleaup returns this.evaluatorDownHandle=a$sleepMs which is\n * interruptible by calling this.evaluatorDownHandle.intr().\n * @type {Promise<void>}\n */\n this.evaluatorDownHandle = null;\n\n /*\n * Make sure this.metrics contains valid data.\n * With old legacy schedulers, this may not be so.\n */\n this.fixUpMetrics();\n\n /** \n * Start loading dependencies in the background. Once these are loaded, this.state will \n * transition to 'ready' and the job will be ready for work.\n */\n this.#jobPromise = this.fetchDependencies(sliceMessages, authorizationMessage);\n\n /* Check for GPU support. */\n const _env = this.requirements?.environment;\n /** @type {boolean} */\n this.useGPU = _env ? (_env.webgpu || _env.offscreenCanvas) : false;\n \n /**\n * Event emitter containing info that describes the job.\n * @type {JobHandle}\n */\n this.jobHandle = new JobHandle(this);\n\n /**\n * Emit the 'job' event on the worker event emitter.\n */\n this.supervisor.safeEmit(this.supervisor.worker, 'job', this.jobHandle);\n }\n\n /** @type {*} */ // Figure out the type\n get jobMessage () { return this.#jobMessage; }\n /** @type {string} */\n get address () { return this.#address; }\n /** @type {string} */\n get uuid () { return this.jobMessage.uuid; }\n /** @type {boolean} */\n get workerConsole () { return this.jobMessage.workerConsole; }\n /** @type {object} */\n get requirements () { return this.jobMessage.requirements; }\n /** @type {boolean} */\n get almostDone () { return this.jobMessage.almostDone; }\n\n // These 3 properties have type object.\n get mro () { return this.jobMessage.mro; }\n get arguments () { return this.jobMessage.arguments; }\n get workFunction () { return this.jobMessage.workFunction; }\n\n /** @type {{computeGroups: Array<{opaqueId: string, name: string, description: string}>, name: string, description: string, link: string}} */\n get public () { return this.jobMessage.public; }\n /** @type {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number, sliceInDataSize: number, sliceOutDataSize: number, lastSliceNumber: number, measuredSlices: number, alpha: number}} */\n get metrics () { return this.jobMessage.metrics; }\n /** @type {Supervisor} */\n get supervisor () { return this.#supervisor; }\n /** @type {Synchronizer} */\n get state () { return this.#state; }\n\n /** @type {boolean} */\n get ready () { return this.state.is(READY); }\n\n // These 4 aren't needed yet.\n ///** @type {boolean} */\n //get initial () { return this.state.is(INITIAL); }\n ///** @type {boolean} */\n //get stopped () { return this.state.is(STOP); }\n ///** @type {boolean} */\n //get refuse () { return this.state.is(REFUSE); }\n ///** @type {boolean} */\n //get broken () { return this.state.is(BROKEN); }\n\n /** @type {Sandbox[]} */\n get sandboxInventory () { return this.supervisor.sandboxInventory.filter((sandbox) => sandbox.jobAddress === this.#address && !sandbox.isTerminated); }\n /** @type {Sandbox[]} */\n get assignedSandboxes () { return this.supervisor.sandboxInventory.filter((sandbox) => sandbox.jobAddress === this.#address && sandbox.isAssigned); }\n /** @type {Sandbox[]} */\n get workingSandboxes () { return this.supervisor.sandboxInventory.filter((sandbox) => sandbox.jobAddress === this.#address && sandbox.isWorking); }\n /** @type {Slice[]} */\n get sliceInventory () { return this.#sliceInventory; }\n /** @type {Slice[]} */\n get readySlices () { return this.sliceInventory.filter((slice) => slice.isReady); }\n /** @type {Slice[]} */\n get queuedSlices () { return this.sliceInventory.filter((slice) => slice.isQueued); } // Ready and soon-to-be-ready\n /** @type {Slice[]} */\n get reservedSlices () { return this.sliceInventory.filter((slice) => slice.isReserved); }\n /** @type {Slice[]} */\n get activeSlices () { return this.sliceInventory.filter((slice) => slice.isActive); } // Reserved, working, workdone and complete\n /** @type {Slice[]} */\n get workingSlices () { return this.sliceInventory.filter((slice) => slice.isWorking || slice.isReserved); } // Working and soon-to-be-working\n /** @type {Slice[]} */\n get unFinishedSlices () { return this.sliceInventory.filter((slice) => !slice.isFinished); }\n\n /** @type {Statistics} */\n get statistics () { return this.#statistics; }\n /** @type {Promise<*>} */\n get jobPromise () { return this.#jobPromise; }\n /** @type {number} */\n get globalTime () { return utils.globalTime(this.metrics); }\n /** @type {number} */\n get emaSliceTime () { return this.localMetrics ? utils.localTime(this.localMetrics) : (utils.globalTime(this.metrics) * this.supervisor.defaultQuanta); }\n /** @type {number} */\n get emaTotalTime () { return this.localMetrics?.TotalTime || (utils.globalTotalTime(this.metrics) * this.supervisor.defaultQuanta); }\n /** @type {number} */\n get adjSliceTime () { return Math.min(this.emaSliceTime, this.supervisor.options.targetTaskDuration); }\n /** @type {string} */\n get identifier () { return `${common.truncateAddress(this.address)}.${this.state}`; }\n /** @type {string} */\n get sliceState () { return `${this.queuedSlices.length}.${this.workingSliceCount}.${this.sliceInventory.length}.${this.workRemaining}`; }\n /** @type {string} */\n get [inspect] () { return `[Object JobManager <${this.public.name}::${common.truncateAddress(this.address)}::${this.state}>]`; }\n /** @type {OriginAccessManager} */\n get originManager() { return this.supervisor.originManager; }\n /**\n * Expected density of a slice.\n * When we get the GPU stuff codified, the definition might be\n * { cpu: this.localMetrics?.CPUDensity ?? 1.0, gpu: this.localMetrics?.GPUDensity ?? 1.0}\n * @type {number}\n */\n get estimateDensity () { return this.localMetrics?.CPUDensity || utils.globalDensity(this.metrics); }\n /** @type {number} */\n get estimateGPUDensity () { return this.localMetrics?.GPUDensity || utils.globalGPUDensity(this.metrics); }\n /** @type {number} */\n get cpuTime () { return this.localMetrics?.CPUTime || this.metrics?.sliceCPUTime || 0; }\n /**\n * Estimate the number of milliseconds of total execution time of the job.\n * @type {number}\n */\n get workRemaining ()\n {\n const _adjSliceTime = this.adjSliceTime;\n const queuedTime = _adjSliceTime * this.queuedSlices.length;\n let workingTime = 0;\n for (const slice of this.workingSlices)\n {\n workingTime += Math.max(0, _adjSliceTime - (Date.now() - slice.startTime));\n selectiveDebug2() && console.debug(`JobManager.workRemaining: _adjSliceTime ${_adjSliceTime}, slice-time-elapsed ${Date.now() - slice.startTime}`);\n }\n selectiveDebug() && console.debug(`JobManager.workRemaining: queuedTime ${queuedTime}, queuedCount ${this.queuedSlices.length}, workingTime ${workingTime}, workingCount ${this.workingSliceCount}`);\n return workingTime + queuedTime;\n }\n /**\n * Are there no working slices?\n * @type {boolean}\n */\n get isNotWorking ()\n {\n for (const slice of this.sliceInventory)\n if (slice.isReserved || slice.isWorking)\n return false;\n return true;\n }\n /**\n * Estimated count of working (or reserved) slices.\n * @type {number}\n */\n get workingSliceCount ()\n {\n let count = 0;\n for (const slice of this.sliceInventory)\n if (slice.isReserved || slice.isWorking)\n ++count;\n return count;\n }\n /**\n * Estimated density of working (or reserved) slices.\n * @type {number}\n */\n get workingSliceDensity () { return this.workingSliceCount * this.estimateDensity; }\n /**\n * Estimated density of working (or reserved) slices.\n * @type {number}\n */\n get workingGPUDensity () { return this.workingSliceCount * this.estimateGPUDensity; }\n /**\n * Always display max info under debug builds, otherwise maximal error\n * messages are displayed to the worker, only if both worker and client agree.\n * When displayMaxDiagInfo is true, display stack trace and other enhanced diag info.\n * @type {boolean}\n **/\n get displayMaxDiagInfo ()\n {\n return common.displayMaxDiagInfo() || this.workerConsole && this.supervisor.options.allowConsoleAccess;\n }\n\n // _Idx\n //\n // addSlices, fetchDependencies, updateStatistics, update, predictLoad\n //\n\n /**\n * Add slices to the job manager's inventory.\n *\n * @param {SliceMessage[]} sliceMessages - Messages from task distributor describing slices.\n * @param {AuthMessage} authorizationMessage - The signature that shipped with the task authorizing this worker.\n * @returns {Promise<*>}\n */\n addSlices (sliceMessages, authorizationMessage)\n {\n /** @type {Array<Promise<void>>} */\n const slicePromises = [];\n sliceMessages.forEach((sliceMessage) => {\n const slice = new Slice(this, sliceMessage, authorizationMessage);\n if (!slice.isEstimation)\n this.isEstimation = false;\n this.sliceInventory.push(slice);\n slicePromises.push(slice.fetchSliceDataPromise);\n });\n return Promise_any(slicePromises);\n }\n\n /**\n * Fetch dependencies.\n * @param {SliceMessage[]} sliceMessages - Messages from task distributor describing slices.\n * @param {AuthMessage} authorizationMessage - The signature that shipped with the task authorizing this worker.\n * @returns {Promise<*>}\n */\n fetchDependencies (sliceMessages, authorizationMessage)\n {\n return this.fetchJob(this.jobMessage)\n .then (() => {\n debugging('supervisor') && console.debug('JobManager is transitioning to READY', this.identifier, Date.now() - this.supervisor.lastTime);\n this.state.set(INITIAL, READY);\n return this.addSlices(sliceMessages, authorizationMessage);\n }, (error) => {\n selectiveDebug() && console.error('fetchJob has failed', error);\n this.state.set(INITIAL, BROKEN);\n throw error;\n });\n }\n\n /** XXXpfr @todo Factor out statistics and EMA into another class. */\n /**\n * @param {number} x\n * @param {boolean} isDensity\n * @returns {boolean}\n */\n static isValid (x, isDensity = false)\n {\n return Number.isFinite(x) && !(x < 0) && !(isDensity && x > 1);\n }\n\n /**\n * Old legacy DCP schedulers can send a corrupt this.metrics .\n * We should get rid of this when we don't support old legacy DCP schedulers.\n */\n fixUpMetrics ()\n {\n if (!this.metrics)\n this.jobMessage.metrics = { sliceCPUTime: 0, sliceCPUDensity: 1, sliceGPUTime: 0, sliceGPUDensity: 0, sliceInDataSize: 0, sliceOutDataSize: 0 };\n else\n {\n if (!(JobManager.isValid(this.metrics.sliceCPUTime) && JobManager.isValid(this.metrics.sliceCPUDensity, true)))\n {\n this.metrics.sliceCPUTime = 1;\n this.metrics.sliceCPUDensity = 0.5;\n }\n if (!(JobManager.isValid(this.metrics.sliceGPUTime) && JobManager.isValid(this.metrics.sliceGPUDensity, true)))\n {\n this.metrics.sliceGPUTime = 1;\n this.metrics.sliceGPUDensity = 0.5;\n }\n if (!JobManager.isValid(this.metrics.sliceInDataSize))\n this.metrics.sliceInDataSize = 0;\n if (!JobManager.isValid(this.metrics.sliceOutDataSize))\n this.metrics.sliceOutDataSize = 0;\n }\n }\n\n /**\n * @param {Slice} slice\n * @param {Sandbox} sandbox\n * @returns {{CPUTime: number, GPUTime: number, CPUDensity: number, GPUDensity: number, InDataSize: number, OutDataSize: number}}\n */\n updateStatistics (slice, sandbox)\n {\n /**\n * @todo XXXpfr Make this only check when debugBuild, but not until the Sup2 churn has stopped.\n * @param {{CPUTime: number, GPUTime: number, CPUDensity: number, GPUDensity: number, InDataSize: number, OutDataSize: number}} metrics\n * @returns {boolean}\n */\n const isValidMetrics = (metrics) => {\n return JobManager.isValid(metrics.CPUTime) && JobManager.isValid(metrics.CPUDensity, true)\n && JobManager.isValid(metrics.GPUTime) && JobManager.isValid(metrics.GPUDensity, true)\n && JobManager.isValid(metrics.InDataSize) && JobManager.isValid(metrics.OutDataSize);\n }\n\n const timeReport = slice.timeReport;\n const dataReport = slice.dataReport;\n selectiveDebug() && console.debug('updateStatistics', slice.identifier, timeReport, dataReport);\n\n // Sanity\n if (!(timeReport.CPU >= 1))\n {\n timeReport.CPU = 1;\n timeReport.total += 1;\n }\n if (!(timeReport.total >= 1))\n timeReport.total = 1;\n\n // Construct metrics.\n const GPUTime = timeReport.webGPU + timeReport.webGL;\n if (this.useGPU)\n updateGPUStatistics(GPUTime);\n const metrics = {\n CPUTime: timeReport.CPU,\n GPUTime,\n CPUDensity: timeReport.CPU / timeReport.total,\n GPUDensity: GPUTime / timeReport.total,\n InDataSize: dataReport.InDataSize,\n OutDataSize: dataReport.OutDataSize,\n };\n //console.debug('JM.updateStatistics metrics:', metrics, timeReport.total);\n\n // Create the measurement object which will be passed to the metrics event.\n const eventMeasurements = {\n elapsed: timeReport.total / 1000,\n CPU: metrics.CPUTime / 1000,\n GPU: metrics.GPUTime / 1000,\n in: metrics.InDataSize,\n out: metrics.OutDataSize,\n };\n\n // Emit metrics event for both jobHandle and sandboxHandle\n this.jobEmit( 'metrics', slice.sliceNumber, eventMeasurements);\n this.sandboxEmit(sandbox, 'metrics', slice.sliceNumber, eventMeasurements);\n selectiveDebug() && console.debug(`updateStatistics: slice ${slice.identifier}, eventMeasurements ${stringify(eventMeasurements)}`);\n\n if (!isValidMetrics(metrics))\n throw new Error(`JobManager.updateStatistics: metrics are in an inconsistent state ${JSON.stringify(metrics)}`);\n\n if (!this.localMetrics)\n this.localMetrics = { CPUTime: 0, GPUTime: 0, TotalTime: 0, CPUDensity: 0, GPUDensity: 0, InDataSize: 0, OutDataSize: 0 };\n\n // measuredSlices+1 because the current slice hasn't been measured yet in RS.updateMetrics.\n const alpha = this.metrics?.lastSliceNumber\n ? constructAlpha(this.metrics.lastSliceNumber, this.metrics.measuredSlices + 1)\n : 0.5;\n selectiveDebug2() && console.debug(`updateStatistics: local alpha ${alpha}, global alpha ${this.metrics?.alpha}`);\n this.localMetrics.CPUTime = nextEma(this.localMetrics.CPUTime, metrics.CPUTime, alpha);\n this.localMetrics.GPUTime = nextEma(this.localMetrics.GPUTime, metrics.GPUTime, alpha);\n this.localMetrics.TotalTime = nextEma(this.localMetrics.TotalTime, timeReport.total, alpha);\n this.localMetrics.CPUDensity = nextEma(this.localMetrics.CPUDensity, metrics.CPUDensity, alpha);\n this.localMetrics.GPUDensity = nextEma(this.localMetrics.GPUDensity, metrics.GPUDensity, alpha);\n this.localMetrics.InDataSize = nextEma(this.localMetrics.InDataSize, metrics.InDataSize, alpha); // Don't need this yet.\n this.localMetrics.OutDataSize = nextEma(this.localMetrics.OutDataSize, metrics.OutDataSize, alpha); // Don't need this yet.\n\n if (common.debugQuanta())\n {\n this.supervisor.dbg.addRawLocal(slice, metrics, alpha);\n this.supervisor.dbg.addLocal(slice, this.emaSliceTime, alpha);\n }\n\n if (!isValidMetrics(this.localMetrics))\n throw new Error(`JobManager.updateStatistics: localMetrics are in an inconsistent state ${JSON.stringify(this.localMetrics)}`);\n\n debugging('supervisor') && console.debug('updateStatistics: ema', this.statistics.ema, 'emaSliceTime', this.emaSliceTime, 'estimateDensity', this.estimateDensity, 'metrics', metrics, 'g-metrics', this.metrics, 'alpha', alpha);\n //if (!Number.isFinite(this.statistics.ma))\n // console.log(this.statistics.x);\n return metrics;\n }\n\n /**\n * Update jobMessage, add some slices to inventory and possibly update the initial seed of the statistics.\n * @param {object} jobMessage - Job Descriptor from getJobsForTask.\n * @param {SliceMessage[]} [sliceMessages] - Messages from task distributor describing slices.\n * @param {AuthMessage} [authorizationMessage] - The signature that shipped with the task authorizing this worker.\n * @returns {Promise<*>}\n */\n update (jobMessage, sliceMessages, authorizationMessage)\n {\n Object.assign(this.jobMessage, jobMessage);\n /*\n * Make sure this.metrics contains valid data.\n * With old legacy schedulers, this may not be so.\n */\n this.fixUpMetrics();\n selectiveDebug2() && console.debug('JM.update: new this.jobMessage', this.jobMessage.metrics);\n\n if (sliceMessages)\n return (this.#jobPromise = this.addSlices(sliceMessages, authorizationMessage));\n }\n\n /**\n * Predict the total reduction in density of working sandboxes timeSpanMs from now.\n * This function is called right before fetchTask, in order to calculate how much space is available.\n * In pratice, the returned property queued will have length <= 1, because when it is more we won't call fetchWork.\n * @param {number} timeSpanMs\n * @returns {{ queued: Slice[], working: number }}\n */\n predictLoad (timeSpanMs)\n {\n let queuedCount = this.queuedSlices.length;\n // Optimize to short-circuit when queued > 1, because we won't call fetchWork in that case.\n if (queuedCount > this.workingSliceCount + 1)\n {\n selectiveDebug() && console.debug('JM.predictLoad: bail early', queuedCount, this.workingSliceCount);\n return { queued: [null, null], working: this.workingSliceDensity };\n }\n let space = 0;\n // 1) Predict the current working slices that will finish in timeSpanMs.\n // 2) Compensate for queued slices that may begin working.\n for (const slice of this.sliceInventory)\n {\n if (!slice.isReserved && !slice.isWorking)\n continue;\n const remainingTime = Math.max(0, this.emaSliceTime - (Date.now() - slice.startTime));\n selectiveDebug2() && console.debug('JM.predictLoad: predicting...', queuedCount, space, this.emaSliceTime, remainingTime, this.emaSliceTime <= timeSpanMs - remainingTime);\n // If slice will end before timeSpanMs elapses, it may open up space.\n if (remainingTime <= timeSpanMs)\n {\n // Check to see whether a new queued slices can start running and finish in the remaining time.\n // Somewhat inaccurate because it's possible multiple slices could finish in the remaining time.\n // e.g. Suppose timeSpanMs = 5 * 1000 and this.emaSliceTime is 1000, then there's room\n // to successively schedule 5 slices, one after another.\n /** XXXpfr @todo Fix to incorporate successive scheduling. */\n if (queuedCount > 0)\n {\n --queuedCount;\n // If the new slice finishes in time, count it as opening up space.\n if (this.emaSliceTime <= timeSpanMs - remainingTime)\n ++space;\n }\n else // No queued slice can run so space has opened up.\n ++space;\n }\n }\n // Optimize to short-circuit when queued > 1, because we won't call fetchWork in that case.\n let queued = [null, null];\n if (queuedCount < 2)\n queued = (queuedCount === 1) ? [this.queuedSlices[0]] : [];\n return { queued, working: (this.workingSliceCount - space) * this.estimateDensity };\n }\n\n // _Idx\n //\n // reserveOneSlice, resetSlices, destroy, evaluatorDownCleanup\n //\n\n /**\n * Find one ready slice, mark it as working and return.\n * If there are no ready slices return undefined.\n * @returns {Slice}\n */\n reserveOneSlice ()\n {\n for (const slice of this.sliceInventory)\n if (slice.isReady)\n return slice.markAsReserved();\n return null;\n }\n\n /**\n * Error recovery of slices.\n * When evaluator is down and it goes up again, there may be\n * working slices from working sandboxes that were terminated.\n * @param {string} [tag]\n */\n resetSlices (tag)\n {\n this.sliceInventory.forEach((slice) => {\n if (this.evaluatorDownHandle)\n this.evaluatorDownHandle.intr(); // Interrupt a$sleep in evaluatorDownCleanup\n this.evaluatorDownHandle = null;\n if (slice.isWorking || slice.isFailed)\n slice.resetState();\n });\n //this.dumpSlices(tag); // SAVE\n }\n\n /**\n * Destructor.\n * @returns {Promise<*>}\n */\n destroy ()\n {\n selectiveDebug() && console.debug(`JobManager.destroy: terminating sandboxes and returning slices to scheduler for job manager ${this.identifier}.`);\n this.sandboxInventory.forEach((sandbox) => { this.supervisor.returnSandbox(sandbox); });\n const reason = 'JobManager destroy';\n return this.supervisor.returnSlices(this.unFinishedSlices, reason)\n .finally (() => {\n this.#sliceInventory = [];\n this.state.removeAllListeners();\n if (this.state.isNot(BROKEN))\n this.state.set([ INITIAL, READY, STOP, REFUSE ], BROKEN);\n this.jobEmit('flush');\n });\n }\n\n /**\n * Destructor.\n * Called when the evaluator goes down (or screensaver goes up), in the sandbox 'terminated' event handler in Supervisor.\n * A slice marked as COMPLETE but not FINISHED means it still needs to submit results.\n * This function allows such slices to still submit results.\n * Because sometimes a screensaver only goes down for a minute or less (e.g. the user needs to look at something quick),\n * we delay an average of 90 (or 1.5 * delay) seconds before returning non-complete slices.\n * param {number} [delay=60] - Average delay is 1.5 * delay seconds.\n */\n evaluatorDownCleanup (delay = 60)\n {\n selectiveDebug() && console.debug(`JobManager.evaluatorDownCleanup: delay returning non-complete slices for on average ${1.5 * delay} seconds, ${this.identifier}.`);\n if (this.sandboxInventory.length > 0)\n this.warning('JobManager.evaluatorDownCleanup: When the evaluator is down there should not be any non-terminated sandboxes left.');\n // Call retVal.intr() to interrupt and cancel the timer inside a$sleepMs.\n return (this.evaluatorDownHandle = a$sleepMs(delay * (1.0 + Math.random()) * 1000)\n .finally(() => {\n const slicesToReturn = this.sliceInventory.filter((slice) => !(slice.isComplete || slice.isFinished));\n const reason = 'JobManager evaluatorDownCleanup';\n return this.supervisor.returnSlices(slicesToReturn, reason);\n }));\n }\n\n // _Idx\n //\n // assignSandbox, runSlice, runSliceOnSandbox\n // Functions chain and return non-awaited promises.\n //\n\n /**\n * Create a Sandbox.\n * Start it. Assign it. Add it to jobManager inventory.\n * @param {Slice} slice\n * @returns {Promise<Sandbox>}\n */\n async assignSandbox (slice)\n {\n try\n {\n // We want to await createSandbox so we don't create a large number of sandboxes all at once.\n const sandbox = await this.supervisor.createSandbox()\n .catch((error) => {\n // Catches exception from createSandbox.\n // Shound be rare unless evaluator or screensaver goes down.\n selectiveDebug2() && !this.supervisor.evaluator.down && console.debug('assignSandbox: createSandbox error', slice.identifier, error);\n slice.unReserve(); // reuse slice\n throw error;\n });\n selectiveDebug2() && console.debug('assignSandbox', slice.identifier);\n sandbox.slice = slice; // Must be before sandbox.assign; cf. fetchModule exception in handleRing2Message.\n return sandbox.assign(this)\n .then((assignedSandbox) => {\n selectiveDebug2() && console.debug('assignSandbox: success', sandbox.identifier);\n this.supervisor.sandboxInventory.push(assignedSandbox);\n return assignedSandbox;\n })\n .catch((error) => {\n // Exception is from sandbox.assign.\n // It could be a string coming from evaluator.\n this.error(`Failed to assign job to sandbox ${sandbox.identifier}`, error);\n slice.unReserve(); // reuse slice\n throw error;\n });\n }\n catch (error)\n {\n this.error(`JobManager.assignSandbox failed to find sandbox to run slice ${slice.identifier}`, error);\n throw error;\n }\n }\n\n /**\n * Create or reuse a sandbox and run a slice on it.\n * @param {Slice} slice - The slice to execute on a sandbox.\n * @returns {Promise<any>}\n */\n runSlice (slice)\n {\n try\n {\n selectiveDebug() && console.debug('runSlice:', slice.identifier, 'assigned/working', this.assignedSandboxes.length, this.workingSandboxes.length);\n\n if (this.supervisor.sliceTiming)\n slice['queueingDelta'] = Date.now();\n\n const _assignedSandboxes = this.assignedSandboxes;\n if (_assignedSandboxes.length > 0)\n {\n const sandbox = _assignedSandboxes[0];\n selectiveDebug2() && console.debug(`runSlice(assigned): sandbox ${sandbox.identifier}`, Date.now() - this.supervisor.lastTime);\n // When evaluator goes down, all sandboxes are terminated.\n // A ready slice has a finite lifetime and if exceeded may transition back to READY, in which\n // case we bail since it might already be in the process of being selected in roundRobinSlices.\n if (slice.isReady || sandbox.isTerminated)\n return Promise.resolve();\n slice.markAsWorking();\n sandbox.markAsWorking();\n sandbox.slice = slice; // Reusing assigned sandbox, so set the slice.\n return this.runSliceOnSandbox(slice, sandbox)\n .catch((error) => {\n // Catches exception from runSliceOnSandbox; s.b. rare.\n this.error(`JobManager.runSlice: Failure trying to run slice ${slice.identifier} on a assigned sandbox.`, error);\n return this.supervisor.returnSlice(slice, 'JobManager.runSlice');\n });\n }\n\n return this.assignSandbox(slice)\n .then((sandbox) => {\n selectiveDebug2() && console.debug(`runSlice: sandbox ${sandbox.identifier}`, Date.now() - this.supervisor.lastTime);\n // When evaluator goes down, all sandboxes are terminated.\n // A ready slice has a finite lifetime and if exceeded may transition back to READY, in which\n // case we bail since it might already be in the process of being selected in roundRobinSlices.\n if (slice.isReady || sandbox.isTerminated)\n return Promise.resolve();\n slice.markAsWorking();\n sandbox.markAsWorking();\n return this.runSliceOnSandbox(slice, sandbox);\n }, (error) => {\n // Catches exception from assignSandbox for error control flow.\n selectiveDebug() && console.error(`JobManager.runSlice: Failure from assignSandbox when trying to run slice ${slice.identifier} on a sandbox.`, error);\n })\n .catch((error) => {\n // Catches exception from runSliceOnSandbox; s.b. rare.\n this.error(`JobManager.runSlice: Failure trying to run slice ${slice.identifier} on a sandbox.`, error);\n return this.supervisor.returnSlice(slice, 'JobManager.runSlice');\n });\n }\n catch (error)\n {\n this.error(`JobManager.runSlice failed to run slice ${slice.identifier}`, error);\n return this.supervisor.returnSlice(slice, 'JobManager.runSlice_static_catch');\n }\n }\n\n /**\n * Execute slice on sandbox, collect results or handle errors, and clean up.\n * @param {Slice} slice - The slice to execute on the sandbox.\n * @param {Sandbox} sandbox - The sandbox on which to execute the slice.\n * @returns {Promise<any>}\n */\n runSliceOnSandbox (slice, sandbox)\n {\n const handleError = (error, tag) => {\n if (sandbox.isTerminated)\n {\n this.error(`The screensaver or evaluator may be down: slice ${slice.identifier}`, error);\n return this.supervisor.returnSlice(slice, `${tag}: sandbox terminated`);\n }\n else\n {\n this.error(`Failure in ${tag} for slice ${slice.identifier}`, error);\n const reason = (error.code === 'EPERM_ORIGIN' || error.code === 'EFETCH_BAD_ORIGIN' || error.code === 'EFETCH') ? error.code : tag;\n this.supervisor.returnSlice(slice, reason);\n }\n };\n debugging('supervisor') && console.debug(`runSliceOnSandbox ${sandbox.identifier} #(r/rsv/w/wsbx/sbx), ${this.supervisor.dbg.workingSliceSandboxStr}, #assigned, ${this.assignedSandboxes.length}, #localSBs, ${this.sandboxInventory.map(s => Number(s.id)).sort((x,y)=>x-y)}`);\n selectiveDebug() && console.debug('runSliceOnSandbox', sandbox.identifier, this.workingSliceCount, Date.now() - this.supervisor.lastTime);\n\n if (sandbox.isTerminated) // When evaluator goes down, all sandboxes are terminated.\n return Promise.resolve();\n\n this.sandboxEmit(sandbox, 'slice', slice.sliceNumber);\n\n if (this.supervisor.sliceTiming)\n {\n slice['queueingDelta'] = Date.now() - slice['queueingDelta'];\n slice['executionDelta'] = Date.now();\n }\n slice.startTime = Date.now();\n\n return sandbox.work()\n .finally(() => {\n if (this.supervisor.sliceTiming)\n {\n slice['executionDelta'] = Date.now() - slice['executionDelta'];\n slice['resultDelta'] = Date.now();\n }\n })\n .then((result) => {\n selectiveDebug2() && console.debug(`runSliceOnSandbox: sandbox${sandbox.id} - success: ${slice.identifier}`, Boolean(result), Date.now() - slice.startTime);\n\n if (sandbox.isTerminated)\n throw new Error(`Slice ${slice.identifier} completed work, but the sandbox ${sandbox.id} is terminated.`);\n\n // Allow reuse of sandbox up to a limit of dcpConfig.supervisor.sandbox.maxSandboxUse .\n sandbox.checkSandboxReUse();\n // Transition slice to COMPLETE\n slice.collectResult(result, true /*success*/);\n\n // Record result.\n return this.supervisor.recordResult(slice, sandbox)\n .catch((error) => {\n // Catches exception from supervisor.recordResult .\n return handleError(error, 'recordResult');\n });\n\n }, (error) => {\n // Catches exception from sandbox.work.\n // Kills sandbox and either retries the slice or transitions slice to FAILED and returns to scheduler.\n const reason = this.supervisor.handleSandboxWorkError(sandbox, slice, error);\n selectiveDebug() && console.error(`runSliceOnSandbox: Failure executing slice on sandbox: ${sandbox.identifier}`, reason, error);\n })\n .catch((error) => {\n // Catches exception from sandbox.work or then-clause.\n return handleError(error, 'runSliceOnSandbox');\n });\n }\n\n // _Idx\n //\n // removeSlice\n //\n\n /**\n * Remove slice from this.sliceInventory.\n * @param {Slice} slice - The slice to remove from this.sliceInventory.\n * @return {boolean} - False when slice is not in this.sliceInventory.\n */\n removeSlice (slice)\n {\n debugging('supervisor') && console.debug(`removeSlice slice ${slice.sliceNumber}`);\n return common.removeElement(this.sliceInventory, slice);\n }\n\n // _Idx\n //\n // fetchError, fetchJob, fetchSliceData\n //\n\n fetchError (error)\n {\n if (error.code === 'EPERM_ORIGIN')\n throw new BadOriginError(error);\n if (error.code === 'EFETCH')\n throw new RemoteFetchError(error);\n throw new UncaughtExceptionError(error);\n }\n\n /**\n * Fetch work function, work function arguments and possibly the range object describing the jobs slices..\n * @param {object} mpe - messagePayloadElement: job object returned by task-jobs.js . \n * @returns {Promise<*>}\n */\n fetchJob (mpe)\n {\n debugging('supervisor') && utils.dumpObject(mpe, 'JobManager.fetchJob: mpe', 512);\n\n const promises = [];\n try\n {\n // Get workFn.\n if (!mpe.workFunction)\n {\n const workFunctionPromise = this.fetchWorkFunctions(mpe.codeLocation)\n .then((workFunction) => {\n this.inputDataSize += workFunction.size;\n mpe.workFunction = workFunction.data;\n if (mpe.requirements.useStrict)\n mpe.useStrict = true;\n delete mpe.codeLocation;\n }, (error) => {\n this.fetchError(error);\n });\n promises.push(workFunctionPromise);\n }\n\n if (!mpe.mro && mpe.MROLocation)\n {\n const mroPromise = this.fetchData(mpe.MROLocation)\n .then((mro) => {\n // It's not ready for computing the inputDataSize yet -- that's done in JobManager.fetchSliceData .\n mpe.mro = mro.data;\n delete mpe.MROLocation;\n }, (error) => {\n this.fetchError(error);\n });\n promises.push(mroPromise)\n }\n\n // Get workFn args.\n if (!mpe.arguments && mpe.argumentsLocation)\n {\n mpe.arguments = new Array(mpe.argumentsLocation.length);\n for (let k = 0; k < mpe.argumentsLocation.length; k++)\n promises.push(this.fetchArguments(mpe.argumentsLocation[k].value)\n .then((arg) => {\n this.inputDataSize += arg.size;\n mpe.arguments[k] = arg.data;\n }, (error) => {\n this.fetchError(error);\n })\n );\n }\n\n return Promise.all(promises)\n .then(() => {\n if (mpe.argumentsLocation)\n delete mpe.argumentsLocation;\n return mpe;\n }, (error) => {\n this.fetchError(error);\n });\n }\n catch (error)\n {\n this.fetchError(error);\n }\n }\n\n /**\n * Look up slice.datumUri or use the range object this.mro, constructed by fetchJob.\n * @param {Slice} slice \n * @returns {Promise<{ data: *, size: number }>}\n */\n fetchSliceData (datumUri, slice)\n {\n try\n {\n if (!datumUri)\n {\n if (!this.mro)\n throw new Error('Must complete call to JobManager.fetchJob before calling JobManager.fetchSliceData.');\n /** XXXpfr @todo Inefficient, we're rehydrating the whole range for a single slice datum. */\n const ro = rehydrateRange(this.mro);\n // Slice numbers start at 1.\n const sliceDatum = ro[slice.sliceNumber - 1];\n debugging('supervisor') && console.debug(`Fetched mro datum: ${stringify(sliceDatum, 512)}`);\n return Promise.resolve({ data: sliceDatum, size: JSON.stringify(sliceDatum).length });\n }\n\n return this.fetchData(datumUri)\n .catch((error) => {\n throw this.fetchError(error);\n });\n }\n catch (error)\n {\n this.fetchError(error);\n }\n }\n\n // _Idx\n //\n // jobEmit, sandboxEmit, error, warning. countSliceStr, dumpSlices\n //\n\n /**\n * Safe event emitter on jobHandle.\n * @param {string} event\n * @param {...any} args\n */\n jobEmit(event, ...args)\n {\n this.supervisor.safeEmit(this.jobHandle, event, ...args);\n }\n\n /**\n * Safe event emitter on sandboxHandle.\n * @param {Sandbox} sandbox\n * @param {string} event\n * @param {...any} args\n */\n sandboxEmit(sandbox, event, ...args)\n {\n this.supervisor.safeEmit(sandbox.sandboxHandle, event, ...args);\n }\n\n /**\n * Error feedback to user.\n * @param {string} message\n * @param {Array<Error>|Error|string} [coreError]\n * @param {string} [additionalInfo]\n */\n error (message, coreError, additionalInfo)\n {\n this.supervisor.error(message, coreError, additionalInfo);\n }\n\n /**\n * Warning feedback to user.\n * @param {string[]} messages\n */\n warning (...messages)\n {\n this.supervisor.warning(...messages);\n }\n\n /**\n * Debugging helper.\n * @param {string} [tag='']\n * @returns {string}\n */\n countSliceStr (tag)\n {\n const { unassigned, ready, reserved, working, workdone, complete, failed, finished } = this.dumpSlices (tag, false, false);\n return `${tag}: u/r/rsv/w/wd/c/f/fsh ${unassigned}/${ready}/${reserved}/${working}/${workdone}/${complete}/${failed}/${finished}`;\n }\n\n /**\n * @param {URL|string} uri\n * @returns {Promise<*>}\n */\n fetchData (uri) { return fetchURIAndLength(uri, this.originManager, allowOriginsPurposes.fetchData); }\n /**\n * @param {URL|string} uri\n * @returns {Promise<*>}\n */\n fetchArguments (uri) { return fetchURIAndLength(uri, this.originManager, allowOriginsPurposes.fetchArguments); }\n /**\n * @param {URL|string} uri\n * @returns {Promise<*>}\n */\n fetchWorkFunctions (uri) { return fetchURIAndLength(uri, this.originManager, allowOriginsPurposes.fetchWorkFunctions); }\n\n /**\n * Debugging helper.\n * @param {string} [tag='']\n * @param {boolean} [details=false]\n * @param {boolean} [display=true]\n * @returns {{ ready, reserved, working, workdone, complete, failed, finished, unassigned }}\n */\n dumpSlices (tag = '', details = false, display = true)\n {\n if (display)\n console.log(`dumpSlices(${tag}): ${this.identifier}, sliceCount ${this.sliceInventory.length}, sandboxCount ${this.sandboxInventory.length}`, Date.now() - this.supervisor.lastTime);\n if (this.sliceInventory.length < 1) return { unassigned:0, ready:0, reserved:0, working:0, workdone:0, complete:0, failed:0, finished:0 };\n if (details)\n {\n // Detailed classification of slices.\n const unassigned = [], ready = [], reserved = [], working = [], workdone = [], complete = [], failed = [], finished = [];\n for (const slice of this.sliceInventory)\n {\n if (slice.isReady)\n ready.push(slice);\n else if (slice.isReserved)\n reserved.push(slice);\n else if (slice.isWorking)\n working.push(slice);\n else if (slice.isWorkDone)\n workdone.push(slice);\n else if (slice.isComplete)\n complete.push(slice);\n else if (slice.isFailed)\n failed.push(slice);\n else if (slice.isFinished)\n finished.push(slice);\n else if (slice.isUnassigned)\n unassigned.push(slice);\n else\n throw new Error(`dumpSlices: Unexpected kind a slice; ${slice.identifier}`);\n }\n if (display)\n {\n const dmpSlices = (name, slices) => {\n console.log(`-----${name}(${slices.length})-----------------------------------------------------------------`);\n for (const slice of slices)\n console.log(slice.identifier);\n }\n dmpSlices('unassigned', unassigned);\n dmpSlices('ready', ready);\n dmpSlices('reserved', reserved);\n dmpSlices('working', working);\n dmpSlices('workdone', workdone);\n dmpSlices('complete', complete);\n dmpSlices('failed', failed);\n dmpSlices('finished', finished);\n console.log('-----------------------------------------------------------------------------------');\n }\n return {\n ready: ready.length,\n reserved: reserved.length,\n working: working.length,\n workdone: workdone.length,\n complete: complete.length,\n failed: failed.length,\n finished: finished.length,\n unassigned: unassigned.length,\n };\n }\n else\n {\n // Quick classification of slices.\n const { ready,\n reserved,\n working,\n workdone,\n complete,\n failed,\n finished,\n unassigned } = common.sliceCounts(this.sliceInventory);\n if (display)\n {\n const dumpCount = (name, count) => {\n console.log(`-----${name}(${count})-----------------------------------------------------------------`);\n }\n dumpCount('unassigned', unassigned);\n dumpCount('ready', ready);\n dumpCount('reserved', reserved);\n dumpCount('working', working);\n dumpCount('workdone', workdone);\n dumpCount('complete', complete);\n dumpCount('failed', failed);\n dumpCount('finished', finished);\n console.log('-----------------------------------------------------------------------------------');\n }\n return { ready, reserved, working, workdone, complete, failed, finished, unassigned };\n }\n }\n\n}\nexports.JobManager = JobManager;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor2/job-manager.js?");
4739
4739
 
4740
4740
  /***/ }),
4741
4741
 
@@ -4809,7 +4809,7 @@ eval("/**\n * @file dcp/src/dcp-client/worker/supervisor2/sandbox2.js\n *\n * A
4809
4809
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4810
4810
 
4811
4811
  "use strict";
4812
- eval("/**\n * @file dcp/src/dcp-client/worker/supervisor2/slice2.js\n *\n * A wrapper for the slice object returned from the scheduler's task distributor.\n *\n * @author Matthew Palma, mpalma@kingsds.network\n * Ryan Rossiter, ryan@kingsds.network\n * Wes Garland, wes@distributive.network\n * Paul, paul@distributive.network\n * @date May 2019\n * May 2019\n * Decemeber 2020\n * June 2022, Feb-April 2023\n * @module slice\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n// @ts-check\n\n\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { a$sleepMs, stringify } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { truncateAddress, selectiveDebug } = __webpack_require__(/*! ./common */ \"./src/dcp-client/worker/supervisor2/common.js\");\n\nconst INITIAL = 'INITIAL';\nconst READY = 'READY';\nconst RESERVED = 'RESERVED';\nconst WORKING = 'WORKING';\nconst WORKDONE = 'WORKDONE';\nconst COMPLETE = 'COMPLETE';\nconst FAILED = 'FAILED';\nconst FINISHED = 'FINISHED';\n\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {import('./job-manager').JobHandle} JobHandle */\n/** @typedef {import('./job-manager').JobManager} JobManager */\n/** @typedef {import('dcp/utils/jsdoc-types').AuthMessage} AuthMessage */\n/** @typedef {import('dcp/utils/jsdoc-types').SliceMessage} SliceMessage */\n\n/** \n * Object use to represent a given slice inside the Supervisor. This object's shape \n * current inherits heavily from the message payload originating from the scheduler,\n * but should not /wg dec 2020.\n *\n * Caveat lector: this documentation was created without a 100% understanding of the code.\n * Please improve when possible.\n * \n * The read-only properties of this object are as follows:\n * - state INITIAL | READY | RESERVED | WORKING | WORKDONE | COMPLETE | FAILED | FINISHED\n * - sliceNumber the number of the slice within this job\n * - jobAddress the address of the job that this slice belongs to\n * - isEstimation true when slice is used in estimation\n * - isLong true when slice execution longer than (supervisor.options.targetTaskDuration || 300) seconds\n * - datum input set element for this slice this slice of the job; could be a data: URI or it could\n * be a URL we need to fetch; note that fetches are limited to worker's allow list\n * - result\n * - result.request 'complete',...\n * - result.result return value from work function\n * - result.timeReport { total, CPU, webGL } ms\n * - error error info when slice FAILED\n * - jobManager wrapper for the job that owns this slice\n * - resultStorageType 'values' => we are storing individual values,\n * which could be data: URIs or URLs\n * at the scheduler\n * 'pattern' => user-specified pattern for result URLs.\n * Data will be uploaded via POST to the\n * URL matching the pattern, provided the\n * worker is allowed to access it.\n * - resultStorageParams user-supplied POST parameters sent to the result \n * storage server when using resultStorageType = pattern.\n * - resultStorageDetails the pattern when using resultStorageType = pattern.\n * - authorizationMessage authorization from task distributor, sent to result submitter, etc...\n * \n * - isUnassigned true, when in state INITIAL\n * - isReady true, when in state READY\n * - isReserved true, when in state RESERVED\n * - isWorking true, when in state WORKING\n * - isWorkDone true, when in state WORKDONE <-- work has completed\n * - isComplete true, when in state COMPLETE <-- work has completed successfully\n * - isFailed true, when in state FAILED <-- work has failed\n * - isFinished true, when in state FINISHED <-- Indicates result submission has succeeded.\n *\n * - isQueued true, when in state INITIAL, READY\n * - isActive true, when in state RESERVED, WORKING, WORKDONE, COMPLETE\n * - identifier string 'sliceNumber.jobAddress.state'\n * - timeReport accessor for this.result.timeReport that updates from this.rejectedTimeReport when appropriate\n *\n * The r/w properties of this object are as follows:\n * - startTime time when slice execution started\n * - hasBeenRejected when true, slice has been rejected at least once\n * - rejectedTimeReport rejected timeReport\n * \n * NOTE: If you ever use a property with a leading underscore you are probably making a mistake.\n * But if you must, please ask paul, yarn, bryan or eddie for a CR.\n *\n * INITIAL -> READY: - when slice data has been successfully fetched\n * INITIAL -> BROKEN: - when slice data has not been fetched successfully\n * READY -> RESERVED: - when a slice has been scheduled to run on a sandbox\n * RESERVED -> WORKING: - right before Sandbox2.work is called\n * WORKING -> WORKDONE: - right after Sandbox2.work completes successfully or not\n * WORKDONE -> COMPLETE: - when Slice2.collectResults is called and Sandbox2.work completed successfully\n * WORKDONE -> FAILED: - when Slice2.collectResults is called and Sandbox2.work did not completed successfully\n * COMPLETE -> FINISHED: - when submitting results succeeds\n * COMPLETE -> FAILED: - when submitting results fails\n */\nclass Slice\n{\n /** @type {Synchronizer} */\n #state;\n /** @type {JobManager} */\n #jobManager;\n /** @type {SliceMessage} */\n #sliceMessage;\n /** @type {Promise<void>} */\n #fetchSliceDataPromise;\n /** @type {AuthMessage} */\n #authorizationMessage;\n // private objects\n #datum;\n #result;\n #error;\n\n /**\n * @param {JobManager} jobManager\n * @param {SliceMessage} sliceMessage\n * @param {AuthMessage} authorizationMessage\n */\n constructor (jobManager, sliceMessage, authorizationMessage)\n {\n this.#state = new Synchronizer(INITIAL, [ INITIAL, READY, RESERVED, WORKING, WORKDONE, COMPLETE, FAILED, FINISHED ]);\n this.#jobManager = jobManager;\n this.#sliceMessage = { ...sliceMessage };\n this.#fetchSliceDataPromise = null;\n this.#authorizationMessage = authorizationMessage;\n this.#datum = null;\n this.#result = null;\n this.#error = null;\n /** @type {number} */\n this.startTime = 0;\n /** @type {boolean} */\n this.hasBeenRejected = false;\n /** @type {{ total: number, CPU: number, webGL: number, webGPU: number }} */\n this.rejectedTimeReport = null;\n /** @type {number} */\n this.inputDataSize = 0;\n \n assert(this.jobAddress === String(this.#sliceMessage.jobAddress));\n if (!this.authorizationMessage)\n throw new Error(`Undefined authorization for slice ${this.identifier}.`);\n \n /** \n * Start loading slice datum in the background. Once these are loaded, slice state will\n * transition to READY and the slice will be ready to transition to WORKING.\n */\n this.#fetchSliceDataPromise = this.fetchSliceData();\n }\n\n /** @type {Synchronizer} */\n get state () { return this.#state; }\n /** @type {number} */\n get sliceNumber () { return this.#sliceMessage.sliceNumber; }\n /** @type {string} */\n get jobAddress () { return this.#jobManager.address; }\n /** @type {boolean} */\n get isEstimation () { return this.#sliceMessage.isEstimationSlice; }\n /** @type {boolean} */\n get isLong () { return this.#sliceMessage.isLongSlice; }\n /** @type {string} */\n get datumUri () { return this.#sliceMessage.datumUri; }\n /** @type {JobManager} */\n get jobManager () { return this.#jobManager; }\n /** @type {JobHandle} */\n get jobHandle () { return this.#jobManager.jobHandle; }\n /** @type {string} */\n get resultStorageType () { return this.#sliceMessage.resultStorageType; }\n /** @type {string} */\n get resultStorageDetails () { return this.#sliceMessage.resultStorageDetails; }\n /** @type {Promise<void>} */\n get fetchSliceDataPromise () { return this.#fetchSliceDataPromise; }\n /** @type {AuthMessage} */\n get authorizationMessage () { return this.#authorizationMessage; }\n\n /** Read-only properties of type object. */\n get datum () { return this.#datum; }\n get result () { return this.#result; }\n get error () { return this.#error; }\n get resultStorageParams () { return this.#sliceMessage.resultStorageParams; }\n\n /** @type {boolean} */\n get isUnassigned () { return this.state.is(INITIAL); }\n /** @type {boolean} */\n get isReady () { return this.state.is(READY); }\n /**\n * Mark a slice as RESERVED to remove it from the ready list, yet still able to transition to WORKING.\n * @type {boolean}\n **/\n get isReserved () { return this.state.is(RESERVED); }\n /** @type {boolean} */\n get isWorking () { return this.state.is(WORKING); }\n /** @type {boolean} */\n get isWorkDone () { return this.state.is(WORKDONE); }\n /** @type {boolean} */\n get isComplete () { return this.state.is(COMPLETE); }\n /** @type {boolean} */\n get isFailed () { return this.state.is(FAILED); }\n /** @type {boolean} */\n get isFinished () { return this.state.is(FINISHED); }\n /** @type {boolean} */\n get isQueued () { return this.isUnassigned || this.isReady; }\n /** @type {boolean} */\n get isActive () { return this.isReserved || this.isWorking || this.isWorkDone || this.isComplete; }\n /** @type {boolean} */\n get isQueuedOrActive () { return !(this.isFailed || this.isFinished); }\n\n /** @type {string} */\n get identifier () {\n return `${this.sliceNumber}.${truncateAddress(this.jobAddress)}.${this.state}`;\n }\n /**\n * A unique string characterizing a slice. Used in key-value patterns.\n * @type {string}\n **/\n get key () {\n return `${this.sliceNumber}.${this.jobAddress}`;\n }\n /** \n * timeReport accessor that optionally updates from this.rejectedTimeReport. \n * @type {{ total: number, CPU: number, webGL: number, webGPU: number }}\n **/\n get timeReport ()\n {\n if (!this.result)\n throw new Error('Slice result is not ready'); // Should never fire.\n if (!this.result.timeReport)\n throw new Error('Slice timeReport is not ready'); // Should never fire.\n if (this.rejectedTimeReport?.total > 0 && this.result.timeReport)\n {\n // Data collected from sandboxes that rejected this slice.\n ['total', 'CPU', 'webGL', 'webGPU'].forEach((key) => {\n if (this.rejectedTimeReport[key])\n this.result.timeReport[key] += this.rejectedTimeReport[key];\n });\n this.rejectedTimeReport = null;\n }\n return this.result.timeReport;\n }\n /**\n * dataReport accessor for the IO part of CGIO.\n * @type {{ InDataSize: number, OutDataSize: number }}\n **/\n get dataReport ()\n {\n if (!this.result)\n throw new Error('Slice result is not ready'); // Should never fire.\n if (!this.result.dataReport)\n throw new Error('Slice dataReport is not ready'); // Should never fire.\n return this.result.dataReport;\n }\n /**\n * Return the elapsed time to estimated slice completion time.\n * @type {number}\n **/\n get etaMs ()\n {\n /** @todo XXXpfr Verify the startTime logic is correct. */\n if (this.startTime === null) return 0; // slice is either COMPLETE, FINISHED or FAILED\n let etaMs = this.jobManager.emaSliceTime;\n if (this.startTime) etaMs -= (Date.now() - this.startTime);\n if (etaMs < 0) etaMs = 0;\n return etaMs;\n }\n\n /** Start slice over, regardless of what state it is in. */\n resetState ()\n {\n if (this.isQueued) return;\n this.#state = new Synchronizer(READY, [ INITIAL, READY, RESERVED, WORKING, WORKDONE, COMPLETE, FAILED, FINISHED ]);\n this.#error = null;\n this.#result = null;\n }\n\n /** \n * Sets the slice status to RESERVED, called to remove slice from the ready list,\n * yet still able to transition to WORKING.\n * @returns {Slice}\n */\n markAsReserved ()\n {\n this.state.set(READY, RESERVED);\n this.startTime = Date.now(); // A ready slice has a finite lifetime (e.g. 5 minutes.)\n return this;\n }\n\n /**\n * Sets the slice status to WORKING, called when the slice is getting ready to be handed to a worker.\n * @returns {boolean}\n */\n markAsWorking() { return this.state.setIf(RESERVED, WORKING); }\n\n /** Something went wrong, but reuse the slice. */\n unReserve () { this.state.set(RESERVED, READY); }\n\n /**\n * Sets the slice status to WORKDONE, called in sandbox.work when slice has finished executiion.\n * This needs to be set before the sandbox 'complete' event fires, or else supervisor.roundRobinSlices\n * may miss scheduling another slice on the empty sandbox slot.\n **/\n markAsWorkDone() { this.state.set(WORKING, WORKDONE); }\n\n /** Sets the slice status to FINISHED, called when the slice has completed and submitted results. */\n markAsFinished() { this.state.set(COMPLETE, FINISHED); }\n\n /** Sets the slice status to FINISHED, called when the slice is being returned to scheduler by Supervisor.repoMan. */\n repoMan() { this.state.set(READY, FINISHED); }\n\n /** Hide the result when returning slices. */\n killResult () { this.#result = null; }\n\n /**\n * Fetch slice data.\n * @returns {Promise<void>}\n */\n fetchSliceData()\n {\n return this.jobManager.fetchSliceData(this.datumUri, this)\n .then ((sliceData) => {\n debugging('slice') && console.debug(`Slice ${this.identifier} is transitioning to READY`);\n this.inputDataSize = sliceData.size;\n this.#datum = sliceData.data;\n this.state.set(INITIAL, READY);\n })\n .catch((error) => {\n selectiveDebug() && console.debug(`fetchSliceData failed for slice ${this.identifier}`, error);\n this.#error = error;\n this.state.set(INITIAL, FAILED);\n throw error;\n });\n }\n\n /**\n * Receives a result from the scheduler.\n * When success is true, it will set the result to this.#result.\n * When success is false, it will set the error to this.#error.\n * If this.#result is populated, it is possible to set the error, but setting the result will throw.\n * If this.#error is populated, then collectResult cannot be called w/o emitting an exception.\n *\n * @param {object|Error} result - The result from the worker sandbox or an error.\n * @param {boolean} [success=true] - True if result is considered successful, false if error occurred.\n */\n collectResult(result, success = true)\n {\n debugging('slice') && console.debug(`collectResult for slice ${this.identifier}`, success, this.error);\n\n this.startTime = null;\n if (success && this.result)\n throw new Error(`Slice ${this.identifier} already has a result.`);\n if (this.error)\n throw new Error(`Slice ${this.identifier} already has an error property.`);\n\n if (success)\n {\n this.state.set(WORKDONE, COMPLETE); // Work completed successfully.\n this.#result = result;\n }\n else\n {\n if (this.isComplete)\n this.state.set(COMPLETE, FAILED); // Failed to successfully submit results.\n else if (this.isWorkDone)\n this.state.set(WORKDONE, FAILED); // Work did not complete successfully.\n else\n this.state.set(WORKING, FAILED); // Work really did not complete successfully.\n this.#error = result;\n }\n }\n\n /**\n * Create basic message object as part of the payload to send back to the result-submitter's status operation.\n * @param {string} status - The kind of status operation\n * @param {object} [extraProperties={}] - Extra properties for the payload object.\n * @returns {object}\n */\n getMessage(status, extraProperties = {})\n {\n return {\n sliceNumbers: [this.sliceNumber],\n job: this.jobAddress,\n authorizationMessage: this.authorizationMessage,\n status,\n ...extraProperties,\n }; \n }\n\n /**\n * Create basic payload object to send back to the result-submitter's status operation.\n * @param {opaqueId} worker - The current worker's opaqueId\n * @param {string} status - The kind of status operation\n * @param {object} [extraProperties={}] - Extra properties for the payload object.\n * @returns {object}\n */\n getMessagePayload(worker, status, extraProperties = {})\n {\n return {\n worker,\n slices: [ this.getMessage(status, extraProperties) ],\n }; \n }\n\n /**\n * Create slice-return payload object to send to the result-submitter's status operation.\n * @param {opaqueId} worker - The current worker's opaqueId\n * @param {string} [reason] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @return {object}\n */\n getReturnMessagePayload(worker, reason)\n {\n this.killResult();\n\n debugging('slice') && console.debug(`getReturnMessagePayload for slice ${this.identifier} and reason ${reason}:`, this.error);\n if (!reason) reason = this.error ? 'EUNCAUGHT' : 'unknown';\n const extraProperties = {\n isEstimationSlice: this.isEstimation,\n error: this.error,\n reason,\n };\n\n return this.getMessagePayload(worker, 'return', extraProperties);\n }\n}\nexports.Slice = Slice;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor2/slice2.js?");
4812
+ eval("/**\n * @file dcp/src/dcp-client/worker/supervisor2/slice2.js\n *\n * A wrapper for the slice object returned from the scheduler's task distributor.\n *\n * @author Matthew Palma, mpalma@kingsds.network\n * Ryan Rossiter, ryan@kingsds.network\n * Wes Garland, wes@distributive.network\n * Paul, paul@distributive.network\n * @date May 2019\n * May 2019\n * Decemeber 2020\n * June 2022, Feb-April 2023\n * @module slice\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n// @ts-check\n\n\nconst { Synchronizer } = __webpack_require__(/*! dcp/common/concurrency */ \"./src/common/concurrency.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('worker');\nconst { a$sleepMs, stringify } = __webpack_require__(/*! dcp/utils */ \"./src/utils/index.js\");\nconst { truncateAddress, selectiveDebug } = __webpack_require__(/*! ./common */ \"./src/dcp-client/worker/supervisor2/common.js\");\n\nconst INITIAL = 'INITIAL';\nconst READY = 'READY';\nconst RESERVED = 'RESERVED';\nconst WORKING = 'WORKING';\nconst WORKDONE = 'WORKDONE';\nconst COMPLETE = 'COMPLETE';\nconst FAILED = 'FAILED';\nconst FINISHED = 'FINISHED';\n\n/** @typedef {string} opaqueId */ // 22 character base64 string\n/** @typedef {import('./job-manager').JobHandle} JobHandle */\n/** @typedef {import('./job-manager').JobManager} JobManager */\n/** @typedef {import('dcp/utils/jsdoc-types').AuthMessage} AuthMessage */\n/** @typedef {import('dcp/utils/jsdoc-types').SliceMessage} SliceMessage */\n\n/** \n * Object use to represent a given slice inside the Supervisor. This object's shape \n * current inherits heavily from the message payload originating from the scheduler,\n * but should not /wg dec 2020.\n *\n * Caveat lector: this documentation was created without a 100% understanding of the code.\n * Please improve when possible.\n * \n * The read-only properties of this object are as follows:\n * - state INITIAL | READY | RESERVED | WORKING | WORKDONE | COMPLETE | FAILED | FINISHED\n * - sliceNumber the number of the slice within this job\n * - jobAddress the address of the job that this slice belongs to\n * - isEstimation true when slice is used in estimation\n * - isLong true when slice execution longer than (supervisor.options.targetTaskDuration || 300) seconds\n * - datum input set element for this slice this slice of the job; could be a data: URI or it could\n * be a URL we need to fetch; note that fetches are limited to worker's allow list\n * - result\n * - result.request 'complete',...\n * - result.result return value from work function\n * - result.timeReport { total, CPU, webGL } ms\n * - error error info when slice FAILED\n * - jobManager wrapper for the job that owns this slice\n * - resultStorageType 'values' => we are storing individual values,\n * which could be data: URIs or URLs\n * at the scheduler\n * 'pattern' => user-specified pattern for result URLs.\n * Data will be uploaded via POST to the\n * URL matching the pattern, provided the\n * worker is allowed to access it.\n * - resultStorageParams user-supplied POST parameters sent to the result \n * storage server when using resultStorageType = pattern.\n * - resultStorageDetails the pattern when using resultStorageType = pattern.\n * - authorizationMessage authorization from task distributor, sent to result submitter, etc...\n * \n * - isUnassigned true, when in state INITIAL\n * - isReady true, when in state READY\n * - isReserved true, when in state RESERVED\n * - isWorking true, when in state WORKING\n * - isWorkDone true, when in state WORKDONE <-- work has completed\n * - isComplete true, when in state COMPLETE <-- work has completed successfully\n * - isFailed true, when in state FAILED <-- work has failed\n * - isFinished true, when in state FINISHED <-- Indicates result submission has succeeded.\n *\n * - isQueued true, when in state INITIAL, READY\n * - isActive true, when in state RESERVED, WORKING, WORKDONE, COMPLETE\n * - identifier string 'sliceNumber.jobAddress.state'\n * - timeReport accessor for this.result.timeReport that updates from this.rejectedTimeReport when appropriate\n *\n * The r/w properties of this object are as follows:\n * - startTime time when slice execution started\n * - hasBeenRejected when true, slice has been rejected at least once\n * - rejectedTimeReport rejected timeReport\n * \n * NOTE: If you ever use a property with a leading underscore you are probably making a mistake.\n * But if you must, please ask paul, yarn, bryan or eddie for a CR.\n *\n * INITIAL -> READY: - when slice data has been successfully fetched\n * INITIAL -> BROKEN: - when slice data has not been fetched successfully\n * READY -> RESERVED: - when a slice has been scheduled to run on a sandbox\n * RESERVED -> WORKING: - right before Sandbox2.work is called\n * WORKING -> WORKDONE: - right after Sandbox2.work completes successfully or not\n * WORKDONE -> COMPLETE: - when Slice2.collectResults is called and Sandbox2.work completed successfully\n * WORKDONE -> FAILED: - when Slice2.collectResults is called and Sandbox2.work did not completed successfully\n * COMPLETE -> FINISHED: - when submitting results succeeds\n * COMPLETE -> FAILED: - when submitting results fails\n */\nclass Slice\n{\n /** @type {Synchronizer} */\n #state;\n /** @type {JobManager} */\n #jobManager;\n /** @type {SliceMessage} */\n #sliceMessage;\n /** @type {Promise<void>} */\n #fetchSliceDataPromise;\n /** @type {AuthMessage} */\n #authorizationMessage;\n // private objects\n #datum;\n #result;\n #error;\n\n /**\n * @param {JobManager} jobManager\n * @param {SliceMessage} sliceMessage\n * @param {AuthMessage} authorizationMessage\n */\n constructor (jobManager, sliceMessage, authorizationMessage)\n {\n this.#state = new Synchronizer(INITIAL, [ INITIAL, READY, RESERVED, WORKING, WORKDONE, COMPLETE, FAILED, FINISHED ]);\n this.#jobManager = jobManager;\n this.#sliceMessage = { ...sliceMessage };\n this.#fetchSliceDataPromise = null;\n this.#authorizationMessage = authorizationMessage;\n this.#datum = null;\n this.#result = null;\n this.#error = null;\n /** @type {number} */\n this.startTime = 0;\n /** @type {boolean} */\n this.hasBeenRejected = false;\n /** @type {{ total: number, CPU: number, webGL: number, webGPU: number }} */\n this.rejectedTimeReport = null;\n /** @type {number} */\n this.inputDataSize = 0;\n \n assert(this.jobAddress === String(this.#sliceMessage.jobAddress));\n if (!this.authorizationMessage)\n throw new Error(`Undefined authorization for slice ${this.identifier}.`);\n \n /** \n * Start loading slice datum in the background. Once these are loaded, slice state will\n * transition to READY and the slice will be ready to transition to WORKING.\n */\n this.#fetchSliceDataPromise = this.fetchSliceData();\n }\n\n /** @type {Synchronizer} */\n get state () { return this.#state; }\n /** @type {number} */\n get sliceNumber () { return this.#sliceMessage.sliceNumber; }\n /** @type {string} */\n get jobAddress () { return this.#jobManager.address; }\n /** @type {boolean} */\n get isEstimation () { return this.#sliceMessage.isEstimationSlice; }\n /** @type {boolean} */\n get isLong () { return this.#sliceMessage.isLongSlice; }\n /** @type {string} */\n get datumUri () { return this.#sliceMessage.datumUri; }\n /** @type {JobManager} */\n get jobManager () { return this.#jobManager; }\n /** @type {JobHandle} */\n get jobHandle () { return this.#jobManager.jobHandle; }\n /** @type {string} */\n get resultStorageType () { return this.#sliceMessage.resultStorageType; }\n /** @type {string} */\n get resultStorageDetails () { return this.#sliceMessage.resultStorageDetails; }\n /** @type {Promise<void>} */\n get fetchSliceDataPromise () { return this.#fetchSliceDataPromise; }\n /** @type {AuthMessage} */\n get authorizationMessage () { return this.#authorizationMessage; }\n\n /** Read-only properties of type object. */\n get datum () { return this.#datum; }\n get result () { return this.#result; }\n get error () { return this.#error; }\n get resultStorageParams () { return this.#sliceMessage.resultStorageParams; }\n\n /** @type {boolean} */\n get isUnassigned () { return this.state.is(INITIAL); }\n /** @type {boolean} */\n get isReady () { return this.state.is(READY); }\n /**\n * Mark a slice as RESERVED to remove it from the ready list, yet still able to transition to WORKING.\n * @type {boolean}\n **/\n get isReserved () { return this.state.is(RESERVED); }\n /** @type {boolean} */\n get isWorking () { return this.state.is(WORKING); }\n /** @type {boolean} */\n get isWorkDone () { return this.state.is(WORKDONE); }\n /** @type {boolean} */\n get isComplete () { return this.state.is(COMPLETE); }\n /** @type {boolean} */\n get isFailed () { return this.state.is(FAILED); }\n /** @type {boolean} */\n get isFinished () { return this.state.is(FINISHED); }\n /** @type {boolean} */\n get isQueued () { return this.isUnassigned || this.isReady; }\n /** @type {boolean} */\n get isActive () { return this.isReserved || this.isWorking || this.isWorkDone || this.isComplete; }\n /** @type {boolean} */\n get isQueuedOrActive () { return !(this.isFailed || this.isFinished); }\n\n /** @type {string} */\n get identifier () {\n return `${this.sliceNumber}.${truncateAddress(this.jobAddress)}.${this.state}`;\n }\n /**\n * A unique string characterizing a slice. Used in key-value patterns.\n * @type {string}\n **/\n get key () {\n return `${this.sliceNumber}.${this.jobAddress}`;\n }\n /** \n * timeReport accessor that optionally updates from this.rejectedTimeReport. \n * @type {{ total: number, CPU: number, webGL: number, webGPU: number }}\n **/\n get timeReport ()\n {\n if (!this.result)\n throw new Error('Slice result is not ready'); // Should never fire.\n if (!this.result.timeReport)\n throw new Error('Slice timeReport is not ready'); // Should never fire.\n if (this.rejectedTimeReport?.total > 0 && this.result.timeReport)\n {\n // Data collected from sandboxes that rejected this slice.\n ['total', 'CPU', 'webGL', 'webGPU'].forEach((key) => {\n if (this.rejectedTimeReport[key])\n this.result.timeReport[key] += this.rejectedTimeReport[key];\n });\n this.rejectedTimeReport = null;\n }\n return this.result.timeReport;\n }\n /**\n * dataReport accessor for the IO part of CGIO.\n * @type {{ InDataSize: number, OutDataSize: number }}\n **/\n get dataReport ()\n {\n if (!this.result)\n throw new Error('Slice result is not ready'); // Should never fire.\n if (!this.result.dataReport)\n throw new Error('Slice dataReport is not ready'); // Should never fire.\n return this.result.dataReport;\n }\n /**\n * Return the elapsed time to estimated slice completion time.\n * @type {number}\n **/\n get etaMs ()\n {\n /** @todo XXXpfr Verify the startTime logic is correct. */\n if (this.startTime === null) return 0; // slice is either COMPLETE, FINISHED or FAILED\n let etaMs = this.jobManager.emaSliceTime;\n if (this.startTime) etaMs -= (Date.now() - this.startTime);\n if (etaMs < 0) etaMs = 0;\n return etaMs;\n }\n\n /** Start slice over, regardless of what state it is in. */\n resetState ()\n {\n if (this.isQueued) return;\n this.#state = new Synchronizer(READY, [ INITIAL, READY, RESERVED, WORKING, WORKDONE, COMPLETE, FAILED, FINISHED ]);\n this.#error = null;\n this.#result = null;\n }\n\n /** \n * Sets the slice status to RESERVED, called to remove slice from the ready list,\n * yet still able to transition to WORKING.\n * @returns {Slice}\n */\n markAsReserved ()\n {\n this.state.set(READY, RESERVED);\n this.startTime = Date.now(); // A ready slice has a finite lifetime (e.g. 5 minutes.)\n return this;\n }\n\n /**\n * Sets the slice status to WORKING, called when the slice is getting ready to be handed to a worker.\n * @returns {boolean}\n */\n markAsWorking() { return this.state.set(RESERVED, WORKING); }\n\n /** Something went wrong, but reuse the slice. */\n unReserve () { this.state.set(RESERVED, READY); }\n\n /**\n * Sets the slice status to WORKDONE, called in sandbox.work when slice has finished executiion.\n * This needs to be set before the sandbox 'complete' event fires, or else supervisor.roundRobinSlices\n * may miss scheduling another slice on the empty sandbox slot.\n **/\n markAsWorkDone() { this.state.set(WORKING, WORKDONE); }\n\n /** Sets the slice status to FINISHED, called when the slice has completed and submitted results. */\n markAsFinished() { this.state.set(COMPLETE, FINISHED); }\n\n /** Sets the slice status to FINISHED, called when the slice is being returned to scheduler by Supervisor.repoMan. */\n repoMan() { this.state.set(READY, FINISHED); }\n\n /** Hide the result when returning slices. */\n killResult () { this.#result = null; }\n\n /**\n * Fetch slice data.\n * @returns {Promise<void>}\n */\n fetchSliceData()\n {\n return this.jobManager.fetchSliceData(this.datumUri, this)\n .then ((sliceData) => {\n debugging('slice') && console.debug(`Slice ${this.identifier} is transitioning to READY`);\n this.inputDataSize = sliceData.size;\n this.#datum = sliceData.data;\n this.state.set(INITIAL, READY);\n })\n .catch((error) => {\n selectiveDebug() && console.debug(`fetchSliceData failed for slice ${this.identifier}`, error);\n this.#error = error;\n this.state.set(INITIAL, FAILED);\n throw error;\n });\n }\n\n /**\n * Receives a result from the scheduler.\n * When success is true, it will set the result to this.#result.\n * When success is false, it will set the error to this.#error.\n * If this.#result is populated, it is possible to set the error, but setting the result will throw.\n * If this.#error is populated, then collectResult cannot be called w/o emitting an exception.\n *\n * @param {object|Error} result - The result from the worker sandbox or an error.\n * @param {boolean} [success=true] - True if result is considered successful, false if error occurred.\n */\n collectResult(result, success = true)\n {\n debugging('slice') && console.debug(`collectResult for slice ${this.identifier}`, success, this.error);\n\n this.startTime = null;\n if (success && this.result)\n throw new Error(`Slice ${this.identifier} already has a result.`);\n if (this.error)\n throw new Error(`Slice ${this.identifier} already has an error property.`);\n\n if (success)\n {\n this.state.set(WORKDONE, COMPLETE); // Work completed successfully.\n this.#result = result;\n }\n else\n {\n if (this.isComplete)\n this.state.set(COMPLETE, FAILED); // Failed to successfully submit results.\n else if (this.isWorkDone)\n this.state.set(WORKDONE, FAILED); // Work did not complete successfully.\n else\n this.state.set(WORKING, FAILED); // Work really did not complete successfully.\n this.#error = result;\n }\n }\n\n /**\n * Create basic message object as part of the payload to send back to the result-submitter's status operation.\n * @param {string} status - The kind of status operation\n * @param {object} [extraProperties={}] - Extra properties for the payload object.\n * @returns {object}\n */\n getMessage(status, extraProperties = {})\n {\n return {\n sliceNumbers: [this.sliceNumber],\n job: this.jobAddress,\n authorizationMessage: this.authorizationMessage,\n status,\n ...extraProperties,\n }; \n }\n\n /**\n * Create basic payload object to send back to the result-submitter's status operation.\n * @param {opaqueId} worker - The current worker's opaqueId\n * @param {string} status - The kind of status operation\n * @param {object} [extraProperties={}] - Extra properties for the payload object.\n * @returns {object}\n */\n getMessagePayload(worker, status, extraProperties = {})\n {\n return {\n worker,\n slices: [ this.getMessage(status, extraProperties) ],\n }; \n }\n\n /**\n * Create slice-return payload object to send to the result-submitter's status operation.\n * @param {opaqueId} worker - The current worker's opaqueId\n * @param {string} [reason] - Optional reason for the return: 'ENOPROGRESS', 'EUNCAUGHT', 'ESLICETOOSLOW', 'unknown'.\n * @return {object}\n */\n getReturnMessagePayload(worker, reason)\n {\n this.killResult();\n\n debugging('slice') && console.debug(`getReturnMessagePayload for slice ${this.identifier} and reason ${reason}:`, this.error);\n if (!reason) reason = this.error ? 'EUNCAUGHT' : 'unknown';\n const extraProperties = {\n isEstimationSlice: this.isEstimation,\n error: this.error,\n reason,\n };\n\n return this.getMessagePayload(worker, 'return', extraProperties);\n }\n}\nexports.Slice = Slice;\n\n\n//# sourceURL=webpack://dcp/./src/dcp-client/worker/supervisor2/slice2.js?");
4813
4813
 
4814
4814
  /***/ }),
4815
4815
 
@@ -4841,7 +4841,7 @@ eval("/**\n * @file events/event-subscriber.js\n * @author Ryan Rossiter
4841
4841
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4842
4842
 
4843
4843
  "use strict";
4844
- eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js */ \"./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js\")[\"Buffer\"];\n/**\n * @file pseudo-express.js\n *\n * A lightweight url->callback routing scheme which uses a syntax very similar to\n * a subset of ExpressJS v4. This was designed explicitly for interoperation with\n * the Target connection class and SocketIO, but could - in theory - be used with \n * any NodeJS http server. \n *\n * @author Wes Garland, wes@kingsds.network\n * @date July 2021\n */\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('pseudo-express');\n\n/**\n * Create, in ExpressJS terminology, an \"app\" or \"router\" that is bound to the given server.\n *\n * @param {object} [httpServer] server to bind to\n * @constructor\n */\nexports.PseudoExpress = function PseudoExpress(httpServer)\n{\n this._routes = [];\n\n if (httpServer)\n this.bindServer(httpServer);\n}\n\n/**\n * API to bind an instance to a specific http server. More than one server per instance is supported, just call\n * this function multiple times.\n *\n * @param {object} server a node http or https Server\n * @param {string} [handledMarker] replaces request.url if the request has been routed successfully.\n */\nexports.PseudoExpress.prototype.bindServer = function PseudoExpress$bindServer(server, handledMarker)\n{\n if (!handledMarker)\n handledMarker = 'pseudo-express-handled';\n\n server.prependListener('request', (request, response) => this.routeRequest(request, response, handledMarker));\n server.on('request', (request, response) => {\n debugging('requests') && console.debug(`pe: finished processing ${request.url}`);\n if (request.url === handledMarker || request.handleOutsideExpress)\n return;\n \n setImmediate(() => {\n if (response.headersSent)\n console.warn('Warning: rogue listener handled request', request.url);\n else\n {\n debugging() && console.debug(`pe: request ${request.url} => 404`);\n sendHttpError(request, response, 404, `${request.url} not found.`);\n }\n });\n });\n}\n\n/**\n * Internal function to mark a request as routed.\n * @param {object} request Node HTTP request object\n * @param {string} handledMarker see PseudoExpress$bindServer\n */\nexports.PseudoExpress.prototype.markRequest = function PseudoExpress$markRequest(request, handledMarker)\n{\n debugging('requests') && console.debug('pe: marking request', request.url, 'handled via', handledMarker);\n request.url = handledMarker; \n}\n\n/**\n * Route a request to a request handler. We match request.url against previously-registered routes\n * (eg with .get) and run the first handler we find, then we mark the request (mutate request.url)\n * so that no other request handlers (I'm looking at you, socket.io) will want to touch it.\n *\n * If a request handler throws an exception, we handle the request by responding with an HTTP error.\n *\n * @param {object} request Node HTTP request object\n * @param {object} response Node HTTP response object\n * @param {string} handledMarker see PseudoExpress$bindServer\n */\nexports.PseudoExpress.prototype.routeRequest = function PseudoExpress$routeRequest(request, response, handledMarker)\n{\n var retval;\n \n try\n {\n debugging() && console.debug(`pe: routing ${request.url}`);\n for (let i=0; i < this._routes.length; i++) \n {\n let pathMatchRe = this._routes[i].match;\n \n if (!pathMatchRe.test(request.url))\n debugging('requests') && !debugging('all') && console.debug('pe: does not match', pathMatchRe)\n else\n {\n if (this._routes[i].handleOutsideExpress)\n {\n request.handleOutsideExpress = true\n break\n }\n else\n {\n pseudoExpressRoute(this._routes[i], request, response);\n this.markRequest(request, handledMarker);\n }\n break;\n }\n }\n }\n catch(error)\n {\n sendHttpError(request, response, 500, `Error processing ${request.url}\\n` + (error.stack || error.message));\n this.markRequest(handledMarker);\n }\n}\n\n/**\n * @param {RegExp} match regular expression that matches request url (pathname + search)\n */\n/**\n * Register an interceptor for the HTTP get method\n *\n * @param {string} match string that matches request pathname exactly\n * @param {function} callback function to run when the route matches\n * @param {Boolean} [handleOutsideExpress=false] set to true if this route should be handled externally from the pseudoExpressRoute\n */\nexports.PseudoExpress.prototype.get = function PseudoExpress$get(match, callback, handleOutsideExpress = false)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}($|\\\\?)`);\n\n debugging('requests') && console.debug(`pe: added GET method intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'get',\n match,\n callback,\n handleOutsideExpress,\n });\n}\n\n/**\n * Register an interceptor for the HTTP post method\n * @see PseudoExpress$get\n * @param {Boolean} [handleOutsideExpress=false] set to true if this route should be handled externally from the pseudoExpressRoute\n */\nexports.PseudoExpress.prototype.post = function PseudoExpress$post(match, callback, handleOutsideExpress = false)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}$`);\n\n debugging('requests') && console.debug(`pe: added POST method intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'post',\n match,\n callback,\n handleOutsideExpress,\n });\n}\n\n/**\n * Register an interceptor for all HTTP methods\n * @see PseudoExpress$get\n * @param {Boolean} [handleOutsideExpress=false] set to true if this route should be handled externally from the pseudoExpressRoute\n */\nexports.PseudoExpress.prototype.all = function PseudoExpress$all(match, callback, handleOutsideExpress = false)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}$`);\n\n debugging('requests') && console.debug(`pe: added all-method intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'all',\n match,\n callback,\n handleOutsideExpress,\n });\n}\n\n/**\n * Register a middleware callback\n * @see PseudoExpress$get\n * @param {function} next third argument callback function\n */\nexports.PseudoExpress.prototype.use = function PseudoExpress$use(match, callback, next)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}$`);\n\n function peUseCallbackWrapper(request, result)\n {\n callback(request, result, next);\n return; /* don't cancel bubble so we can chain to other routes */\n }\n\n debugging('requests') && console.debug(`pe: added use intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'all',\n match,\n callback: peUseCallbackWrapper,\n retval: true\n });\n}\n\nfunction sendHttpError(request, response, code, text)\n{\n const corsHeaders = {\n 'access-control-allow-origin': '*',\n 'access-control-allow-headers': 'content-type',\n };\n\n response.setHeader('content-type', 'text/plain');\n response.setHeader('content-type', 'text/plain; charset=utf8');\n response.setHeader('cache-control', 'no-cache');\n for (let header in corsHeaders)\n response.setHeader(header, corsHeaders[header]);\n \n response.writeHead(code);\n response.write(`${code} - ${text}`);\n response.end();\n}\n\n/**\n * Utility function for use by the PseudoExpress class. Actually implements the \"routing\" behaviour.\n *\n * @param {object} descriptor mapping for the behaviour of the \"express\" rule - e.g., \n * send all GET queries at location /status to the status handler\n * @param {object} httpRequest the http request to handle, from the node http or https modules\n * @param {object} httpResponse the response object associated with httpRequest\n */\nfunction pseudoExpressRoute(descriptor, httpRequest, httpResponse)\n{\n const rMethod = httpRequest.method.toLowerCase();\n const request = new PseudoExpressRequest (httpRequest);\n const response = new PseudoExpressResponse(httpResponse, request);\n \n if (descriptor.method !== 'all' && descriptor.method !== rMethod)\n return; /* try next handler */\n\n try\n {\n if (rMethod === 'get')\n {\n let i = httpRequest.url.indexOf('?');\n request.query = {};\n \n if (i !== -1) /* query string present - decode */\n {\n let queryString = httpRequest.url.slice(i + 1);\n for (let kvp of queryString.split('&'))\n {\n let [ key, value ] = kvp.split('=');\n request.query[key] = value && decodeURIComponent(value.replace(/\\+/g, ' '));\n }\n }\n descriptor.callback(request, response);\n }\n else if (rMethod === 'post')\n {\n const chunks = [];\n request.on('data', chunk => chunks.push(chunk));\n request.on('end', function pseudoExpress$postEnd() {\n request.body = Buffer.concat(chunks);\n descriptor.callback(request, response);\n });\n }\n else\n {\n descriptor.callback(request, response);\n }\n }\n catch(error)\n {\n dumpError(request, response, error);\n }\n \n if (descriptor.hasOwnProperty('retval'))\n return descriptor.retval;\n}\n\n/**\n * Class to represent incoming http request\n * param {object} httpRequest NodeJS request from httpServer\n * @constructor\n */\nfunction PseudoExpressRequest(httpRequest)\n{\n Object.assign(this, httpRequest);\n this.__httpRequest = httpRequest;\n this.originalUrl = this.__httpRequest.url;\n this.path = this.__httpRequest.url.replace(/\\?.*$/, '');\n this.hostname = this.__httpRequest.headers.host;\n}\n\n/**\n * Class to represent outgoing http response\n * @constructor\n */\nfunction PseudoExpressResponse(httpResponse, request)\n{\n this.httpResponse = httpResponse;\n this.headers = {};\n this.request = request;\n this.headersSent = false;\n}\n\n/**\n * @param {object} field Object containing multiple fields and values\n */\n/**\n * Sets the response’s HTTP header field to value. To set multiple fields at once, pass an \n * object as the parameter.\n * \n * @note Headers are collapsed to lower case\n *\n * @param {string} field Header name\n * @param {string} value Value of header\n */\nPseudoExpressResponse.prototype.set = function PseudoExpressResponse$$set(field, value)\n{\n if (typeof field === 'string') /* single header */\n this.headers[field.toLowerCase()] = value;\n else /* assume Object => multiple headers */\n {\n for (let entry of Object.values(field))\n this.headers[entry.field.toLowerCase()] = entry.value;\n }\n\n return this;\n}\n\n/** \n * Returns the HTTP response header specified by field. The match is case-insensitive.\n *\n * @param {string} field\n * @returns {string}\n */\nPseudoExpressResponse.prototype.get = function PseudoExpressResponse$$get(field)\n{\n return this.headers[field.toLowerCase()];\n}\n\n/**\n * Send HTTP status and headers immediately, without the respsonse body.\n *\n * @note Express does not appear to have an API that does this. It is needed to allow\n * streaming content.\n *\n * @param {object} defaultHeaders Headers to send as though they were in an object passed to\n * the set() method, unless they were already defined elsewhere\n * @param {object|string} body The body, per .send(), for the message; only used to detect\n * default header params; is not actually used in the response.\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.sendHeaders = function PseudoExpressResponse$$sendHeaders(defaultHeaders, body)\n{\n var defaultContentType;\n\n if (!body)\n body = this.body;\n\n if (!this.headers['content-type'])\n {\n switch(typeof body)\n {\n case 'object':\n {\n if (Buffer.isBuffer(body))\n defaultContentType = 'application/octet-stream';\n else\n defaultContentType = 'application/json';\n break;\n }\n case 'string': \n defaultContentType = 'text/plain; charset=utf-8';\n break;\n default:\n defaultContentType = 'application/octet-stream';\n break;\n }\n }\n \n this.httpResponse.writeHead(this.statusCode || 200, Object.assign({ 'content-type': defaultContentType }, defaultHeaders, this.headers))\n this.headersSent = true;\n\n return this;\n}\n\n/**\n * Sends the HTTP response.\n * The body parameter can be a Buffer object, a String, an object. Plain objects are serialized via\n * JSON.stringify. This method performs many useful tasks for simple non-streaming responses: For \n * example, it automatically assigns the Content-Length HTTP response header field (unless previously \n * defined).\n * \n * When the parameter is a Buffer object, the method sets the Content-Type response header field \n * to “application/octet-stream”, unless previously defined.\n *\n * @param {string} body The body to send as the response.\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.send = function PseudoExpressResponse$$send(body)\n{\n var defaultHeaders = {};\n var contentLength;\n var sendBody = body;\n var argv = Array.from(arguments);\n \n if (typeof sendBody === 'object' && sendBody !== null)\n {\n if (Buffer.isBuffer(sendBody))\n contentLength = Buffer.byteLength(sendBody);\n else\n sendBody = JSON.stringify(sendBody);\n }\n argv[0] = sendBody || '';\n\n if (typeof contentLength === 'undefined' && sendBody && typeof sendBody.length !== 'undefined')\n contentLength = sendBody.length;\n\n if (typeof contentLength !== 'undefined')\n defaultHeaders['content-length'] = contentLength;\n \n if (!this.headersSent)\n this.sendHeaders(defaultHeaders, body);\n\n try {\n this.httpResponse.end.apply(this.httpResponse, argv);\n }\n catch (error) {\n if (error.code === 'ERR_STREAM_WRITE_AFTER_END')\n console.warn('Pseudo-express tried to write after end. Suppressing here so we don\\'t crash');\n else {\n console.error('Unexpected error in pseudo-express', error);\n throw error;\n }\n\n }\n\n return this;\n}\n\n/**\n * Ends the response process. This method actually comes from Node core, specifically the \n * response.end() method of http.ServerResponse.\n *\n * Use to quickly end the response without any data. If you need to respond with data, instead \n * use methods such as res.send() and res.json().\n *\n * @param {string} [_data]\n * @param {string} [_encoding]\n *\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.end = function PseudoExpressResponse$$end(_data, _encoding)\n{\n this.httpResponse.end.apply(this.httpResponse, arguments);\n\n return this;\n}\n\n/** \n * Sends a JSON response. This method sends a response (with the correct content-type) that is the \n * parameter converted to a JSON string using JSON.stringify().\n *\n * @param {object} bodyObject The object to send as the body of the response\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.json = function PseudeoExpressResponse$$json(bodyObject)\n{\n this.send(JSON.stringify(bodyObject || typeof bodyObject));\n return this;\n}\n\n/**\n * Sets the HTTP status for the response. It is a chainable alias of Node’s response.statusCode.\n *\n * @param {number} statusCode\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.status = function PseudoExpressResponse$$status(statusCode)\n{\n this.statusCode = statusCode;\n return this;\n}\n \n/**\n * Sets the response Location HTTP header to the specified path parameter.\n * A path value of “back” has a special meaning, it refers to the URL specified in the Referer\n * header of the request. If the Referer header was not specified, it refers to “/”.\n *\n * @param {string|URL|DcpURL} location\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.location = function PseudoExpressResponse$$location(location)\n{\n if (location === '..')\n location = this.request.url.replace(/\\/[^/]*$/, '') || '/';\n if (location === 'back')\n location = this.request.headers['http-referer'] || '/';\n\n return this.set('location', location).send(`Redirecting to ${location}`);\n}\n\n/**\n * Sets the Content-Type HTTP header to the MIME type as determined by the specified type.\n *\n * If the type starts with 'text/' and the value does not contain a charset attribute, the attribute\n * charset=utf-8 will be added to the value.\n *\n * @param {string} contentType\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.type = function PseudoExpressResponse$$type(contentType)\n{\n if (contentType.startsWith('text/') && contentType.includes('charset='))\n contentType += '; charset=\"utf-8\"';\n return this.set('content-type', contentType);\n}\n\n/**\n * Redirects to the URL derived from the specified path, with specified status, a positive integer\n * that corresponds to an HTTP status code. If not specified, status defaults to 302.\n *\n * @param {number} [status]\n * @param {string|URL} [path]\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.redirect = function PseudoExpressResponse$$redirect(status, path)\n{\n if (typeof status !== 'number')\n {\n path = status;\n status = 302;\n }\n\n return this.status(status).location(path);\n}\n\n/* Try to tell user why pseudo-express \"route\" failed */\nfunction dumpError(request, response, error)\n{\n response.set('content-type', 'text/plain; charset=utf8');\n response.set('cache-control', 'no-cache');\n response.set('access-control-allow-origin', '*');\n response.set('access-control-allow-headers', 'content-type');\n response.statusCode = 500;\n response.write(`500 Internal Server Error accessing ${request.url} (${new Date()})\\n\\n` + (error.stack || error.message));\n}\n\n\n//# sourceURL=webpack://dcp/./src/node-libs/pseudo-express.js?");
4844
+ eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js */ \"./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js\")[\"Buffer\"];\n/**\n * @file pseudo-express.js\n *\n * A lightweight url->callback routing scheme which uses a syntax very similar to\n * a subset of ExpressJS v4. This was designed explicitly for interoperation with\n * the Target connection class and SocketIO, but could - in theory - be used with \n * any NodeJS http server. \n *\n * @author Wes Garland, wes@kingsds.network\n * @date July 2021\n */\n\n\nconst debugging = (__webpack_require__(/*! dcp/debugging */ \"./src/debugging.js\").scope)('pseudo-express');\n\n/**\n * Create, in ExpressJS terminology, an \"app\" or \"router\" that is bound to the given server.\n *\n * @param {object} [httpServer] server to bind to\n * @constructor\n */\nexports.PseudoExpress = function PseudoExpress(httpServer)\n{\n this._routes = [];\n\n if (httpServer)\n this.bindServer(httpServer);\n}\n\n/**\n * API to bind an instance to a specific http server. More than one server per instance is supported, just call\n * this function multiple times.\n *\n * @param {object} server a node http or https Server\n * @param {string} [handledMarker] replaces request.url if the request has been routed successfully.\n */\nexports.PseudoExpress.prototype.bindServer = function PseudoExpress$bindServer(server, handledMarker)\n{\n if (!handledMarker)\n handledMarker = 'pseudo-express-handled';\n\n server.prependListener('request', (request, response) => this.routeRequest(request, response, handledMarker));\n server.on('request', (request, response) => {\n debugging('requests') && console.debug(`pe: finished processing ${request.url}`);\n if (request.url === handledMarker || request.handleOutsideExpress)\n return;\n \n setImmediate(() => {\n if (response.headersSent)\n console.warn('Warning: rogue listener handled request', request.url);\n else\n {\n debugging() && console.debug(`pe: request ${request.url} => 404`);\n sendHttpError(request, response, 404, `${request.url} not found.`);\n }\n });\n });\n}\n\n/**\n * Internal function to mark a request as routed.\n * @param {object} request Node HTTP request object\n * @param {string} handledMarker see PseudoExpress$bindServer\n */\nexports.PseudoExpress.prototype.markRequest = function PseudoExpress$markRequest(request, handledMarker)\n{\n debugging('requests') && console.debug('pe: marking request', request.url, 'handled via', handledMarker);\n request.url = handledMarker; \n}\n\n/**\n * Route a request to a request handler. We match request.url against previously-registered routes\n * (eg with .get) and run the first handler we find, then we mark the request (mutate request.url)\n * so that no other request handlers (I'm looking at you, socket.io) will want to touch it.\n *\n * If a request handler throws an exception, we handle the request by responding with an HTTP error.\n *\n * @param {object} request Node HTTP request object\n * @param {object} response Node HTTP response object\n * @param {string} handledMarker see PseudoExpress$bindServer\n */\nexports.PseudoExpress.prototype.routeRequest = function PseudoExpress$routeRequest(request, response, handledMarker)\n{\n var retval;\n \n try\n {\n debugging() && console.debug(`pe: routing ${request.url}`);\n for (let i=0; i < this._routes.length; i++) \n {\n let pathMatchRe = this._routes[i].match;\n \n if (!pathMatchRe.test(request.url))\n debugging('requests') && !debugging('all') && console.debug('pe: does not match', pathMatchRe)\n else\n {\n if (this._routes[i].handleOutsideExpress)\n {\n request.handleOutsideExpress = true\n break\n }\n else\n {\n pseudoExpressRoute(this._routes[i], request, response);\n this.markRequest(request, handledMarker);\n }\n break;\n }\n }\n }\n catch(error)\n {\n sendHttpError(request, response, 500, `Error processing ${request.url}\\n` + (error.stack || error.message));\n this.markRequest(handledMarker);\n }\n}\n\n/**\n * @param {RegExp} match regular expression that matches request url (pathname + search)\n */\n/**\n * Register an interceptor for the HTTP get method\n *\n * @param {string} match string that matches request pathname exactly\n * @param {function} callback function to run when the route matches\n * @param {Boolean} [handleOutsideExpress=false] set to true if this route should be handled externally from the pseudoExpressRoute\n */\nexports.PseudoExpress.prototype.get = function PseudoExpress$get(match, callback, handleOutsideExpress = false)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}($|\\\\?)`);\n\n debugging('requests') && console.debug(`pe: added GET method intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'get',\n match,\n callback,\n handleOutsideExpress,\n });\n}\n\n/**\n * Register an interceptor for the HTTP post method\n * @see PseudoExpress$get\n * @param {Boolean} [handleOutsideExpress=false] set to true if this route should be handled externally from the pseudoExpressRoute\n */\nexports.PseudoExpress.prototype.post = function PseudoExpress$post(match, callback, handleOutsideExpress = false)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}$`);\n\n debugging('requests') && console.debug(`pe: added POST method intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'post',\n match,\n callback,\n handleOutsideExpress,\n });\n}\n\n/**\n * Register an interceptor for all HTTP methods\n * @see PseudoExpress$get\n * @param {Boolean} [handleOutsideExpress=false] set to true if this route should be handled externally from the pseudoExpressRoute\n */\nexports.PseudoExpress.prototype.all = function PseudoExpress$all(match, callback, handleOutsideExpress = false)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}$`);\n\n debugging('requests') && console.debug(`pe: added all-method intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'all',\n match,\n callback,\n handleOutsideExpress,\n });\n}\n\n/**\n * Register a middleware callback\n * @see PseudoExpress$get\n * @param {function} next third argument callback function\n */\nexports.PseudoExpress.prototype.use = function PseudoExpress$use(match, callback, next)\n{\n if (typeof match === 'string')\n match = new RegExp(`^${match}$`);\n\n function peUseCallbackWrapper(request, result)\n {\n callback(request, result, next);\n return; /* don't cancel bubble so we can chain to other routes */\n }\n\n debugging('requests') && console.debug(`pe: added use intercept ${match}: ${callback.name}`);\n this._routes.push({\n method: 'all',\n match,\n callback: peUseCallbackWrapper,\n retval: true\n });\n}\n\nfunction sendHttpError(request, response, code, text)\n{\n const corsHeaders = {\n 'access-control-allow-origin': '*',\n 'access-control-allow-headers': 'content-type',\n };\n\n response.setHeader('content-type', 'text/plain');\n response.setHeader('content-type', 'text/plain; charset=utf8');\n response.setHeader('cache-control', 'no-cache');\n for (let header in corsHeaders)\n response.setHeader(header, corsHeaders[header]);\n \n response.writeHead(code);\n response.write(`${code} - ${text}`);\n response.end();\n}\n\n/**\n * Utility function for use by the PseudoExpress class. Actually implements the \"routing\" behaviour.\n *\n * @param {object} descriptor mapping for the behaviour of the \"express\" rule - e.g., \n * send all GET queries at location /status to the status handler\n * @param {object} httpRequest the http request to handle, from the node http or https modules\n * @param {object} httpResponse the response object associated with httpRequest\n */\nfunction pseudoExpressRoute(descriptor, httpRequest, httpResponse)\n{\n const rMethod = httpRequest.method.toLowerCase();\n const requestPE = new PseudoExpressRequest (httpRequest);\n const responsePE = new PseudoExpressResponse(httpResponse, requestPE);\n \n if (descriptor.method !== 'all' && descriptor.method !== rMethod)\n return; /* try next handler */\n\n try\n {\n if (rMethod === 'get')\n {\n let i = httpRequest.url.indexOf('?');\n requestPE.query = {};\n \n if (i !== -1) /* query string present - decode */\n {\n let queryString = httpRequest.url.slice(i + 1);\n for (let kvp of queryString.split('&'))\n {\n let [ key, value ] = kvp.split('=');\n requestPE.query[key] = value && decodeURIComponent(value.replace(/\\+/g, ' '));\n }\n }\n descriptor.callback(requestPE, responsePE);\n }\n else if (rMethod === 'post')\n {\n const chunks = [];\n requestPE.on('data', chunk => chunks.push(chunk));\n requestPE.on('end', function pseudoExpress$postEnd() {\n requestPE.body = Buffer.concat(chunks);\n descriptor.callback(requestPE, responsePE);\n });\n }\n else\n {\n descriptor.callback(requestPE, responsePE);\n }\n }\n catch(error)\n {\n dumpError(requestPE, responsePE, error);\n }\n \n if (descriptor.hasOwnProperty('retval'))\n return descriptor.retval;\n}\n\n/**\n * Class to represent incoming http request\n * param {object} httpRequest NodeJS request from httpServer\n * @constructor\n */\nfunction PseudoExpressRequest(httpRequest)\n{\n Object.assign(this, httpRequest);\n // headers is non-enumerable, inherited in node 16+, so need to explicitly assign it.\n this.headers = httpRequest.headers;\n this.__httpRequest = httpRequest;\n this.originalUrl = this.__httpRequest.url;\n this.path = this.__httpRequest.url.replace(/\\?.*$/, '');\n this.hostname = this.__httpRequest.headers.host;\n}\n\n/**\n * Class to represent outgoing http response\n * @constructor\n */\nfunction PseudoExpressResponse(httpResponse, request)\n{\n this.httpResponse = httpResponse;\n this.headers = {};\n this.request = request;\n this.headersSent = false;\n}\n\n/**\n * @param {object} field Object containing multiple fields and values\n */\n/**\n * Sets the response’s HTTP header field to value. To set multiple fields at once, pass an \n * object as the parameter.\n * \n * @note Headers are collapsed to lower case\n *\n * @param {string} field Header name\n * @param {string} value Value of header\n */\nPseudoExpressResponse.prototype.set = function PseudoExpressResponse$$set(field, value)\n{\n if (typeof field === 'string') /* single header */\n this.headers[field.toLowerCase()] = value;\n else /* assume Object => multiple headers */\n {\n for (let entry of Object.values(field))\n this.headers[entry.field.toLowerCase()] = entry.value;\n }\n\n return this;\n}\n\n/** \n * Returns the HTTP response header specified by field. The match is case-insensitive.\n *\n * @param {string} field\n * @returns {string}\n */\nPseudoExpressResponse.prototype.get = function PseudoExpressResponse$$get(field)\n{\n return this.headers[field.toLowerCase()];\n}\n\n/**\n * Send HTTP status and headers immediately, without the respsonse body.\n *\n * @note Express does not appear to have an API that does this. It is needed to allow\n * streaming content.\n *\n * @param {object} defaultHeaders Headers to send as though they were in an object passed to\n * the set() method, unless they were already defined elsewhere\n * @param {object|string} body The body, per .send(), for the message; only used to detect\n * default header params; is not actually used in the response.\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.sendHeaders = function PseudoExpressResponse$$sendHeaders(defaultHeaders, body)\n{\n var defaultContentType;\n\n if (!body)\n body = this.body;\n\n if (!this.headers['content-type'])\n {\n switch(typeof body)\n {\n case 'object':\n {\n if (Buffer.isBuffer(body))\n defaultContentType = 'application/octet-stream';\n else\n defaultContentType = 'application/json';\n break;\n }\n case 'string': \n defaultContentType = 'text/plain; charset=utf-8';\n break;\n default:\n defaultContentType = 'application/octet-stream';\n break;\n }\n }\n \n this.httpResponse.writeHead(this.statusCode || 200, Object.assign({ 'content-type': defaultContentType }, defaultHeaders, this.headers))\n this.headersSent = true;\n\n return this;\n}\n\n/**\n * Sends the HTTP response.\n * The body parameter can be a Buffer object, a String, an object. Plain objects are serialized via\n * JSON.stringify. This method performs many useful tasks for simple non-streaming responses: For \n * example, it automatically assigns the Content-Length HTTP response header field (unless previously \n * defined).\n * \n * When the parameter is a Buffer object, the method sets the Content-Type response header field \n * to “application/octet-stream”, unless previously defined.\n *\n * @param {string} body The body to send as the response.\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.send = function PseudoExpressResponse$$send(body)\n{\n var defaultHeaders = {};\n var contentLength;\n var sendBody = body;\n var argv = Array.from(arguments);\n \n if (typeof sendBody === 'object' && sendBody !== null)\n {\n if (Buffer.isBuffer(sendBody))\n contentLength = Buffer.byteLength(sendBody);\n else\n sendBody = JSON.stringify(sendBody);\n }\n argv[0] = sendBody || '';\n\n if (typeof contentLength === 'undefined' && sendBody && typeof sendBody.length !== 'undefined')\n contentLength = sendBody.length;\n\n if (typeof contentLength !== 'undefined')\n defaultHeaders['content-length'] = contentLength;\n \n if (!this.headersSent)\n this.sendHeaders(defaultHeaders, body);\n\n try {\n this.httpResponse.end.apply(this.httpResponse, argv);\n }\n catch (error) {\n if (error.code === 'ERR_STREAM_WRITE_AFTER_END')\n console.warn('Pseudo-express tried to write after end. Suppressing here so we don\\'t crash');\n else {\n console.error('Unexpected error in pseudo-express', error);\n throw error;\n }\n\n }\n\n return this;\n}\n\n/**\n * Ends the response process. This method actually comes from Node core, specifically the \n * response.end() method of http.ServerResponse.\n *\n * Use to quickly end the response without any data. If you need to respond with data, instead \n * use methods such as res.send() and res.json().\n *\n * @param {string} [_data]\n * @param {string} [_encoding]\n *\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.end = function PseudoExpressResponse$$end(_data, _encoding)\n{\n this.httpResponse.end.apply(this.httpResponse, arguments);\n\n return this;\n}\n\n/** \n * Sends a JSON response. This method sends a response (with the correct content-type) that is the \n * parameter converted to a JSON string using JSON.stringify().\n *\n * @param {object} bodyObject The object to send as the body of the response\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.json = function PseudeoExpressResponse$$json(bodyObject)\n{\n this.send(JSON.stringify(bodyObject || typeof bodyObject));\n return this;\n}\n\n/**\n * Sets the HTTP status for the response. It is a chainable alias of Node’s response.statusCode.\n *\n * @param {number} statusCode\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.status = function PseudoExpressResponse$$status(statusCode)\n{\n this.statusCode = statusCode;\n return this;\n}\n \n/**\n * Sets the response Location HTTP header to the specified path parameter.\n * A path value of “back” has a special meaning, it refers to the URL specified in the Referer\n * header of the request. If the Referer header was not specified, it refers to “/”.\n *\n * @param {string|URL|DcpURL} location\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.location = function PseudoExpressResponse$$location(location)\n{\n if (location === '..')\n location = this.request.url.replace(/\\/[^/]*$/, '') || '/';\n if (location === 'back')\n location = this.request.headers['http-referer'] || '/';\n\n return this.set('location', location).send(`Redirecting to ${location}`);\n}\n\n/**\n * Sets the Content-Type HTTP header to the MIME type as determined by the specified type.\n *\n * If the type starts with 'text/' and the value does not contain a charset attribute, the attribute\n * charset=utf-8 will be added to the value.\n *\n * @param {string} contentType\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.type = function PseudoExpressResponse$$type(contentType)\n{\n if (contentType.startsWith('text/') && contentType.includes('charset='))\n contentType += '; charset=\"utf-8\"';\n return this.set('content-type', contentType);\n}\n\n/**\n * Redirects to the URL derived from the specified path, with specified status, a positive integer\n * that corresponds to an HTTP status code. If not specified, status defaults to 302.\n *\n * @param {number} [status]\n * @param {string|URL} [path]\n * @returns this, to allow chaining\n */\nPseudoExpressResponse.prototype.redirect = function PseudoExpressResponse$$redirect(status, path)\n{\n if (typeof status !== 'number')\n {\n path = status;\n status = 302;\n }\n\n return this.status(status).location(path);\n}\n\n/* Try to tell user why pseudo-express \"route\" failed */\nfunction dumpError(requestPE, responsePE, error)\n{\n responsePE.set('content-type', 'text/plain; charset=utf8');\n responsePE.set('cache-control', 'no-cache');\n responsePE.set('access-control-allow-origin', '*');\n responsePE.set('access-control-allow-headers', 'content-type');\n responsePE.statusCode = 500;\n responsePE.send(`500 Internal Server Error accessing ${requestPE.url} (${new Date()})\\n\\n` + error.stack);\n}\n\n\n//# sourceURL=webpack://dcp/./src/node-libs/pseudo-express.js?");
4845
4845
 
4846
4846
  /***/ }),
4847
4847
 
@@ -5197,7 +5197,7 @@ eval("/**\n * @file utils/http.js Helper module for things rel
5197
5197
  /***/ ((module, exports, __webpack_require__) => {
5198
5198
 
5199
5199
  "use strict";
5200
- eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js */ \"./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js\")[\"Buffer\"];\n/**\n * @file src/utils/index.js\n * @author Ryan Rossiter\n * Paul, paul@distributive.network\n * @date Feb 2020\n * April 2023\n *\n * Place to put little JS utilities. If they are more than a few lines, please `require` and `export` \n * them instead of making this monolithic.\n * @module dcp/utils\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n/* global dcpConfig */ // eslint-disable-line no-redeclare\n// @ts-check\n\n\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\n\nObject.assign(exports, __webpack_require__(/*! ./at-exit */ \"./src/utils/at-exit.js\"));\nmodule.exports.paramUtils = __webpack_require__(/*! ./assert-params */ \"./src/utils/assert-params.js\");\nmodule.exports.httpUtils = __webpack_require__(/*! ./http */ \"./src/utils/http.js\");\nmodule.exports.serialize = __webpack_require__(/*! ./serialize */ \"./src/utils/serialize.js\");\nObject.assign(exports, __webpack_require__(/*! ./web-format-date */ \"./src/utils/web-format-date.js\"));\nObject.assign(exports, __webpack_require__(/*! ./confirm-prompt */ \"./src/utils/confirm-prompt.js\"));\nObject.assign(exports, __webpack_require__(/*! ./content-encoding */ \"./src/utils/content-encoding.js\"));\n\n/** @typedef {import('dcp/dcp-client/worker/slice').Slice} Slice */\n/** @typedef {import('dcp/dcp-client/worker/sandbox').Sandbox} Sandbox */\n\n /**\n * Writes object properties into another object matching shape.\n * For example: if obj = { a: { b: 1, c: 2}}\n * and source = { a: {c: 0, d: 1}}\n * then obj will be updated to { a: { b: 1, c: 0, d: 1 } }\n * compare this to Object.assign which gives { a: { c: 0, d: 1 } }\n */\nconst setObjProps = module.exports.setObjProps = (obj, source) => {\n for (let p in source) {\n if (typeof source[p] === 'object') setObjProps(obj[p], source[p]);\n else obj[p] = source[p];\n }\n}\n\n\n/**\n * Generates a new random opaqueId i.e. a 22-character base64 string.\n * Used for job and slice ids.\n */\nmodule.exports.generateOpaqueId = function utils$$generateOpaqueId()\n{\n const schedulerConstants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\n if (!utils$$generateOpaqueId.nanoid)\n {\n const nanoidModule = __webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\");\n utils$$generateOpaqueId.nanoid = nanoidModule.customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+', schedulerConstants.workerIdLength);\n }\n\n return utils$$generateOpaqueId.nanoid();\n}\n\n/**\n * Accepts an object and a list of properties to apply to the object to retreive a final value.\n * E.g.\n * object = { a: { b: { c: 4 } } }\n * listOfProperties = [\"a\", \"b\", \"c\"]\n * return = 4\n */\nmodule.exports.getNestedFromListOfProperties = function (object, listOfProperties) {\n return listOfProperties.reduce((o, k) => {\n if (typeof o !== 'object') return undefined;\n return o[k];\n }, object);\n}\n\n/**\n * Accepts an object and a dot-separated property name to retrieve from the object.\n * E.g.\n * object = { a: { b: { c: 4 } } }\n * property = 'a.b.c'\n * return = 4\n *\n * @param {object} object \n * @param {string} property \n */\nmodule.exports.getNestedProperty = function (object, property) {\n if (typeof property !== 'string') return undefined;\n return module.exports.getNestedFromListOfProperties(object, property.split(\".\"));\n}\n\n/**\n * Accepts an object and a list of properties and a value and mutates the object\n * to have the specified value at the location denoted by the list of properties.\n * Similar to getNestedFromListOfProperties, but sets instead of gets.\n */\nmodule.exports.setNestedFromListOfProperties = function (object, listOfProperties, value) {\n if(!listOfProperties.length || listOfProperties.length < 1) {\n throw new Error(\"listOfProperties must be an array of length >= 1\");\n }\n const indexOfLastProp = listOfProperties.length - 1;\n const pathToParent = listOfProperties.slice(0, indexOfLastProp);\n const parent = module.exports.getNestedFromListOfProperties(object, pathToParent);\n if(!parent) {\n throw new Error(\"Could not find value at:\", pathToParent, \"in object:\", object);\n }\n const lastProperty = listOfProperties[indexOfLastProp];\n parent[lastProperty] = value;\n}\n\n/**\n * Block the event loop for a specified time\n * @milliseconds the number of milliseconds to wait (integer)\n */\nexports.msleep = function utils$$msleep(milliseconds) {\n try\n {\n let sab = new SharedArrayBuffer(4);\n let int32 = new Int32Array(sab);\n Atomics.wait(int32, 0, 0, milliseconds);\n }\n catch(error)\n {\n console.error('Cannot msleep;', error);\n }\n}\n\n/**\n * Block the event loop for a specified time\n * @seconds the number of seconds to wait (float)\n */\nexports.sleep = function utils$$sleep(seconds) {\n return exports.msleep(seconds * 1000);\n}\n\n/** \n * Resolve a promise after a specified time.\n * \n * @param {number} ms the number of milliseconds after which to resolve the promise.\n * @returns Promise with an extra property, intr(). Calling this function causes the promise\n * to resolve immediately. If the promise was interrupted, it will resolve with false;\n * otherwise, true.\n */\nexports.a$sleepMs = function a$sleepMs(ms)\n{\n var interrupt;\n \n const ret = new Promise((resolve, reject) => {\n var resolved = false;\n const timerHnd = setTimeout(function __a$sleepMs() { resolved=true; resolve(false) }, ms);\n function a$sleepMs_intr()\n {\n clearTimeout(timerHnd);\n if (!resolved)\n resolve(true);\n }\n \n interrupt = a$sleepMs_intr;\n });\n\n ret.intr = () => interrupt();\n return ret;\n}\n\n/** \n * @see: a$sleepMs\n * @param {number} ms the number of milliseconds after which to resolve the promise.\n */\nexports.a$sleep = function a$sleep(seconds) {\n return exports.a$sleepMs(seconds * 1000);\n}\n\n/** \n * Returns the number of millisecond in a time expression.\n * @param s {number} The number of seconds\n * @returns {number}\n */\n/**\n * @param s {string|number} - A complex time expression using m, w, d, s, h. '10d 6h 1s' means 10 days, 6 hours, and 1 second.\n */\nexports.ms = function utils$$ms(s)\n{\n let ms = 0;\n \n if (typeof s === 'number')\n return s * 1000;\n\n assert(typeof s === 'string');\n \n for (let expr of s.match(/[0-9.]+[smhdw]/g))\n {\n let unit = expr.slice(-1);\n let value = +expr.slice(0, -1);\n\n switch(unit)\n {\n case 's': {\n ms += value * 1000;\n break;\n }\n case 'm': {\n ms += value * 1000 * 60;\n break;\n }\n case 'h': {\n ms += value * 1000 * 60 * 60;\n break;\n }\n case 'd': {\n ms = value * 1000 * 60 * 60 * 24;\n break;\n }\n case 'w': {\n ms = value * 1000 * 60 * 60 * 24 * 7;\n break;\n }\n default: {\n throw new Error(`invalid time unit ${unit}`);\n }\n }\n }\n\n return ms;\n}\n\n/**\n * @param n {number} this number is returned, divided by 100 .\n */\n/**\n * @param n {string} this string is converted to a number and returned; it is divided by 100 if it ends in %.\n */\n/**\n * Returns a percentage as a number.\n * @param n {number|string} this number is returned\n * @returns {number}\n */\nexports.pct = function utils$$pct(n)\n{\n if (typeof n === 'number')\n return n / 100;\n\n if (n.match(/%$/))\n return +n / 100;\n\n return +n;\n}\n\n/**\n * Coerce human-written or registry-provided values into Boolean in a sensisble way.\n */\nexports.booley = function utils$$booley(check)\n{\n switch (typeof check)\n {\n case 'undefined':\n return false;\n case 'string':\n return check && (check !== 'false');\n case 'boolean':\n return check;\n case 'number':\n case 'object':\n return Boolean(check);\n default:\n throw new Error(`can't coerce ${typeof check} to booley`);\n }\n}\n\ntry {\n exports.useChalk = requireNative ? requireNative('tty').isatty(0) || process.env.FORCE_COLOR : false;\n}\ncatch (error) {\n if (error.message.includes('no native require'))\n exports.useChalk = false;\n else\n throw error;\n}\n\n/** \n * Factory function which constructs an error message relating to version mismatches\n * @param {string} oldThingLabel The name of the thing that is too old\n * @param {string} upgradeThingLabel [optional] The name of the thing that needs to be upgraded to fix that; if\n * unspecified, we use oldThingLabel.\n * @param {string} newThingLabel What the thing is that is that is newer than oldThingLabel\n * @param {string} minimumVersion The minimum version of the old thing that the new thing supports. \n * Obstensibibly semver, but unparsed.\n * @param {string} code [optional] A code property to add to the return value\n *\n * @returns instance of Error\n */\nexports.versionError = function dcpClient$$versionError(oldThingLabel, upgradeThingLabel, newThingLabel, minimumVersion, code)\n{\n function repeat(what, len) {\n let out = '';\n while (len--)\n out += what;\n return out;\n }\n\n function bold(string) {\n if ((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").platform) === 'nodejs') {\n const chalk = new (requireNative('chalk').Instance)({level: exports.useChalk ? 1 : 0});\n\n return chalk.yellow(string);\n }\n\n return string;\n }\n \n var mainMessage = `${oldThingLabel} is too old; ${newThingLabel} needs version ${minimumVersion}`;\n var upgradeMessage = bold(`Please upgrade ${upgradeThingLabel || oldThingLabel}${repeat(\" \", mainMessage.length - 15 - upgradeThingLabel.length)}`);\n var error = new Error(`\n****${repeat('*', mainMessage.length)}****\n*** ${repeat(' ', mainMessage.length)} ***\n*** ${mainMessage} ***\n*** ${upgradeMessage} ***\n*** ${repeat(' ', mainMessage.length)} ***\n****${repeat('*', mainMessage.length)}****\n`);\n\n if (code)\n error.code = code;\n return error;\n}\n\n/**\n * Detect whether value is from the output of job-values:serializeJobValue.\n * @param {*} value\n * @returns {boolean}\n */\nexports.isFromSerializeJobValue = function utils$$isFromSerializeJobValue(value)\n{\n return value.hasOwnProperty('string') && value.hasOwnProperty('method') && value.hasOwnProperty('MIMEType') && Object.keys(value).length === 3;\n}\n\n/**\n * Perf Timers:\n * shortTime\n * class PerfTimers\n *********************************************************************************************/\n\n/**\n * Previous short form perf time stamp.\n * @private\n * @type {Date}\n */\nlet previousTime = new Date();\n\n/**\n * Short form perf time format with timespan diff from last call.\n * @param {boolean} [displayDiffOnly=true]\n * @returns {string}\n */\nexports.shortTime = function utils$$shortTime(displayDiffOnly=true) {\n const currentTime = new Date();\n const diff = currentTime.getTime() - previousTime.getTime();\n previousTime = currentTime;\n if (displayDiffOnly) return `diff=${diff}`;\n return `diff=${diff}: ${currentTime.getMinutes()}.${currentTime.getSeconds()}.${currentTime.getMilliseconds()}`;\n}\n\n/**\n * By default returns\n * functionName:lineNumber: message\n * When (!message || !message.length), returns\n * functionName:lineNumber\n *\n * When shortForm is true, returns\n * lineNumber: message\n *\n * @param {string} [message]\n * @param {number} [depth=2]\n * @param {boolean} [shortForm=false]\n * @returns {string}\n */\nexports.debugLine = function debugLine(message, depth = 2, shortForm = false) {\n const e = new Error();\n const frame = e.stack.split(\"\\n\")[depth];\n const lineNumber = frame.split(\":\").reverse()[1];\n const funcName = frame.split(\" \")[5];\n const header = shortForm ? 'line#' : funcName;\n const headerAndLine = `${header}:${lineNumber}`;\n if (!message || !message.length) return headerAndLine;\n return headerAndLine + \" \" + message;\n}\n\n/**\n * class Tracer: Method mark, displays time span since the last mark.\n *\n * When perf testing the first step is to place a bunch of timer entries\n * in the code where the time difference from the last entry is displayed\n * This class Tracer is used as follows:\n * const tracer = new Tracer(tag);\n * ...............................\n * tracer.mark(); // displays dt1: lineNumber: tag -- where dt1 is time since constructor was called\n * ...............................\n * tracer.mark(); // displays dt2: lineNumber: tag -- where dt2 is time since previous mark\n * ...............................\n * tracer.mark(); // displays dt3: lineNumber: tag -- where dt3 is time since previous mark\n *\n * There are many variations possible as can be seen by the comment on the mark method.\n */\nclass Tracer {\n /**\n * \n * @param {boolean} [shortForm=true]\n * @param {string} [tag]\n */\n constructor(shortForm = true, tag) {\n this.shortForm = shortForm;\n this.tag = tag;\n this.previousTime = new Date();\n }\n /**\n * Let diff be the time span since the last call to mark or the Tracer constructor.\n * Let lineNumber be the line # of the file containing the call to mark.\n * Let line# be the string \"line#\"\n * Let funcName be the name of the function where mark was called.\n * When tag.length > 0,\n * displays: diff: line#:lineNumber: tag:message\n * or: diff: line#:lineNumber: tag when !message\n * or: diff: funcName:lineNumber tag:message when !shortForm\n * or: diff: funcName:lineNumber tag when !shortForm and !message\n * else\n * displays: diff: line#:lineNumber: message\n * or: diff: line#lineNumber: when !message\n * or: diff: funcName:lineNumber message when !shortForm\n * or: diff: funcName:lineNumber when !shortForm and !message\n * @param {string} [message]\n */\n mark(message) {\n const currentTime = new Date();\n const diff = currentTime.getTime() - this.previousTime.getTime();\n this.previousTime = currentTime;\n let tag = this.tag;\n if (tag && tag.length && message) tag = `${tag}:${message}`;\n else if (message) tag = message;\n console.log(`${diff}: ${exports.debugLine(tag, 3 /* depth*/, this.shortForm)}`);\n }\n}\nexports.Tracer = Tracer;\n\n/**\n* class PerfTimer: Construct a set of possibly overlapping timers.\n*\n* Example:\n* const perfTimers = new PerfTimers(['timer0', 'timer1', 'timer2']);\n* perfTimers.start('timer0');\n* perfTimers.start('timer1');\n* .........................\n* perfTimers.stop('timer1');\n* .........................\n* perfTimers.stop('timer0');\n* .........................\n* perfTimers.start('timer2');\n* ..........................\n* perfTimers.stop('timer2');\n* perfTimers.sum(name => name !== 'timer1');\n*\n* Should display something like:\n* timer1: 472ms\n* timer0: 1650ms\n* timer2: 2333ms\n* The sum of timers [timer0, timer2] is 3983ms\n*/\nclass PerfTimers {\n /**\n * @constructor\n * @param {string[]} perfTimerNames\n * @param {boolean} [disable=false]]\n * @param {boolean} [summaryView=false]]\n */\n constructor (perfTimerNames, disable = false, summaryView = false) {\n this.disable = disable;\n if (this.disable) return;\n assert(Array.isArray(perfTimerNames));\n /** @type {string[]} */\n this.perfTimerNames = perfTimerNames;\n /** @type {boolean} */\n this.summaryView = summaryView;\n /** @type {number[]} */\n this.perfTimers = Array.from({ length: perfTimerNames.length }, (v, i) => 0);\n /** @type {object} */\n this.perfTimerMap = {};\n for (let k = 0; k < this.perfTimerNames.length; k++)\n this.perfTimerMap[this.perfTimerNames[k]] = k;\n }\n /**\n * Start a timer on perfTimerName\n * @param {string} perfTimerName\n */\n start(perfTimerName) {\n if (this.disable) return;\n const slot = this.perfTimerMap[perfTimerName];\n if (slot === undefined) throw new Error(`PerfDebugging: perfTimer '${perfTimerName}' not found.`);\n this.perfTimers[slot] = Date.now();\n }\n /**\n * Stop a timer on perfTimerName and display the result.\n * @param {string} perfTimerName\n */\n stop(perfTimerName) {\n if (this.disable) return;\n const slot = this.perfTimerMap[perfTimerName];\n if (slot === undefined) throw new Error(`PerfDebugging: perfTimer '${perfTimerName}' not found.`);\n this.perfTimers[slot] = Date.now() - this.perfTimers[slot];\n if (!this.summaryView) console.log(`${perfTimerName}: ${this.perfTimers[slot]}ms`);\n }\n /** \n * @callback PredicateCallback\n * @param {string} value\n * @param {number} index\n * @param {string[]} array\n */\n /**\n * Use predicate to choose a subset of this.perfTimerNames,\n * sum up the corresponding timers and display the result.\n * @param {PredicateCallback} predicate\n * @param {boolean} [forceDisplay=false]\n */\n sum(predicate, forceDisplay = false) {\n if (this.disable || this.summaryView && !forceDisplay) return;\n const names = this.perfTimerNames.filter(predicate);\n const timers = this.perfTimers.map(k => this.perfTimerMap(names[k]));\n const sum = timers.reduce((previous, current) => previous + current, 0);\n console.log(`The sum of timers ${JSON.stringify(names)} is ${sum}ms`);\n }\n /**\n * Display summary.\n * @param {PredicateCallback} predicate\n * @param {boolean} [forceDisplay=false]\n */\n summary(predicate, forceDisplay = false) {\n if (this.disable) return;\n if (this.summaryView || forceDisplay) {\n for (let k = 0; k < this.perfTimers.length; k++)\n console.log(`${this.perfTimerNames[k]}: ${this.perfTimers[k]}ms`);\n }\n if (predicate) this.sum(predicate);\n }\n}\nexports.PerfTimers = PerfTimers;\n\n/**\n * Sandbox and Slice debugging and logging tools.\n * dumpSlices -- dump array of slices\n * dumpSandboxes -- dump array of sandboxes\n * dumpSlicesIfNotUnique -- dump array of slices when there are dups\n * dumpSandboxesIfNotUnique -- dump array of sandboxes when there are dups\n * isUniqueSlices -- detect dups in an array of slices\n * isUniqueSandboxes -- detect dups in an array of sandboxes\n *********************************************************************************************/\n\n/**\n * Log sliceArray.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n */\nexports.dumpSlices = function utils$$dumpSlices(sliceArray, header) {\n if (header) console.log(`\\n${header}`);\n console.log(exports.compressSlices(sliceArray));\n}\n\n/**\n * Log sandboxArray.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n */\nexports.dumpSandboxes = function utils$$dumpSandboxes(sandboxArray, header) {\n if (header) console.log(`\\n${header}`);\n console.log(exports.compressSandboxes(sandboxArray));\n}\n\n/**\n * If the elements of sliceArray are not unique, log the duplicates and log the full array.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n */\nexports.dumpSlicesIfNotUnique = function utils$$dumpSlicesIfNotUnique(sliceArray, header) {\n if (!exports.isUniqueSlices(sliceArray, header))\n console.log(exports.compressSlices(sliceArray));\n}\n\n/**\n * If the elements of sandboxArray are not unique, log the duplicates and log the full array.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n */\nexports.dumpSandboxesIfNotUnique = function utils$$dumpSandboxesIfNotUnique(sandboxArray, header) {\n if (!exports.isUniqueSandboxes(sandboxArray, header))\n console.log(exports.compressSandboxes(sandboxArray));\n}\n\n/**\n * Checks whether the elements of sliceArray are unique and if not, log the duplicates.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n * @param {function} [log]\n * @returns {boolean}\n */\nexports.isUniqueSlices = function utils$$isUniqueSlices(sliceArray, header, log) {\n const slices = [];\n let once = true;\n sliceArray.forEach(x => {\n if (slices.indexOf(x) >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x) : console.log(`\\tFound duplicate slice ${x.identifier}.`);\n } else slices.push(x);\n });\n return sliceArray.length === slices.length;\n}\n\n/**\n * Checks whether the elements of sandboxArray are unique and if not, log the duplicates.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n * @param {function} [log]\n * @returns {boolean}\n */\nexports.isUniqueSandboxes = function utils$$isUniqueSandboxes(sandboxArray, header, log) {\n const sandboxes = [];\n let once = true;\n sandboxArray.forEach(x => {\n if (sandboxes.indexOf(x) >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x) : console.log(`\\tFound duplicate sandbox ${x.identifier}.`);\n } else sandboxes.push(x);\n });\n return sandboxArray.length === sandboxes.length;\n}\n\n/**\n * JSON Serialization\n * stringify -- ignore cycles\n * dumpJSON -- safely wrapped stringify with header\n * dumpObject -- Apply dumpJSON to every [key, value] of Object.entries(theObject)\n *********************************************************************************************/\n\n/**\n * Quck and dirty JSON serialization that ignores cycles.\n *\n * @param {*} theAnything - entity to be serialized.\n * @param {number} [truncationLength=512] - number of string elements to return.\n * @param {number} [space=0] - # of spaces to indent (0 <= space <= 10).\n * @returns {string}\n */\nexports.stringify = function _stringify(theAnything, truncationLength = 512, space = 2) {\n let cache = [];\n const str = JSON.stringify(theAnything, (key, value) => {\n if (typeof value === 'object' && value !== null) {\n if (cache.includes(value)) return;\n cache.push(value);\n }\n return value;\n }, space);\n cache = null;\n if (str && truncationLength > 0)\n return str.slice(0, truncationLength);\n return str;\n}\n\n/**\n * Calls truncated JSON.stringify on theAnything.\n * @param {*} theAnything\n * @param {string} [header='dumpJSON']\n * @param {number} [truncationLength=512]\n */\nexports.dumpJSON = function utils$$dumpJSON(theAnything, header = 'dumpJSON: ', truncationLength = 512) {\n if (theAnything) {\n const strV = exports.stringify(theAnything, truncationLength);\n if (strV) console.log(`${header}: ${String(strV)}`);\n else console.log(`${header}:`, theAnything);\n } else console.log(`${header}:`, theAnything);\n}\n\n/**\n * Iterates over all property [key, value]-pairs of theObject and call truncated JSON.stringify on property values.\n * @param {object} theObject\n * @param {string} [header='dumpObject']\n * @param {number} [truncationLength=512]\n */\nexports.dumpObject = function utils$$dumpObject(theObject, header = 'dumpObject: ', truncationLength = 512, dumpKeys = true) {\n if (theObject) {\n if (dumpKeys) console.log(`${header}: dump the keys`, Object.keys(theObject));\n console.log(`${header}: dump the key-value entries...`);\n console.group();\n for (const [key, value] of Object.entries(theObject))\n exports.dumpJSON(value, `${header}.${key}`, truncationLength);\n console.groupEnd();\n }\n}\n\n/**\n * Compressed Representations and Maps\n * toJobMap\n * compressSandboxes\n * compressSlices\n * compressJobMap\n * compressJobArray\n * compressJobValue\n * compressJobEntry\n * compressEnhancedJobEntry\n * truncateAddress\n * compressRange\n * compressEnhancedRange\n *********************************************************************************************/\n\n/**\n * @param {object[]} jobArray\n * @param {function} functor\n * @returns {object}\n */\nexports.toJobMap = function utils$$toJobMap(jobArray, functor) {\n const jobMap = {};\n for (const x of jobArray) {\n if (!jobMap[x.jobAddress]) jobMap[x.jobAddress] = [functor(x)];\n else jobMap[x.jobAddress].push(functor(x));\n }\n return jobMap;\n}\n\n/**\n * @param {Sandbox[]} sandboxArray\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressSandboxes = function utils$$compressSandboxes(sandboxArray, digits = -1) {\n const jobSandboxMap = exports.toJobMap(sandboxArray, sbx => sbx.id);\n return exports.compressJobMap(jobSandboxMap, digits);\n}\n\n/**\n * @param {Slice[]} sliceArray\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressSlices = function utils$$compressSlices(sliceArray, digits = -1) {\n const jobSliceMap = exports.toJobMap(sliceArray, slice => slice.sliceNumber);\n return exports.compressJobMap(jobSliceMap, digits);\n}\n\n/**\n * @param {object} jobMap\n * @param {number} [digits=-1]\n * @param {function} [functor]\n * @returns {string}\n */\nexports.compressJobMap = function utils$$compressJobMap(jobMap, digits = -1, functor) {\n if (functor)\n {\n jobMap = { ...jobMap };\n for (const job in jobMap)\n jobMap[job] = jobMap[job].map((x) => functor(x));\n }\n return exports.compressJobArray(Object.entries(jobMap), digits);\n}\n\n/**\n * @param {object[]} jobArray\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobArray = function utils$$compressJobArray(jobArray, digits = -1) {\n let output = '';\n for (let k = 0; k < jobArray.length; k++) {\n output += exports.compressJobValue(jobArray[k], digits);\n }\n return output;\n}\n\n/**\n * @param {object|object[]} jobValue\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobValue = function utils$$compressJobValue(jobValue, digits = -1) {\n if (jobValue.job && jobValue.slices)\n return exports.compressJobEntry(jobValue.job, jobValue.slices, digits);\n if (jobValue.length === 2)\n return exports.compressJobEntry(jobValue[0], jobValue[1], digits);\n return 'nada';\n}\n\n/**\n * @param {*} jobAddress\n * @param {Array<number>} sliceNumbers\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobEntry = function utils$$compressJobEntry(jobAddress, sliceNumbers, digits = -1) {\n return `${exports.truncateAddress(jobAddress, digits)}:[${exports.compressRange(sliceNumbers)}]:`;\n}\n\nexports.compressEnhancedJobEntry = function utils$$compressEnhancedJobEntry(job, slices, digits = -1) {\n return `${job.id}.${exports.truncateAddress(job.address, digits)}:[${exports.compressEnhancedRange(slices)}]:`;\n}\n\n/**\n * @param {*} address\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.truncateAddress = function utils$$truncateAddress(address, digits = -1) {\n let value = address.toString();\n if (digits < 0) return value.startsWith('0x') ? value.substring(2) : value;\n return value.startsWith('0x') ? value.substring(2, 2 + digits) : value.substring(0, digits);\n}\n\n/**\n * Input [2, 3, 4, 7, 5, 8, 9, 13, 14, 15] returns '2-5,7-9,13-15'\n * Input [2, 3, 4, 7, 5, 8, 9, 13] returns '2-5,7-9,13'\n * Input [2, 3, 4, 7, 5, 4, 4, 8, 9] returns '2-4,4,4-5,7-9'\n * @param {Array<number>} numberArray\n * @returns {string}\n */\nexports.compressRange = function utils$$compressRange(numberArray) {\n assert(numberArray && Array.isArray(numberArray));\n numberArray = [...numberArray];\n numberArray.sort((x, y) => x - y); // increasing...\n let start = numberArray[0];\n let output = `${start}`;\n for (let k = 1; k < numberArray.length; k++) {\n assert(typeof numberArray[k] === 'number' || numberArray[k] && numberArray[k].constructor.name === 'Number');\n if (numberArray[k] - numberArray[k - 1] !== 1) {\n output += (numberArray[k - 1] > start) ? `-${numberArray[k - 1]},` : ',';\n start = numberArray[k];\n output += `${start}`;\n } else if (k === numberArray.length - 1) {\n output += `-${numberArray[k]}`;\n }\n }\n return output;\n}\n\nexports.compressEnhancedRange = function utils$$compressEnhancedRange(slices) {\n assert(slices && Array.isArray(slices));\n slices = [...slices];\n slices.sort((x, y) => x.sliceNumber - y.sliceNumber); // increasing...\n let start = slices[0];\n let output = fragment(start);\n for (let k = 1; k < slices.length; k++) {\n if (slices[k].sliceNumber - slices[k - 1].sliceNumber !== 1) {\n output += (slices[k - 1].sliceNumber > start.sliceNumber) ? `-${fragment(slices[k - 1])},` : ',';\n start = slices[k];\n output += `${fragment(start)}`;\n } else if (k === slices.length - 1) {\n output += `-${fragment(slices[k])}`;\n }\n }\n return output;\n}\n\nfunction fragment(slice) {\n return `${slice.sliceNumber}.${slice.isEstimationSlice}.${slice.isLongSlice}`;\n}\n\n/**\n * categorizeContentType\n * Analyze contentType (cf. https://www.w3.org/Protocols/rfc1341/4_Content-Type.html) and\n * determine whether it represents JSON, KVIN, binary or text.\n *********************************************************************************************/\n\nfunction normalizeCharset(charset)\n{\n const checkQuote = (c) => { return (c === \"'\" || c === '\"' || c === \"`\"); }\n if (checkQuote(charset[0]) && checkQuote(charset[charset.length - 1]))\n charset = charset.slice(1, charset.length - 1);\n // We don't diagnose 1-sided quotes -- these are malformed.\n return charset.toLowerCase();\n}\n\n/**\n * Transforms \"text/plain;charset=utf8\" to { type: \"text/plain\", topType: \"text\", charset: \"utf8\" }\n * @param {string} contentType - getResponseHeader('content-type')\n * @returns {{ type: string, topType: string, charset: string }}\n */\nfunction parseContentType(contentType)\n{\n if (!contentType)\n return { type: \"text/plain\", topType: \"text\", charset: \"utf8\" };\n // Eliminate any whitespace\n contentType = contentType.replace(/\\s/g, '');\n // Parse contentType into tokens.\n // https://www.w3.org/Protocols/rfc1341/4_Content-Type.html\n // Content-Type := type \"/\" subtype *[\";\" parameter]\n // parameter := attribute \"=\" value\n const ctArray = contentType.split(';');\n const type = ctArray[0];\n const topType = type.split('/')[0];\n let charset;\n if (ctArray.length > 1)\n {\n const paramArray = ctArray[1].split('=');\n if (paramArray.length > 1 && paramArray[0].toLowerCase() === 'charset')\n charset = normalizeCharset(paramArray[1]);\n } else if (topType === 'text')\n charset = 'us-ascii'; // https://www.w3.org/Protocols/rfc1341/7_1_Text.html\n\n return { type, topType, charset };\n}\n/**\n * Transforms:\n * \"text/plain;charset=utf8\" --> { isJson: false, isKVIN: false, isBinary: false, isText: true }\n * \"application/JSON\" --> { isJson: true, isKVIN: false, isBinary: false, isText: false }\n * \"application/x-kvin\" --> { isJson: false, isKVIN: true, isBinary: false, isText: false }\n * \"application/octet-stream\" --> { isJson: false, isKVIN: false, isBinary: true, isText: false }\n * \"application/javascript\" --> { isJson: false, isKVIN: false, isBinary: false, isText: false }\n * @param {string} contentType - getResponseHeader('content-type')\n * @param {string} [overrideType] - when overrideType === \"JSON\", return { isJSON: true, isKVIN: false, isBinary: false, isText: false };\n * @returns {{ isJSON: boolean, isKVIN: boolean, isBinary: boolean, isText: boolean }}\n */\nexports.categorizeContentType = function justFetch$categorizeContentType(contentType, overrideType)\n{\n if (overrideType === 'JSON')\n return { isJSON: true, isKVIN: false, isBinary: false, isText: false };\n\n const { type, topType, charset } = parseContentType(contentType);\n\n const isJSON = (type === 'application/json');\n const isKVIN = (type === 'application/x-kvin');\n const isJS = (type === 'application/javascript');\n const isText = (topType === 'text'\n && (charset === 'utf-8' || charset === 'utf8' // utf8\n || charset === 'us-ascii' || charset === 'ascii' // 7bit\n || charset === 'iso-8859-1' || charset === 'latin1')); // 8bit\n const isBinary = !(isJSON || isKVIN || isText || isJS);\n\n return { isJSON, isKVIN, isBinary, isText };\n}\n\n/**\n * shuffle\n * hashGeneration\n *********************************************************************************************/\n\n/**\n * Randomly shuffle array.\n * @param {Array<any>} array\n */\nexports.shuffle = function utils$$shuffle(array) {\n for (let i = array.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const temp = array[i];\n array[i] = array[j];\n array[j] = temp;\n }\n}\n\nexports.hashGeneration = function utils$$hashGeneration(object) {\n const { sha256 } = (__webpack_require__(/*! dcp/dcp-client/wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\")._internalEth.util);\n return sha256(Buffer.from(JSON.stringify(object)));\n}\n\n/**\n * Helpers for communicating jobPerfData stats with Supervisor, RS, TD.\n * collectMetrics\n * constructAlpha\n * constructAlphaFromDb\n * localTime\n * globalTime\n * globalTimeFromDb\n * totalTimeFromDb\n * nextEma\n *********************************************************************************************/\n\n/**\n * Construct the payload for updating job metrics in Supervisor.\n * Returned as part of getJobsForTask and in result submitter 'result' processing.\n * @param {*} job - Row from jobs table.\n * @param {*} jobPerfData - Correpsonding row from jobPerfData table.\n * @returns {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number, lastSliceNumber: number, measuredSlices: number, alpha: number}}\n */\nexports.collectMetrics = function utils$$collectMetrics(job, jobPerfData)\n{\n const lastSliceNumber = Number(job.lastSliceNumber || 0);\n const nextSliceNumber = Number(job.nextSliceNumber || 0);\n // Invariant jobPerfData.measuredSlices <= min(job.nextSliceNumber-1, job.lastSliceNumber)\n const measuredSlices = Math.min(Number(jobPerfData.measuredSlices), nextSliceNumber, lastSliceNumber);\n const alpha = exports.constructAlpha(lastSliceNumber, measuredSlices);\n const metrics = {\n sliceCPUTime: Number(jobPerfData.sliceCPUTime || 0),\n sliceGPUTime: Number(jobPerfData.sliceGPUTime || 0),\n sliceCPUDensity: Number(jobPerfData.sliceCPUDensity || 0),\n sliceGPUDensity: Number(jobPerfData.sliceGPUDensity || 0),\n sliceInDataSize: Number(jobPerfData.sliceInDataSize || 0),\n sliceOutDataSize: Number(jobPerfData.sliceOutDataSize || 0),\n // Transmit the EMA coefficient information to supervisor.\n lastSliceNumber,\n measuredSlices,\n alpha,\n };\n return metrics;\n}\n\n/**\n * Construct the EMA coefficient for computing jobPerfData columns.\n * @param {number} lastSliceNumber - job.lastSliceNumber\n * @param {number} measuredSlices - Math.min(jobPerfData.measuredSlices, job.nextSliceNumber, job.lastSliceNumber)\n * @returns {number} - 0 < retVal <= 0.5\n */\nexports.constructAlpha = function utils$$constructAlpha(lastSliceNumber, measuredSlices)\n{\n // lastSliceNumber - measuredSlices is # of slices which have not reported results.\n const alpha = Math.min(0.5, Math.max(1.0, lastSliceNumber - measuredSlices) / lastSliceNumber);\n return alpha;\n}\n\n/**\n * Construct the EMA coefficient for computing jobPerfData columns.\n * @param {*} job - row in jobs table\n * @param {*} jobPerfData - corresponding row in jobPerfData table\n * @returns {number} - 0 < retVal <= 0.5\n */\nexports.constructAlphaFromDb = function utils$$constructAlpha(job, jobPerfData)\n{\n const last = Number(job.lastSliceNumber || 0);\n const next = Number(job.nextSliceNumber || 0);\n const measured = Math.min(Number(jobPerfData.measuredSlices), next, last);\n const alpha = exports.constructAlpha(last, measured);\n return alpha;\n}\n\n/**\n * Compute density weighted time span.\n * @param {{CPUTime: number, GPUTime: number, CPUDensity: number, GPUDensity: number}} metrics\n * @returns {number}\n */\nexports.localTime = function utils$$localTime(metrics)\n{\n if (!metrics) return 0;\n const ltime = metrics.CPUTime + metrics.GPUTime;\n return ltime;\n}\n\n/**\n * Compute density weighted time span.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalTime = function utils$$globalTime(metrics)\n{\n if (!metrics) return 0;\n const gtime = metrics.sliceCPUTime + metrics.sliceGPUTime;\n return gtime;\n}\n\n/**\n * Compute total amount of time it takes to execute a slice.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalTotalTime = function utils$$globalTotalTime(metrics)\n{\n if (!metrics) return 0;\n const gttime = (metrics.sliceCPUTime + metrics.sliceGPUTime) / ((metrics.sliceCPUDensity + metrics.sliceGPUDensity) || 1);\n return gttime;\n}\n\n/**\n * Compute density.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalDensity = function utils$$globalDensity(metrics)\n{\n const gdens = metrics?.sliceCPUDensity || 1;\n return gdens;\n}\n\n/**\n * Compute GPU density.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalGPUDensity = function utils$$globalGPUDensity(metrics)\n{\n const ggdens = metrics?.sliceGPUDensity || 1;\n return ggdens;\n}\n\n/**\n * Compute density weighted time span for running phase in requestTask.\n * @param {*} jobPerfData\n * @returns {number}\n */\nexports.globalTimeFromDb = function utils$$globalTimeFromDb(jobPerfData)\n{\n const metrics = {\n sliceCPUTime: Number(jobPerfData.sliceCPUTime || 0),\n sliceGPUTime: Number(jobPerfData.sliceGPUTime || 0),\n sliceCPUDensity: Number(jobPerfData.sliceCPUDensity || 0),\n sliceGPUDensity: Number(jobPerfData.sliceGPUDensity || 0),\n };\n return exports.globalTime(metrics);\n}\n\n/**\n * Compute density weighted time span for estimation phase in requestTask.\n * @param {*} jobPerfData\n * @returns {number}\n */\nexports.totalTimeFromDb = function utils$$totalTimeFromDb(jobPerfData)\n{\n const metrics = {\n sliceCPUTime: Number(jobPerfData.totalCPUTime),\n sliceGPUTime: Number(jobPerfData.totalGPUTime),\n sliceCPUDensity: Number(jobPerfData.sliceCPUDensity || 0),\n sliceGPUDensity: Number(jobPerfData.sliceGPUDensity || 0),\n };\n return exports.globalTime(metrics);\n}\n\n/**\n * Create next step in an EMA time series of numeric values.\n * return alpha * current + (1 - alpha) * previous;\n * @param {number} previous\n * @param {number} current\n * @param {number} alpha\n * @returns {number}\n */\nexports.nextEma = function utils$$nextEma(previous, current, alpha)\n{\n if (!previous)\n return current;\n return alpha * current + (1 - alpha) * previous;\n}\n\n/**\n * CG service error handling types and helpers.\n *********************************************************************************************/\n\n/**\n * @typedef {object} apiClientType\n * @property {boolean} success\n * @property {*} [payload]\n * @property {DCPError} [error]\n */\n\n/**\n * @typedef {object} apiServiceType\n * @property {boolean} success\n * @property {*} [payload]\n * @property {string} [message]\n * @property {string} [stack]\n * @property {string} [code]\n */\n\n/**\n * @param {apiServiceType} payload \n * @returns {apiClientType}\n */\nexports.reconstructServiceError = function utils$$reconstructServiceError(payload) {\n assert(!payload.success);\n let ex = new DCPError(payload.message);\n ex.stack = dcpConfig.worker.allowConsoleAccess ? payload.stack : '';\n if (payload.code) ex.code = payload.code;\n return exports._clientError(ex);\n}\n\n/**\n * @param {DCPError} ex\n * @returns {apiClientType}\n */\nexports._clientError = function utils$$_clientError(ex) {\n if (dcpConfig.worker.allowConsoleAccess) console.error(ex);\n return { success: false, error: ex };\n}\n/**\n * @param {string} message\n * @returns {apiClientType}\n */\nexports.clientError = function utils$$clientError(message) {\n const ex = new DCPError(message);\n return exports._clientError(ex);\n}\n\n/**\n * @param {DCPError} ex\n * @returns {apiServiceType}\n */\nexports._serviceError = function utils$$_serviceError(ex) {\n return { success: false, message: ex.message, stack: ex.stack, code: ex.code };\n}\n/**\n * @param {string} message\n * @param {string|object} codeEx\n * @returns {apiServiceType}\n */\nexports.serviceError = function utils$$serviceError(message, codeEx) {\n const ex = new DCPError(message, codeEx);\n return exports._serviceError(ex);\n}\n\nexports.chalk = function utils$$chalk()\n{\n return new ((__webpack_require__(/*! chalk */ \"./node_modules/chalk/source/index.js\").Instance))({level: exports.useChalk ? 1 : 0});\n}\n\n/**\n * exports\n *********************************************************************************************/\n\nif ((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").platform) === 'nodejs') {\n Object.assign(exports, __webpack_require__(/*! ./tmpfiles */ \"./src/utils/tmpfiles.js\"));\n Object.assign(exports, __webpack_require__(/*! ./readln */ \"./src/utils/readln.js\"));\n}\nmodule.exports.encodeDataURI = __webpack_require__(/*! ./encodeDataURI */ \"./src/utils/encodeDataURI.js\").encodeDataURI;\n\nObject.assign(exports, __webpack_require__(/*! ./sh */ \"./src/utils/sh.js\"));\nObject.assign(exports, __webpack_require__(/*! ./eventUtils */ \"./src/utils/eventUtils.js\"));\nObject.assign(exports, __webpack_require__(/*! ./obj-merge */ \"./src/utils/obj-merge.js\"));\nObject.assign(exports, __webpack_require__(/*! ./make-data-uri */ \"./src/utils/make-data-uri.js\"));\nObject.assign(exports, __webpack_require__(/*! ./just-fetch */ \"./src/utils/just-fetch.js\"));\nObject.assign(exports, __webpack_require__(/*! ./inventory */ \"./src/utils/inventory.js\"));\nObject.assign(exports, __webpack_require__(/*! ./fetch-keystore */ \"./src/utils/fetch-keystore.js\"));\nObject.assign(exports, __webpack_require__(/*! ./fetch-uri */ \"./src/utils/fetch-uri.js\"));\n\n\n//# sourceURL=webpack://dcp/./src/utils/index.js?");
5200
+ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_modules/process/browser.js */ \"./node_modules/process/browser.js\");\n/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js */ \"./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js\")[\"Buffer\"];\n/**\n * @file src/utils/index.js\n * @author Ryan Rossiter\n * Paul, paul@distributive.network\n * @date Feb 2020\n * April 2023\n *\n * Place to put little JS utilities. If they are more than a few lines, please `require` and `export` \n * them instead of making this monolithic.\n * @module dcp/utils\n * @copyright Copyright (c) 2018-2023, Distributive Corp. All Rights Reserved\n */\n/* global dcpConfig */ // eslint-disable-line no-redeclare\n// @ts-check\n\n\nconst { DCPError } = __webpack_require__(/*! dcp/common/dcp-error */ \"./src/common/dcp-error.js\");\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst DCP_ENV = __webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\");\n\nObject.assign(exports, __webpack_require__(/*! ./at-exit */ \"./src/utils/at-exit.js\"));\nmodule.exports.paramUtils = __webpack_require__(/*! ./assert-params */ \"./src/utils/assert-params.js\");\nmodule.exports.httpUtils = __webpack_require__(/*! ./http */ \"./src/utils/http.js\");\nmodule.exports.serialize = __webpack_require__(/*! ./serialize */ \"./src/utils/serialize.js\");\nObject.assign(exports, __webpack_require__(/*! ./web-format-date */ \"./src/utils/web-format-date.js\"));\nObject.assign(exports, __webpack_require__(/*! ./confirm-prompt */ \"./src/utils/confirm-prompt.js\"));\nObject.assign(exports, __webpack_require__(/*! ./content-encoding */ \"./src/utils/content-encoding.js\"));\n\n/** @typedef {import('dcp/dcp-client/worker/slice').Slice} Slice */\n/** @typedef {import('dcp/dcp-client/worker/sandbox').Sandbox} Sandbox */\n\n /**\n * Writes object properties into another object matching shape.\n * For example: if obj = { a: { b: 1, c: 2}}\n * and source = { a: {c: 0, d: 1}}\n * then obj will be updated to { a: { b: 1, c: 0, d: 1 } }\n * compare this to Object.assign which gives { a: { c: 0, d: 1 } }\n */\nconst setObjProps = module.exports.setObjProps = (obj, source) => {\n for (let p in source) {\n if (typeof source[p] === 'object') setObjProps(obj[p], source[p]);\n else obj[p] = source[p];\n }\n}\n\n\n/**\n * Generates a new random opaqueId i.e. a 22-character base64 string.\n * Used for job and slice ids.\n */\nmodule.exports.generateOpaqueId = function utils$$generateOpaqueId()\n{\n const schedulerConstants = __webpack_require__(/*! dcp/common/scheduler-constants */ \"./src/common/scheduler-constants.js\");\n if (!utils$$generateOpaqueId.nanoid)\n {\n const nanoidModule = __webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\");\n utils$$generateOpaqueId.nanoid = nanoidModule.customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+', schedulerConstants.workerIdLength);\n }\n\n return utils$$generateOpaqueId.nanoid();\n}\n\n/**\n * Accepts an object and a list of properties to apply to the object to retreive a final value.\n * E.g.\n * object = { a: { b: { c: 4 } } }\n * listOfProperties = [\"a\", \"b\", \"c\"]\n * return = 4\n */\nmodule.exports.getNestedFromListOfProperties = function (object, listOfProperties) {\n return listOfProperties.reduce((o, k) => {\n if (typeof o !== 'object') return undefined;\n return o[k];\n }, object);\n}\n\n/**\n * Accepts an object and a dot-separated property name to retrieve from the object.\n * E.g.\n * object = { a: { b: { c: 4 } } }\n * property = 'a.b.c'\n * return = 4\n *\n * @param {object} object \n * @param {string} property \n */\nmodule.exports.getNestedProperty = function (object, property) {\n if (typeof property !== 'string') return undefined;\n return module.exports.getNestedFromListOfProperties(object, property.split(\".\"));\n}\n\n/**\n * Accepts an object and a list of properties and a value and mutates the object\n * to have the specified value at the location denoted by the list of properties.\n * Similar to getNestedFromListOfProperties, but sets instead of gets.\n */\nmodule.exports.setNestedFromListOfProperties = function (object, listOfProperties, value) {\n if(!listOfProperties.length || listOfProperties.length < 1) {\n throw new Error(\"listOfProperties must be an array of length >= 1\");\n }\n const indexOfLastProp = listOfProperties.length - 1;\n const pathToParent = listOfProperties.slice(0, indexOfLastProp);\n const parent = module.exports.getNestedFromListOfProperties(object, pathToParent);\n if(!parent) {\n throw new Error(\"Could not find value at:\", pathToParent, \"in object:\", object);\n }\n const lastProperty = listOfProperties[indexOfLastProp];\n parent[lastProperty] = value;\n}\n\n/**\n * Block the event loop for a specified time\n * @milliseconds the number of milliseconds to wait (integer)\n */\nexports.msleep = function utils$$msleep(milliseconds) {\n try\n {\n let sab = new SharedArrayBuffer(4);\n let int32 = new Int32Array(sab);\n Atomics.wait(int32, 0, 0, milliseconds);\n }\n catch(error)\n {\n console.error('Cannot msleep;', error);\n }\n}\n\n/**\n * Block the event loop for a specified time\n * @seconds the number of seconds to wait (float)\n */\nexports.sleep = function utils$$sleep(seconds) {\n return exports.msleep(seconds * 1000);\n}\n\n/** \n * Resolve a promise after a specified time.\n * \n * @param {number} ms the number of milliseconds after which to resolve the promise.\n * @returns Promise with an extra property, intr(). Calling this function causes the promise\n * to resolve immediately. If the promise was interrupted, it will resolve with false;\n * otherwise, true.\n */\nexports.a$sleepMs = function a$sleepMs(ms)\n{\n var interrupt;\n \n const ret = new Promise((resolve, reject) => {\n var resolved = false;\n const timerHnd = setTimeout(function __a$sleepMs() { resolved=true; resolve(false) }, ms);\n function a$sleepMs_intr()\n {\n clearTimeout(timerHnd);\n if (!resolved)\n resolve(true);\n }\n \n interrupt = a$sleepMs_intr;\n });\n\n ret.intr = () => interrupt();\n return ret;\n}\n\n/** \n * @see: a$sleepMs\n * @param {number} ms the number of milliseconds after which to resolve the promise.\n */\nexports.a$sleep = function a$sleep(seconds) {\n return exports.a$sleepMs(seconds * 1000);\n}\n\n/** \n * Returns the number of millisecond in a time expression.\n * @param s {number} The number of seconds\n * @returns {number}\n */\n/**\n * @param s {string|number} - A complex time expression using m, w, d, s, h. '10d 6h 1s' means 10 days, 6 hours, and 1 second.\n */\nexports.ms = function utils$$ms(s)\n{\n let ms = 0;\n \n if (typeof s === 'number')\n return s * 1000;\n\n assert(typeof s === 'string');\n \n for (let expr of s.match(/[0-9.]+[smhdw]/g))\n {\n let unit = expr.slice(-1);\n let value = +expr.slice(0, -1);\n\n switch(unit)\n {\n case 's': {\n ms += value * 1000;\n break;\n }\n case 'm': {\n ms += value * 1000 * 60;\n break;\n }\n case 'h': {\n ms += value * 1000 * 60 * 60;\n break;\n }\n case 'd': {\n ms += value * 1000 * 60 * 60 * 24;\n break;\n }\n case 'w': {\n ms += value * 1000 * 60 * 60 * 24 * 7;\n break;\n }\n default: {\n throw new Error(`invalid time unit ${unit}`);\n }\n }\n }\n\n return ms;\n}\n\n/**\n * @param n {number} this number is returned, divided by 100 .\n */\n/**\n * @param n {string} this string is converted to a number and returned; it is divided by 100 if it ends in %.\n */\n/**\n * Returns a percentage as a number.\n * @param n {number|string} this number is returned\n * @returns {number}\n */\nexports.pct = function utils$$pct(n)\n{\n if (typeof n === 'number')\n return n / 100;\n\n if (n.match(/%$/))\n return +n / 100;\n\n return +n;\n}\n\n/**\n * Coerce human-written or registry-provided values into Boolean in a sensisble way.\n */\nexports.booley = function utils$$booley(check)\n{\n switch (typeof check)\n {\n case 'undefined':\n return false;\n case 'string':\n return check && (check !== 'false');\n case 'boolean':\n return check;\n case 'number':\n case 'object':\n return Boolean(check);\n default:\n throw new Error(`can't coerce ${typeof check} to booley`);\n }\n}\n\ntry {\n exports.useChalk = requireNative ? requireNative('tty').isatty(0) || process.env.FORCE_COLOR : false;\n}\ncatch (error) {\n if (error.message.includes('no native require'))\n exports.useChalk = false;\n else\n throw error;\n}\n\n/** \n * Factory function which constructs an error message relating to version mismatches\n * @param {string} oldThingLabel The name of the thing that is too old\n * @param {string} upgradeThingLabel [optional] The name of the thing that needs to be upgraded to fix that; if\n * unspecified, we use oldThingLabel.\n * @param {string} newThingLabel What the thing is that is that is newer than oldThingLabel\n * @param {string} minimumVersion The minimum version of the old thing that the new thing supports. \n * Obstensibibly semver, but unparsed.\n * @param {string} code [optional] A code property to add to the return value\n *\n * @returns instance of Error\n */\nexports.versionError = function dcpClient$$versionError(oldThingLabel, upgradeThingLabel, newThingLabel, minimumVersion, code)\n{\n function repeat(what, len) {\n let out = '';\n while (len--)\n out += what;\n return out;\n }\n\n function bold(string) {\n if ((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").platform) === 'nodejs') {\n const chalk = new (requireNative('chalk').Instance)({level: exports.useChalk ? 1 : 0});\n\n return chalk.yellow(string);\n }\n\n return string;\n }\n \n var mainMessage = `${oldThingLabel} is too old; ${newThingLabel} needs version ${minimumVersion}`;\n var upgradeMessage = bold(`Please upgrade ${upgradeThingLabel || oldThingLabel}${repeat(\" \", mainMessage.length - 15 - upgradeThingLabel.length)}`);\n var error = new Error(`\n****${repeat('*', mainMessage.length)}****\n*** ${repeat(' ', mainMessage.length)} ***\n*** ${mainMessage} ***\n*** ${upgradeMessage} ***\n*** ${repeat(' ', mainMessage.length)} ***\n****${repeat('*', mainMessage.length)}****\n`);\n\n if (code)\n error.code = code;\n return error;\n}\n\n/**\n * Detect whether value is from the output of job-values:serializeJobValue.\n * @param {*} value\n * @returns {boolean}\n */\nexports.isFromSerializeJobValue = function utils$$isFromSerializeJobValue(value)\n{\n return value.hasOwnProperty('string') && value.hasOwnProperty('method') && value.hasOwnProperty('MIMEType') && Object.keys(value).length === 3;\n}\n\n/**\n * Perf Timers:\n * shortTime\n * class PerfTimers\n *********************************************************************************************/\n\n/**\n * Previous short form perf time stamp.\n * @private\n * @type {Date}\n */\nlet previousTime = new Date();\n\n/**\n * Short form perf time format with timespan diff from last call.\n * @param {boolean} [displayDiffOnly=true]\n * @returns {string}\n */\nexports.shortTime = function utils$$shortTime(displayDiffOnly=true) {\n const currentTime = new Date();\n const diff = currentTime.getTime() - previousTime.getTime();\n previousTime = currentTime;\n if (displayDiffOnly) return `diff=${diff}`;\n return `diff=${diff}: ${currentTime.getMinutes()}.${currentTime.getSeconds()}.${currentTime.getMilliseconds()}`;\n}\n\n/**\n * By default returns\n * functionName:lineNumber: message\n * When (!message || !message.length), returns\n * functionName:lineNumber\n *\n * When shortForm is true, returns\n * lineNumber: message\n *\n * @param {string} [message]\n * @param {number} [depth=2]\n * @param {boolean} [shortForm=false]\n * @returns {string}\n */\nexports.debugLine = function debugLine(message, depth = 2, shortForm = false) {\n const e = new Error();\n const frame = e.stack.split(\"\\n\")[depth];\n const lineNumber = frame.split(\":\").reverse()[1];\n const funcName = frame.split(\" \")[5];\n const header = shortForm ? 'line#' : funcName;\n const headerAndLine = `${header}:${lineNumber}`;\n if (!message || !message.length) return headerAndLine;\n return headerAndLine + \" \" + message;\n}\n\n/**\n * class Tracer: Method mark, displays time span since the last mark.\n *\n * When perf testing the first step is to place a bunch of timer entries\n * in the code where the time difference from the last entry is displayed\n * This class Tracer is used as follows:\n * const tracer = new Tracer(tag);\n * ...............................\n * tracer.mark(); // displays dt1: lineNumber: tag -- where dt1 is time since constructor was called\n * ...............................\n * tracer.mark(); // displays dt2: lineNumber: tag -- where dt2 is time since previous mark\n * ...............................\n * tracer.mark(); // displays dt3: lineNumber: tag -- where dt3 is time since previous mark\n *\n * There are many variations possible as can be seen by the comment on the mark method.\n */\nclass Tracer {\n /**\n * \n * @param {boolean} [shortForm=true]\n * @param {string} [tag]\n */\n constructor(shortForm = true, tag) {\n this.shortForm = shortForm;\n this.tag = tag;\n this.previousTime = new Date();\n }\n /**\n * Let diff be the time span since the last call to mark or the Tracer constructor.\n * Let lineNumber be the line # of the file containing the call to mark.\n * Let line# be the string \"line#\"\n * Let funcName be the name of the function where mark was called.\n * When tag.length > 0,\n * displays: diff: line#:lineNumber: tag:message\n * or: diff: line#:lineNumber: tag when !message\n * or: diff: funcName:lineNumber tag:message when !shortForm\n * or: diff: funcName:lineNumber tag when !shortForm and !message\n * else\n * displays: diff: line#:lineNumber: message\n * or: diff: line#lineNumber: when !message\n * or: diff: funcName:lineNumber message when !shortForm\n * or: diff: funcName:lineNumber when !shortForm and !message\n * @param {string} [message]\n */\n mark(message) {\n const currentTime = new Date();\n const diff = currentTime.getTime() - this.previousTime.getTime();\n this.previousTime = currentTime;\n let tag = this.tag;\n if (tag && tag.length && message) tag = `${tag}:${message}`;\n else if (message) tag = message;\n console.log(`${diff}: ${exports.debugLine(tag, 3 /* depth*/, this.shortForm)}`);\n }\n}\nexports.Tracer = Tracer;\n\n/**\n* class PerfTimer: Construct a set of possibly overlapping timers.\n*\n* Example:\n* const perfTimers = new PerfTimers(['timer0', 'timer1', 'timer2']);\n* perfTimers.start('timer0');\n* perfTimers.start('timer1');\n* .........................\n* perfTimers.stop('timer1');\n* .........................\n* perfTimers.stop('timer0');\n* .........................\n* perfTimers.start('timer2');\n* ..........................\n* perfTimers.stop('timer2');\n* perfTimers.sum(name => name !== 'timer1');\n*\n* Should display something like:\n* timer1: 472ms\n* timer0: 1650ms\n* timer2: 2333ms\n* The sum of timers [timer0, timer2] is 3983ms\n*/\nclass PerfTimers {\n /**\n * @constructor\n * @param {string[]} perfTimerNames\n * @param {boolean} [disable=false]]\n * @param {boolean} [summaryView=false]]\n */\n constructor (perfTimerNames, disable = false, summaryView = false) {\n this.disable = disable;\n if (this.disable) return;\n assert(Array.isArray(perfTimerNames));\n /** @type {string[]} */\n this.perfTimerNames = perfTimerNames;\n /** @type {boolean} */\n this.summaryView = summaryView;\n /** @type {number[]} */\n this.perfTimers = Array.from({ length: perfTimerNames.length }, (v, i) => 0);\n /** @type {object} */\n this.perfTimerMap = {};\n for (let k = 0; k < this.perfTimerNames.length; k++)\n this.perfTimerMap[this.perfTimerNames[k]] = k;\n }\n /**\n * Start a timer on perfTimerName\n * @param {string} perfTimerName\n */\n start(perfTimerName) {\n if (this.disable) return;\n const slot = this.perfTimerMap[perfTimerName];\n if (slot === undefined) throw new Error(`PerfDebugging: perfTimer '${perfTimerName}' not found.`);\n this.perfTimers[slot] = Date.now();\n }\n /**\n * Stop a timer on perfTimerName and display the result.\n * @param {string} perfTimerName\n */\n stop(perfTimerName) {\n if (this.disable) return;\n const slot = this.perfTimerMap[perfTimerName];\n if (slot === undefined) throw new Error(`PerfDebugging: perfTimer '${perfTimerName}' not found.`);\n this.perfTimers[slot] = Date.now() - this.perfTimers[slot];\n if (!this.summaryView) console.log(`${perfTimerName}: ${this.perfTimers[slot]}ms`);\n }\n /** \n * @callback PredicateCallback\n * @param {string} value\n * @param {number} index\n * @param {string[]} array\n */\n /**\n * Use predicate to choose a subset of this.perfTimerNames,\n * sum up the corresponding timers and display the result.\n * @param {PredicateCallback} predicate\n * @param {boolean} [forceDisplay=false]\n */\n sum(predicate, forceDisplay = false) {\n if (this.disable || this.summaryView && !forceDisplay) return;\n const names = this.perfTimerNames.filter(predicate);\n const timers = this.perfTimers.map(k => this.perfTimerMap(names[k]));\n const sum = timers.reduce((previous, current) => previous + current, 0);\n console.log(`The sum of timers ${JSON.stringify(names)} is ${sum}ms`);\n }\n /**\n * Display summary.\n * @param {PredicateCallback} predicate\n * @param {boolean} [forceDisplay=false]\n */\n summary(predicate, forceDisplay = false) {\n if (this.disable) return;\n if (this.summaryView || forceDisplay) {\n for (let k = 0; k < this.perfTimers.length; k++)\n console.log(`${this.perfTimerNames[k]}: ${this.perfTimers[k]}ms`);\n }\n if (predicate) this.sum(predicate);\n }\n}\nexports.PerfTimers = PerfTimers;\n\n/**\n * Sandbox and Slice debugging and logging tools.\n * dumpSlices -- dump array of slices\n * dumpSandboxes -- dump array of sandboxes\n * dumpSlicesIfNotUnique -- dump array of slices when there are dups\n * dumpSandboxesIfNotUnique -- dump array of sandboxes when there are dups\n * isUniqueSlices -- detect dups in an array of slices\n * isUniqueSandboxes -- detect dups in an array of sandboxes\n *********************************************************************************************/\n\n/**\n * Log sliceArray.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n */\nexports.dumpSlices = function utils$$dumpSlices(sliceArray, header) {\n if (header) console.log(`\\n${header}`);\n console.log(exports.compressSlices(sliceArray));\n}\n\n/**\n * Log sandboxArray.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n */\nexports.dumpSandboxes = function utils$$dumpSandboxes(sandboxArray, header) {\n if (header) console.log(`\\n${header}`);\n console.log(exports.compressSandboxes(sandboxArray));\n}\n\n/**\n * If the elements of sliceArray are not unique, log the duplicates and log the full array.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n */\nexports.dumpSlicesIfNotUnique = function utils$$dumpSlicesIfNotUnique(sliceArray, header) {\n if (!exports.isUniqueSlices(sliceArray, header))\n console.log(exports.compressSlices(sliceArray));\n}\n\n/**\n * If the elements of sandboxArray are not unique, log the duplicates and log the full array.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n */\nexports.dumpSandboxesIfNotUnique = function utils$$dumpSandboxesIfNotUnique(sandboxArray, header) {\n if (!exports.isUniqueSandboxes(sandboxArray, header))\n console.log(exports.compressSandboxes(sandboxArray));\n}\n\n/**\n * Checks whether the elements of sliceArray are unique and if not, log the duplicates.\n * @param {Slice[]} sliceArray\n * @param {string} [header]\n * @param {function} [log]\n * @returns {boolean}\n */\nexports.isUniqueSlices = function utils$$isUniqueSlices(sliceArray, header, log) {\n const slices = [];\n let once = true;\n sliceArray.forEach(x => {\n if (slices.indexOf(x) >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x) : console.log(`\\tFound duplicate slice ${x.identifier}.`);\n } else slices.push(x);\n });\n return sliceArray.length === slices.length;\n}\n\n/**\n * Checks whether the elements of sandboxArray are unique and if not, log the duplicates.\n * @param {Sandbox[]} sandboxArray\n * @param {string} [header]\n * @param {function} [log]\n * @returns {boolean}\n */\nexports.isUniqueSandboxes = function utils$$isUniqueSandboxes(sandboxArray, header, log) {\n const sandboxes = [];\n let once = true;\n sandboxArray.forEach(x => {\n if (sandboxes.indexOf(x) >= 0) {\n if (once && header) console.log(`\\n${header}`); once = false;\n log ? log(x) : console.log(`\\tFound duplicate sandbox ${x.identifier}.`);\n } else sandboxes.push(x);\n });\n return sandboxArray.length === sandboxes.length;\n}\n\n/**\n * JSON Serialization\n * stringify -- ignore cycles\n * dumpJSON -- safely wrapped stringify with header\n * dumpObject -- Apply dumpJSON to every [key, value] of Object.entries(theObject)\n *********************************************************************************************/\n\n/**\n * Quck and dirty JSON serialization that ignores cycles.\n *\n * @param {*} theAnything - entity to be serialized.\n * @param {number} [truncationLength=512] - number of string elements to return.\n * @param {number} [space=0] - # of spaces to indent (0 <= space <= 10).\n * @returns {string}\n */\nexports.stringify = function _stringify(theAnything, truncationLength = 512, space = 2) {\n let cache = [];\n const str = JSON.stringify(theAnything, (key, value) => {\n if (typeof value === 'object' && value !== null) {\n if (cache.includes(value)) return;\n cache.push(value);\n }\n return value;\n }, space);\n cache = null;\n if (str && truncationLength > 0)\n return str.slice(0, truncationLength);\n return str;\n}\n\n/**\n * Calls truncated JSON.stringify on theAnything.\n * @param {*} theAnything\n * @param {string} [header='dumpJSON']\n * @param {number} [truncationLength=512]\n */\nexports.dumpJSON = function utils$$dumpJSON(theAnything, header = 'dumpJSON: ', truncationLength = 512) {\n if (theAnything) {\n const strV = exports.stringify(theAnything, truncationLength);\n if (strV) console.log(`${header}: ${String(strV)}`);\n else console.log(`${header}:`, theAnything);\n } else console.log(`${header}:`, theAnything);\n}\n\n/**\n * Iterates over all property [key, value]-pairs of theObject and call truncated JSON.stringify on property values.\n * @param {object} theObject\n * @param {string} [header='dumpObject']\n * @param {number} [truncationLength=512]\n */\nexports.dumpObject = function utils$$dumpObject(theObject, header = 'dumpObject: ', truncationLength = 512, dumpKeys = true) {\n if (theObject) {\n if (dumpKeys) console.log(`${header}: dump the keys`, Object.keys(theObject));\n console.log(`${header}: dump the key-value entries...`);\n console.group();\n for (const [key, value] of Object.entries(theObject))\n exports.dumpJSON(value, `${header}.${key}`, truncationLength);\n console.groupEnd();\n }\n}\n\n/**\n * Compressed Representations and Maps\n * toJobMap\n * compressSandboxes\n * compressSlices\n * compressJobMap\n * compressJobArray\n * compressJobValue\n * compressJobEntry\n * compressEnhancedJobEntry\n * truncateAddress\n * compressRange\n * compressEnhancedRange\n *********************************************************************************************/\n\n/**\n * @param {object[]} jobArray\n * @param {function} functor\n * @returns {object}\n */\nexports.toJobMap = function utils$$toJobMap(jobArray, functor) {\n const jobMap = {};\n for (const x of jobArray) {\n if (!jobMap[x.jobAddress]) jobMap[x.jobAddress] = [functor(x)];\n else jobMap[x.jobAddress].push(functor(x));\n }\n return jobMap;\n}\n\n/**\n * @param {Sandbox[]} sandboxArray\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressSandboxes = function utils$$compressSandboxes(sandboxArray, digits = -1) {\n const jobSandboxMap = exports.toJobMap(sandboxArray, sbx => sbx.id);\n return exports.compressJobMap(jobSandboxMap, digits);\n}\n\n/**\n * @param {Slice[]} sliceArray\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressSlices = function utils$$compressSlices(sliceArray, digits = -1) {\n const jobSliceMap = exports.toJobMap(sliceArray, slice => slice.sliceNumber);\n return exports.compressJobMap(jobSliceMap, digits);\n}\n\n/**\n * @param {object} jobMap\n * @param {number} [digits=-1]\n * @param {function} [functor]\n * @returns {string}\n */\nexports.compressJobMap = function utils$$compressJobMap(jobMap, digits = -1, functor) {\n if (functor)\n {\n jobMap = { ...jobMap };\n for (const job in jobMap)\n jobMap[job] = jobMap[job].map((x) => functor(x));\n }\n return exports.compressJobArray(Object.entries(jobMap), digits);\n}\n\n/**\n * @param {object[]} jobArray\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobArray = function utils$$compressJobArray(jobArray, digits = -1) {\n let output = '';\n for (let k = 0; k < jobArray.length; k++) {\n output += exports.compressJobValue(jobArray[k], digits);\n }\n return output;\n}\n\n/**\n * @param {object|object[]} jobValue\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobValue = function utils$$compressJobValue(jobValue, digits = -1) {\n if (jobValue.job && jobValue.slices)\n return exports.compressJobEntry(jobValue.job, jobValue.slices, digits);\n if (jobValue.length === 2)\n return exports.compressJobEntry(jobValue[0], jobValue[1], digits);\n return 'nada';\n}\n\n/**\n * @param {*} jobAddress\n * @param {Array<number>} sliceNumbers\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.compressJobEntry = function utils$$compressJobEntry(jobAddress, sliceNumbers, digits = -1) {\n return `${exports.truncateAddress(jobAddress, digits)}:[${exports.compressRange(sliceNumbers)}]:`;\n}\n\nexports.compressEnhancedJobEntry = function utils$$compressEnhancedJobEntry(job, slices, digits = -1) {\n return `${job.id}.${exports.truncateAddress(job.address, digits)}:[${exports.compressEnhancedRange(slices)}]:`;\n}\n\n/**\n * @param {*} address\n * @param {number} [digits=-1]\n * @returns {string}\n */\nexports.truncateAddress = function utils$$truncateAddress(address, digits = -1) {\n let value = address.toString();\n if (digits < 0) return value.startsWith('0x') ? value.substring(2) : value;\n return value.startsWith('0x') ? value.substring(2, 2 + digits) : value.substring(0, digits);\n}\n\n/**\n * Input [2, 3, 4, 7, 5, 8, 9, 13, 14, 15] returns '2-5,7-9,13-15'\n * Input [2, 3, 4, 7, 5, 8, 9, 13] returns '2-5,7-9,13'\n * Input [2, 3, 4, 7, 5, 4, 4, 8, 9] returns '2-4,4,4-5,7-9'\n * @param {Array<number>} numberArray\n * @returns {string}\n */\nexports.compressRange = function utils$$compressRange(numberArray) {\n assert(numberArray && Array.isArray(numberArray));\n numberArray = [...numberArray];\n numberArray.sort((x, y) => x - y); // increasing...\n let start = numberArray[0];\n let output = `${start}`;\n for (let k = 1; k < numberArray.length; k++) {\n assert(typeof numberArray[k] === 'number' || numberArray[k] && numberArray[k].constructor.name === 'Number');\n if (numberArray[k] - numberArray[k - 1] !== 1) {\n output += (numberArray[k - 1] > start) ? `-${numberArray[k - 1]},` : ',';\n start = numberArray[k];\n output += `${start}`;\n } else if (k === numberArray.length - 1) {\n output += `-${numberArray[k]}`;\n }\n }\n return output;\n}\n\nexports.compressEnhancedRange = function utils$$compressEnhancedRange(slices) {\n assert(slices && Array.isArray(slices));\n slices = [...slices];\n slices.sort((x, y) => x.sliceNumber - y.sliceNumber); // increasing...\n let start = slices[0];\n let output = fragment(start);\n for (let k = 1; k < slices.length; k++) {\n if (slices[k].sliceNumber - slices[k - 1].sliceNumber !== 1) {\n output += (slices[k - 1].sliceNumber > start.sliceNumber) ? `-${fragment(slices[k - 1])},` : ',';\n start = slices[k];\n output += `${fragment(start)}`;\n } else if (k === slices.length - 1) {\n output += `-${fragment(slices[k])}`;\n }\n }\n return output;\n}\n\nfunction fragment(slice) {\n return `${slice.sliceNumber}.${slice.isEstimationSlice}.${slice.isLongSlice}`;\n}\n\n/**\n * categorizeContentType\n * Analyze contentType (cf. https://www.w3.org/Protocols/rfc1341/4_Content-Type.html) and\n * determine whether it represents JSON, KVIN, binary or text.\n *********************************************************************************************/\n\nfunction normalizeCharset(charset)\n{\n const checkQuote = (c) => { return (c === \"'\" || c === '\"' || c === \"`\"); }\n if (checkQuote(charset[0]) && checkQuote(charset[charset.length - 1]))\n charset = charset.slice(1, charset.length - 1);\n // We don't diagnose 1-sided quotes -- these are malformed.\n return charset.toLowerCase();\n}\n\n/**\n * Transforms \"text/plain;charset=utf8\" to { type: \"text/plain\", topType: \"text\", charset: \"utf8\" }\n * @param {string} contentType - getResponseHeader('content-type')\n * @returns {{ type: string, topType: string, charset: string }}\n */\nfunction parseContentType(contentType)\n{\n if (!contentType)\n return { type: \"text/plain\", topType: \"text\", charset: \"utf8\" };\n // Eliminate any whitespace\n contentType = contentType.replace(/\\s/g, '');\n // Parse contentType into tokens.\n // https://www.w3.org/Protocols/rfc1341/4_Content-Type.html\n // Content-Type := type \"/\" subtype *[\";\" parameter]\n // parameter := attribute \"=\" value\n const ctArray = contentType.split(';');\n const type = ctArray[0];\n const topType = type.split('/')[0];\n let charset;\n if (ctArray.length > 1)\n {\n const paramArray = ctArray[1].split('=');\n if (paramArray.length > 1 && paramArray[0].toLowerCase() === 'charset')\n charset = normalizeCharset(paramArray[1]);\n } else if (topType === 'text')\n charset = 'us-ascii'; // https://www.w3.org/Protocols/rfc1341/7_1_Text.html\n\n return { type, topType, charset };\n}\n/**\n * Transforms:\n * \"text/plain;charset=utf8\" --> { isJson: false, isKVIN: false, isBinary: false, isText: true }\n * \"application/JSON\" --> { isJson: true, isKVIN: false, isBinary: false, isText: false }\n * \"application/x-kvin\" --> { isJson: false, isKVIN: true, isBinary: false, isText: false }\n * \"application/octet-stream\" --> { isJson: false, isKVIN: false, isBinary: true, isText: false }\n * \"application/javascript\" --> { isJson: false, isKVIN: false, isBinary: false, isText: false }\n * @param {string} contentType - getResponseHeader('content-type')\n * @param {string} [overrideType] - when overrideType === \"JSON\", return { isJSON: true, isKVIN: false, isBinary: false, isText: false };\n * @returns {{ isJSON: boolean, isKVIN: boolean, isBinary: boolean, isText: boolean }}\n */\nexports.categorizeContentType = function justFetch$categorizeContentType(contentType, overrideType)\n{\n if (overrideType === 'JSON')\n return { isJSON: true, isKVIN: false, isBinary: false, isText: false };\n\n const { type, topType, charset } = parseContentType(contentType);\n\n const isJSON = (type === 'application/json');\n const isKVIN = (type === 'application/x-kvin');\n const isJS = (type === 'application/javascript');\n const isText = (topType === 'text'\n && (charset === 'utf-8' || charset === 'utf8' // utf8\n || charset === 'us-ascii' || charset === 'ascii' // 7bit\n || charset === 'iso-8859-1' || charset === 'latin1')); // 8bit\n const isBinary = !(isJSON || isKVIN || isText || isJS);\n\n return { isJSON, isKVIN, isBinary, isText };\n}\n\n/**\n * shuffle\n * hashGeneration\n *********************************************************************************************/\n\n/**\n * Randomly shuffle array.\n * @param {Array<any>} array\n */\nexports.shuffle = function utils$$shuffle(array) {\n for (let i = array.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const temp = array[i];\n array[i] = array[j];\n array[j] = temp;\n }\n}\n\nexports.hashGeneration = function utils$$hashGeneration(object) {\n const { sha256 } = (__webpack_require__(/*! dcp/dcp-client/wallet/keystore */ \"./src/dcp-client/wallet/keystore.js\")._internalEth.util);\n return sha256(Buffer.from(JSON.stringify(object)));\n}\n\n/**\n * Helpers for communicating jobPerfData stats with Supervisor, RS, TD.\n * collectMetrics\n * constructAlpha\n * constructAlphaFromDb\n * localTime\n * globalTime\n * globalTimeFromDb\n * totalTimeFromDb\n * nextEma\n *********************************************************************************************/\n\n/**\n * Construct the payload for updating job metrics in Supervisor.\n * Returned as part of getJobsForTask and in result submitter 'result' processing.\n * @param {*} job - Row from jobs table.\n * @param {*} jobPerfData - Correpsonding row from jobPerfData table.\n * @returns {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number, lastSliceNumber: number, measuredSlices: number, alpha: number}}\n */\nexports.collectMetrics = function utils$$collectMetrics(job, jobPerfData)\n{\n const lastSliceNumber = Number(job.lastSliceNumber || 0);\n const nextSliceNumber = Number(job.nextSliceNumber || 0);\n // Invariant jobPerfData.measuredSlices <= min(job.nextSliceNumber-1, job.lastSliceNumber)\n const measuredSlices = Math.min(Number(jobPerfData.measuredSlices), nextSliceNumber, lastSliceNumber);\n const alpha = exports.constructAlpha(lastSliceNumber, measuredSlices);\n const metrics = {\n sliceCPUTime: Number(jobPerfData.sliceCPUTime || 0),\n sliceGPUTime: Number(jobPerfData.sliceGPUTime || 0),\n sliceCPUDensity: Number(jobPerfData.sliceCPUDensity || 0),\n sliceGPUDensity: Number(jobPerfData.sliceGPUDensity || 0),\n sliceInDataSize: Number(jobPerfData.sliceInDataSize || 0),\n sliceOutDataSize: Number(jobPerfData.sliceOutDataSize || 0),\n // Transmit the EMA coefficient information to supervisor.\n lastSliceNumber,\n measuredSlices,\n alpha,\n };\n return metrics;\n}\n\n/**\n * Construct the EMA coefficient for computing jobPerfData columns.\n * @param {number} lastSliceNumber - job.lastSliceNumber\n * @param {number} measuredSlices - Math.min(jobPerfData.measuredSlices, job.nextSliceNumber, job.lastSliceNumber)\n * @returns {number} - 0 < retVal <= 0.5\n */\nexports.constructAlpha = function utils$$constructAlpha(lastSliceNumber, measuredSlices)\n{\n // lastSliceNumber - measuredSlices is # of slices which have not reported results.\n const alpha = Math.min(0.5, Math.max(1.0, lastSliceNumber - measuredSlices) / lastSliceNumber);\n return alpha;\n}\n\n/**\n * Construct the EMA coefficient for computing jobPerfData columns.\n * @param {*} job - row in jobs table\n * @param {*} jobPerfData - corresponding row in jobPerfData table\n * @returns {number} - 0 < retVal <= 0.5\n */\nexports.constructAlphaFromDb = function utils$$constructAlpha(job, jobPerfData)\n{\n const last = Number(job.lastSliceNumber || 0);\n const next = Number(job.nextSliceNumber || 0);\n const measured = Math.min(Number(jobPerfData.measuredSlices), next, last);\n const alpha = exports.constructAlpha(last, measured);\n return alpha;\n}\n\n/**\n * Compute density weighted time span.\n * @param {{CPUTime: number, GPUTime: number, CPUDensity: number, GPUDensity: number}} metrics\n * @returns {number}\n */\nexports.localTime = function utils$$localTime(metrics)\n{\n if (!metrics) return 0;\n const ltime = metrics.CPUTime + metrics.GPUTime;\n return ltime;\n}\n\n/**\n * Compute density weighted time span.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalTime = function utils$$globalTime(metrics)\n{\n if (!metrics) return 0;\n const gtime = metrics.sliceCPUTime + metrics.sliceGPUTime;\n return gtime;\n}\n\n/**\n * Compute total amount of time it takes to execute a slice.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalTotalTime = function utils$$globalTotalTime(metrics)\n{\n if (!metrics) return 0;\n const gttime = (metrics.sliceCPUTime + metrics.sliceGPUTime) / ((metrics.sliceCPUDensity + metrics.sliceGPUDensity) || 1);\n return gttime;\n}\n\n/**\n * Compute density.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalDensity = function utils$$globalDensity(metrics)\n{\n const gdens = metrics?.sliceCPUDensity || 1;\n return gdens;\n}\n\n/**\n * Compute GPU density.\n * @param {{sliceCPUTime: number, sliceGPUTime: number, sliceCPUDensity: number, sliceGPUDensity: number}} metrics\n * @returns {number}\n */\nexports.globalGPUDensity = function utils$$globalGPUDensity(metrics)\n{\n const ggdens = metrics?.sliceGPUDensity || 1;\n return ggdens;\n}\n\n/**\n * Compute density weighted time span for running phase in requestTask.\n * @param {*} jobPerfData\n * @returns {number}\n */\nexports.globalTimeFromDb = function utils$$globalTimeFromDb(jobPerfData)\n{\n const metrics = {\n sliceCPUTime: Number(jobPerfData.sliceCPUTime || 0),\n sliceGPUTime: Number(jobPerfData.sliceGPUTime || 0),\n sliceCPUDensity: Number(jobPerfData.sliceCPUDensity || 0),\n sliceGPUDensity: Number(jobPerfData.sliceGPUDensity || 0),\n };\n return exports.globalTime(metrics);\n}\n\n/**\n * Compute density weighted time span for estimation phase in requestTask.\n * @param {*} jobPerfData\n * @returns {number}\n */\nexports.totalTimeFromDb = function utils$$totalTimeFromDb(jobPerfData)\n{\n const metrics = {\n sliceCPUTime: Number(jobPerfData.totalCPUTime),\n sliceGPUTime: Number(jobPerfData.totalGPUTime),\n sliceCPUDensity: Number(jobPerfData.sliceCPUDensity || 0),\n sliceGPUDensity: Number(jobPerfData.sliceGPUDensity || 0),\n };\n return exports.globalTime(metrics);\n}\n\n/**\n * Create next step in an EMA time series of numeric values.\n * return alpha * current + (1 - alpha) * previous;\n * @param {number} previous\n * @param {number} current\n * @param {number} alpha\n * @returns {number}\n */\nexports.nextEma = function utils$$nextEma(previous, current, alpha)\n{\n if (!previous)\n return current;\n return alpha * current + (1 - alpha) * previous;\n}\n\n/**\n * CG service error handling types and helpers.\n *********************************************************************************************/\n\n/**\n * @typedef {object} apiClientType\n * @property {boolean} success\n * @property {*} [payload]\n * @property {DCPError} [error]\n */\n\n/**\n * @typedef {object} apiServiceType\n * @property {boolean} success\n * @property {*} [payload]\n * @property {string} [message]\n * @property {string} [stack]\n * @property {string} [code]\n */\n\n/**\n * @param {apiServiceType} payload \n * @returns {apiClientType}\n */\nexports.reconstructServiceError = function utils$$reconstructServiceError(payload) {\n assert(!payload.success);\n let ex = new DCPError(payload.message);\n ex.stack = dcpConfig.worker.allowConsoleAccess ? payload.stack : '';\n if (payload.code) ex.code = payload.code;\n return exports._clientError(ex);\n}\n\n/**\n * @param {DCPError} ex\n * @returns {apiClientType}\n */\nexports._clientError = function utils$$_clientError(ex) {\n if (dcpConfig.worker.allowConsoleAccess) console.error(ex);\n return { success: false, error: ex };\n}\n/**\n * @param {string} message\n * @returns {apiClientType}\n */\nexports.clientError = function utils$$clientError(message) {\n const ex = new DCPError(message);\n return exports._clientError(ex);\n}\n\n/**\n * @param {DCPError} ex\n * @returns {apiServiceType}\n */\nexports._serviceError = function utils$$_serviceError(ex) {\n return { success: false, message: ex.message, stack: ex.stack, code: ex.code };\n}\n/**\n * @param {string} message\n * @param {string|object} codeEx\n * @returns {apiServiceType}\n */\nexports.serviceError = function utils$$serviceError(message, codeEx) {\n const ex = new DCPError(message, codeEx);\n return exports._serviceError(ex);\n}\n\nexports.chalk = function utils$$chalk()\n{\n return new ((__webpack_require__(/*! chalk */ \"./node_modules/chalk/source/index.js\").Instance))({level: exports.useChalk ? 1 : 0});\n}\n\n/**\n * exports\n *********************************************************************************************/\n\nif ((__webpack_require__(/*! dcp/common/dcp-env */ \"./src/common/dcp-env.js\").platform) === 'nodejs') {\n Object.assign(exports, __webpack_require__(/*! ./tmpfiles */ \"./src/utils/tmpfiles.js\"));\n Object.assign(exports, __webpack_require__(/*! ./readln */ \"./src/utils/readln.js\"));\n}\nmodule.exports.encodeDataURI = __webpack_require__(/*! ./encodeDataURI */ \"./src/utils/encodeDataURI.js\").encodeDataURI;\n\nObject.assign(exports, __webpack_require__(/*! ./sh */ \"./src/utils/sh.js\"));\nObject.assign(exports, __webpack_require__(/*! ./eventUtils */ \"./src/utils/eventUtils.js\"));\nObject.assign(exports, __webpack_require__(/*! ./obj-merge */ \"./src/utils/obj-merge.js\"));\nObject.assign(exports, __webpack_require__(/*! ./make-data-uri */ \"./src/utils/make-data-uri.js\"));\nObject.assign(exports, __webpack_require__(/*! ./just-fetch */ \"./src/utils/just-fetch.js\"));\nObject.assign(exports, __webpack_require__(/*! ./inventory */ \"./src/utils/inventory.js\"));\nObject.assign(exports, __webpack_require__(/*! ./fetch-keystore */ \"./src/utils/fetch-keystore.js\"));\nObject.assign(exports, __webpack_require__(/*! ./fetch-uri */ \"./src/utils/fetch-uri.js\"));\n\n\n//# sourceURL=webpack://dcp/./src/utils/index.js?");
5201
5201
 
5202
5202
  /***/ }),
5203
5203
 
@@ -5239,7 +5239,7 @@ eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modu
5239
5239
  \********************************/
5240
5240
  /***/ ((__unused_webpack_module, exports) => {
5241
5241
 
5242
- eval("/**\n * @file obj-merge.js\n * Routines for merging objects in different ways\n *\n * @author Wes Garland, wes@page.ca\n * @date Dec 2020\n */\n\n/** \n * Merge objects at their leaves, combining intermediary objects as necessary. \n * Arrays are treated as units, not objects. Any number of objects may be specified\n * on the argument vector. The objects on the left are considered to have lower\n * precedence (replaced more easily) than objects on the right. \n *\n * @examples\n * leafMerge({a:1}, {a:2}) => {a:2}\n * leafMerge({a:1}, {b:2}) => {a:1, b:2}\n * leafMerge({a:{b:1}}, {a:{b:2}}) => {a:{b:2}}\n * leafMerge({a:{b:1}}, {b:{c:2}}) => {a:{b:{c:2}}}\n * leafMerge({a:{b:1}}, {a:{c:2}}) => {a:{b:1, c:2}}\n *\n * @param [...] Objects to merge\n * @returns new object\n */\nexports.leafMerge = function utils$$objMerge$leafMerge() {\n var target = {};\n \n for (let i=0; i < arguments.length; i++) {\n let neo = arguments[i];\n if (neo === undefined)\n continue;\n \n for (let prop in neo) {\n if (!neo.hasOwnProperty(prop))\n continue;\n if (typeof neo[prop] === 'object' && neo[prop] !== null && !Array.isArray(neo[prop]) && ['Function','Object'].includes(neo[prop].constructor.name)) {\n target[prop] = exports.leafMerge(target[prop], neo[prop]);\n } else {\n target[prop] = neo[prop];\n }\n }\n }\n\n return target;\n}\n\n\n//# sourceURL=webpack://dcp/./src/utils/obj-merge.js?");
5242
+ eval("/**\n * @file obj-merge.js\n * Routines for merging objects in different ways\n *\n * @author Wes Garland, wes@page.ca\n * @date Dec 2020\n */\n\n/** \n * Merge objects at their leaves, combining intermediary objects as necessary. \n * Arrays are treated as units, not objects. Any number of objects may be specified\n * on the argument vector. The objects on the left are considered to have lower\n * precedence (replaced more easily) than objects on the right. \n *\n * @examples\n * leafMerge({a:1}, {a:2}) => {a:2}\n * leafMerge({a:1}, {b:2}) => {a:1, b:2}\n * leafMerge({a:{b:1}}, {a:{b:2}}) => {a:{b:2}}\n * leafMerge({a:{b:1}}, {b:{c:2}}) => {a:{b:1},b:{c:2}}\n * leafMerge({a:{b:1}}, {a:{c:2}}) => {a:{b:1, c:2}}\n *\n * @param [...] Objects to merge\n * @returns new object\n */\nexports.leafMerge = function utils$$objMerge$leafMerge() {\n var target = {};\n \n for (let i=0; i < arguments.length; i++) {\n let neo = arguments[i];\n if (neo === undefined)\n continue;\n \n for (let prop in neo) {\n if (!neo.hasOwnProperty(prop))\n continue;\n if (typeof neo[prop] === 'object' && neo[prop] !== null && !Array.isArray(neo[prop]) && ['Function','Object'].includes(neo[prop].constructor.name)) {\n target[prop] = exports.leafMerge(target[prop], neo[prop]);\n } else {\n target[prop] = neo[prop];\n }\n }\n }\n\n return target;\n}\n\n\n//# sourceURL=webpack://dcp/./src/utils/obj-merge.js?");
5243
5243
 
5244
5244
  /***/ }),
5245
5245
 
@@ -5292,7 +5292,7 @@ eval("/* provided dependency */ var process = __webpack_require__(/*! ./node_mod
5292
5292
  /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
5293
5293
 
5294
5294
  "use strict";
5295
- eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js */ \"./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js\")[\"Buffer\"];\n/**\n * @file tmpfiles.js Utilities for securely and portably creating temporary\n * files with NodeJS.\n * @author Wes Garland, wes@kingsds.network\n * @date April 2020\n */\n\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst path = requireNative('path');\nconst fs = requireNative('fs');\nconst os = requireNative('os');\nconst process = requireNative('process');\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { atExit, atExitCancel } = __webpack_require__(/*! ./index */ \"./src/utils/index.js\");\n\n/**\n * deterministic initializer ensures dcpConfig is ready\n */\nexports.getSystemTempDir = function tmpfiles$$getSystemTempDir()\n{\n if (!exports.hasOwnProperty('_systemTempDir'))\n {\n let systemTempDir = process.env.TMPDIR || (dcpConfig.system && dcpConfig.system.tempDir) || (process.platform === 'win32' && process.env.TEMP);\n if (!systemTempDir) {\n if (process.platform === 'win32') {\n if (process.env.LOCALAPPDATA) \n systemTempDir = path.join(systemTempDir, 'Temp');\n else if (process.env.HOMEDRIVE && process.env.HOMEPATH)\n systemTempDir = path.join(process.env.HOMEDRIVE + process.env.HOMEPATH, 'AppData', 'Local', 'Temp');\n else if (process.env.SystemRoot)\n systemTempDir = path.join(process.env.SystemRoot, 'Temp');\n else if (process.env.SystemDrive)\n systemTempDir = path.join(process.env.SystemDrive + '\\\\Windows', 'Temp');\n else\n systemTempDir = 'C:\\\\WINDOWS\\\\Temp'\n }\n else\n systemTempDir = '/tmp';\n }\n\n exports._systemTempDir = systemTempDir;\n }\n\n return exports._systemTempDir;\n}\n\n/**\n * Create a temporary thing in the filesystem.\n *\n * @param {function} createThingFn The function which creates the thing; must take as its argument\n * a fully-pathed filename and throw an exception failure. It must\n * return a handle to the thing, which will become the return value\n * of this function.\n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; false => none, falsey => dcp\n * @param {string} tempDir [optional] The directory in which to create the thing. Default is\n * exports.systemTempDir.\n *\n * @return a handle created by createThingFn, which contains removeFn(), a function which can remove all\n * traces of the temporary thing.\n *\n */\nexports.createTempThing = function createTempThing(createThingFn, basenamePattern, extension, tempDir)\n{\n const generateEntropy = (__webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\").customAlphabet)('1234567890abcdefghijklmnopqrstuvwyxzkABCDEFGHIJKLMNOPQRSTUVWXYZ', 64);\n\n var entropy = generateEntropy();\n var basename = '';\n var filename; /* The filename of the temp thing we are creating */\n var thing; /* The temp thing we are creating */\n var removed = false; /* Used by removeFn to avoid races */\n\n if (!tempDir)\n tempDir = exports.getSystemTempDir();\n \n if (!basenamePattern)\n basenamePattern = `dcp-${process.pid}-XXXXXXXX`;\n\n const re = /[xX]{3,}/;\n assert(re.test(basenamePattern))\n\n let tries = 0 \n loop: while ('goto considered harmful considered harmful') { // eslint-disable-line no-constant-condition\n let j = Math.floor(Math.random() * entropy.length);\n for (let i=0; i < basenamePattern.length; i++) {\n if (basenamePattern[i] === 'X')\n basename += entropy[j++ % entropy.length];\n else\n basename += basenamePattern[i];\n }\n\n if (extension !== false)\n basename += '.' + (extension || 'dcp');\n filename = path.join(tempDir, basename);\n if (fs.existsSync(filename)) {\n tries = tries++; \n basename='';\n if (tries > 1000)\n throw new Error('DCPU-1001'); /* this is ~impossible */\n continue loop;\n }\n try {\n thing = createThingFn(filename);\n } catch(e) {\n if (e.code === 'EEXIST')\n continue;\n throw e;\n }\n break loop;\n }\n\n function removeFn()\n {\n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExitCancel)(removeFn);\n if (removed)\n return;\n if (!process.env.DCP_DEBUG_TMP_FILES)\n {\n try\n {\n try\n {\n fs.unlinkSync(filename);\n removed = true;\n }\n catch(error)\n {\n if (!fs.lstatSync(filename).isDirectory())\n throw error;\n fs.rmdirSync(filename, { recursive: true, maxRetries: 20 }); /* maxRetries might need rimraf? rtfm later /wg aug 2022 */\n removed = true;\n }\n }\n catch(error)\n {\n if (error.code !== 'ENOENT')\n process.emitWarning(`Warning: unable to clean up '${filename}'`, error.message);\n else\n removed = true;\n }\n }\n }\n \n return { thing, removeFn, filename };\n}\n\n/** \n * Securely create a temporary file in the system temp directory. A cleanup is registered to\n * automatically remove the file when the process is closed.\n * \n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; pass false for none\n * @param perms [optional] The permissions argument to openSync.\n * @param {string} tempDir [optional] The directory in which to create the file. Default\n * is exports.systemTempDir.\n *\n * @returns {object} An object with properties:\n * fd: the open file descriptor\n * filename: the name of the newly-created file\n * toString: method which returns the filename\n * write: method which writes to the file descriptor (like fs.write)\n * writeSync: method which writes to the file descriptor (like fs.writeSync)\n * close: method which closes the file descriptor (like fs.close)\n * closeSync: method which closes the file descriptor (like fs.closeSync)\n * chmod: method which changes the file mode (like fs.fchmod)\n * chmodSync: method which changes the file mode (like fs.fchmodSync)\n * remove: method which remove the file from disk and cleans up the registered\n * process.exit cleanup. Does not close the file descriptor!\n */\nexports.createTempFile = function utils$$createTempFile(basenamePattern, extension, perms, tempDir)\n{\n const inspect = requireNative('util').inspect.custom;\n let f = fs.constants;\n let hnd;\n\n /** Atomically create a file using the permissions in the closed-over environment.\n * Atomic here means that either the file will be created or it won't be, and the\n * check-then-create race condition is handled by the OS.\n */\n function atomicCreateFile(filename) {\n return fs.openSync(filename, f.O_RDWR | f.O_CREAT | f.O_EXCL, perms | 0o600);\n }\n\n const { thing: fd, removeFn, filename } = exports.createTempThing(atomicCreateFile, basenamePattern, extension, tempDir);\n\n hnd = { fd, filename };\n hnd.toString = () => filename;\n hnd.write = function tmp$write() { return fs.write .apply(null, [fd].concat(Array.from(arguments))) };\n hnd.writeSync = function tmp$writeSync() { return fs.writeSync .apply(null, [fd].concat(Array.from(arguments))) };\n hnd.chmod = function tmp$chmod() { return fs.fchmod .apply(null, [fd].concat(Array.from(arguments))) };\n hnd.chmodSync = function tmp$chmodSync() { return fs.fchmodSync.apply(null, [fd].concat(Array.from(arguments))) };\n hnd.close = function tmp$close() { fs.close .apply(null, [fd].concat(Array.from(arguments))); delete hnd.fd; return hnd };\n hnd.closeSync = function tmp$closeSync() { fs.closeSync .apply(null, [fd].concat(Array.from(arguments))); delete hnd.fd; return hnd };\n hnd.remove = removeFn;\n\n hnd[inspect] = () => '[Object utils$$tempFile(' + filename + ')]';\n \n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExit)(removeFn);\n return hnd;\n}\n\n/**\n * Securely create a temporary directory in the system temp directory. A cleanup is registered to\n * automatically remove the directory and its children when the process is closed.\n *\n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; pass false for none\n * @param perms [optional] The mode argument to mkdirSync\n * @param {string} tempDir [optional] The directory in which to create the directory.\n *\n * @returns {object} An object with properties:\n * fd: the open file descriptor\n * dirname: the name of the newly-created directory\n * toString: method which returns dirname\n * remove: method which removes the directory and its children\n * from disk and cleans up the registered process.exit cleanup.\n */\nexports.createTempDir = function utils$$createTempDir(basenamePattern, extension, perms, tempDir)\n{\n const inspect = requireNative('util').inspect.custom;\n\n function createDir(dirname)\n {\n var opts = { recursive: Boolean(tempDir) };\n\n if (typeof perms !== 'undefined')\n opts.mode = perms;\n \n fs.mkdirSync(dirname, opts);\n }\n \n const { thing: fd, removeFn, filename } = exports.createTempThing(createDir, basenamePattern, extension, tempDir);\n const hnd = { fd, dirname: filename };\n hnd.toString = () => filename;\n hnd.remove = removeFn;\n hnd[inspect] = () => '[Object utils$$tempDir(' + hnd.dirname + ')]';\n \n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExit)(removeFn);\n return hnd;\n}\n\n/** \n * Securely create a temporary socket (eg for IPC) in the system temp directory. A cleanup is registered to\n * automatically remove the file when the process is closed.\n * \n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; pass false for none\n * @param perms [optional] The permissions argument to openSync. Default is 0600;\n * pass false to use OS default (0666 & umask(2)).\n * @param {function} connectHandler [optional] The callback to invoke when the socket is connected to\n * @returns {object} a socket, created with net.createConnection, using a path argument\n */\nexports.createTempSocket = function utils$$createTempSocket(basenamePattern, extension, connectHandler, perms) {\n const inspect = requireNative('util').inspect.custom;\n var pipeDir;\n \n if (os.platform() === 'win32')\n pipeDir = '\\\\\\\\.\\\\pipe\\\\';\n\n function createSocket(filename)\n {\n const net = requireNative('net');\n var server = net.createServer(connectHandler);\n var socket = server.listen(filename); /* creates the socket (named pipe) */\n\n socket.unref();\n\n return net.createConnection(filename);\n }\n\n const { thing: socket, removeFn, filename } = exports.createTempThing(createSocket, basenamePattern, extension, pipeDir);\n assert(os.platform() !== 'win32' || filename.startsWith('\\\\\\\\.\\\\pipe\\\\'));\n\n if (!socket)\n throw new Error(`Could not create socket ${filename || 'like ' + basenamePattern + '.' + extension}`); /* this should be impossible */\n\n if (filename && perms !== false) {\n try {\n fs.chmodSync(filename, perms || 0o600);\n } catch(error) {\n if (socket.close)\n socket.close();\n console.log(`Could not change permissions on named pipe ${filename} (${error.code || error.message}); closing`);\n throw error;\n }\n }\n\n socket[inspect] = () => '[Object utils$$tempSocket(' + filename + ')]';\n if (os.platform() !== 'win32') /* Pipe namespace automatically cleaned up on win32 */\n {\n socket.on('close', removeFn); \n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExit)(removeFn);\n }\n return socket;\n}\n\n/**\n * Copy the entire source file into the target, appending at the current\n * file offset. If source file has been closed, we will re-open for the\n * the duration of this call in read-only mode. This routine does not attempt\n * to preserve the file position of the input file.\n *\n * @param source {object} The file to copy, handle from createTempFile\n * @param target {object} The file to append to, handle from createTempFile\n * @param target2 {object} An optioanl stream which also receives the same \n * data via its write method. For npm create-hash.\n */ \nexports.catFile = function utils$$catFile(source, target, target2) {\n var buf = Buffer.allocUnsafe(8192 * 64);\n var pos = 0;\n var nRead;\n var sourceFd = source.fd;\n var targetFd = target.fd;\n\n if (!sourceFd && sourceFd !== 0) {\n sourceFd = fs.openSync(source.filename, fs.O_RDONLY, 0o400);\n }\n \n do {\n nRead = fs.readSync(sourceFd, buf, 0, buf.length, pos);\n fs.writeSync(targetFd, buf.slice(0, nRead));\n pos += nRead;\n if (target2)\n target2.write(buf);\n } while(nRead === buf.length);\n\n if (!source.fd && sourceFd !== 0) {\n fs.closeSync(sourceFd);\n }\n}\n\n\n//# sourceURL=webpack://dcp/./src/utils/tmpfiles.js?");
5295
+ eval("/* provided dependency */ var Buffer = __webpack_require__(/*! ./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js */ \"./node_modules/node-polyfill-webpack-plugin/node_modules/buffer/index.js\")[\"Buffer\"];\n/**\n * @file tmpfiles.js Utilities for securely and portably creating temporary\n * files with NodeJS.\n * @author Wes Garland, wes@kingsds.network\n * @date April 2020\n */\n\nconst { requireNative } = __webpack_require__(/*! dcp/dcp-client/webpack-native-bridge */ \"./src/dcp-client/webpack-native-bridge.js\");\nconst path = requireNative('path');\nconst fs = requireNative('fs');\nconst os = requireNative('os');\nconst process = requireNative('process');\nconst { assert } = __webpack_require__(/*! dcp/common/dcp-assert */ \"./src/common/dcp-assert.js\");\nconst { atExit, atExitCancel } = __webpack_require__(/*! ./index */ \"./src/utils/index.js\");\n\n/**\n * deterministic initializer ensures dcpConfig is ready\n */\nexports.getSystemTempDir = function tmpfiles$$getSystemTempDir()\n{\n if (!exports.hasOwnProperty('_systemTempDir'))\n {\n let systemTempDir = process.env.TMPDIR || (dcpConfig.system && dcpConfig.system.tempDir) || (process.platform === 'win32' && process.env.TEMP);\n if (!systemTempDir) {\n if (process.platform === 'win32') {\n if (process.env.LOCALAPPDATA) \n systemTempDir = path.join(systemTempDir, 'Temp');\n else if (process.env.HOMEDRIVE && process.env.HOMEPATH)\n systemTempDir = path.join(process.env.HOMEDRIVE + process.env.HOMEPATH, 'AppData', 'Local', 'Temp');\n else if (process.env.SystemRoot)\n systemTempDir = path.join(process.env.SystemRoot, 'Temp');\n else if (process.env.SystemDrive)\n systemTempDir = path.join(process.env.SystemDrive + '\\\\Windows', 'Temp');\n else\n systemTempDir = 'C:\\\\WINDOWS\\\\Temp'\n }\n else\n systemTempDir = '/tmp';\n }\n\n exports._systemTempDir = systemTempDir;\n }\n\n return exports._systemTempDir;\n}\n\n/**\n * Create a temporary thing in the filesystem.\n *\n * @param {function} createThingFn The function which creates the thing; must take as its argument\n * a fully-pathed filename and throw an exception failure. It must\n * return a handle to the thing, which will become the return value\n * of this function.\n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; false => none, falsey => dcp\n * @param {string} tempDir [optional] The directory in which to create the thing. Default is\n * exports.systemTempDir.\n *\n * @return a handle created by createThingFn, which contains removeFn(), a function which can remove all\n * traces of the temporary thing.\n *\n */\nexports.createTempThing = function createTempThing(createThingFn, basenamePattern, extension, tempDir)\n{\n const generateEntropy = (__webpack_require__(/*! nanoid */ \"./node_modules/nanoid/index.browser.js\").customAlphabet)('1234567890abcdefghijklmnopqrstuvwyxzkABCDEFGHIJKLMNOPQRSTUVWXYZ', 64);\n\n var entropy = generateEntropy();\n var basename = '';\n var filename; /* The filename of the temp thing we are creating */\n var thing; /* The temp thing we are creating */\n var removed = false; /* Used by removeFn to avoid races */\n\n if (!tempDir)\n tempDir = exports.getSystemTempDir();\n \n if (!basenamePattern)\n basenamePattern = `dcp-${process.pid}-XXXXXXXX`;\n\n const re = /[xX]{3,}/;\n assert(re.test(basenamePattern))\n\n let tries = 0 \n loop: while ('goto considered harmful considered harmful') { // eslint-disable-line no-constant-condition\n let j = Math.floor(Math.random() * entropy.length);\n for (let i=0; i < basenamePattern.length; i++) {\n if (basenamePattern[i] === 'X')\n basename += entropy[j++ % entropy.length];\n else\n basename += basenamePattern[i];\n }\n\n if (extension !== false)\n basename += '.' + (extension || 'dcp');\n filename = path.join(tempDir, basename);\n if (fs.existsSync(filename)) {\n tries = tries++; \n basename='';\n if (tries > 1000)\n throw new Error('DCPU-1001'); /* this is ~impossible */\n continue loop;\n }\n try {\n thing = createThingFn(filename);\n } catch(e) {\n if (e.code === 'EEXIST')\n continue;\n throw e;\n }\n break loop;\n }\n\n function removeFn()\n {\n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExitCancel)(removeFn);\n if (removed)\n return;\n if (!process.env.DCP_DEBUG_TMP_FILES)\n {\n try\n {\n try\n {\n fs.unlinkSync(filename);\n removed = true;\n }\n catch(error)\n {\n if (!fs.lstatSync(filename).isDirectory())\n throw error;\n fs.rmdirSync(filename, { recursive: true, maxRetries: 20 }); /* maxRetries might need rimraf? rtfm later /wg aug 2022 */\n removed = true;\n }\n }\n catch(error)\n {\n if (error.code !== 'ENOENT')\n process.emitWarning(`Warning: unable to clean up '${filename}'`, error.message);\n else\n removed = true;\n }\n }\n }\n \n return { thing, removeFn, filename };\n}\n\n/** \n * Securely create a temporary file in the system temp directory. A cleanup is registered to\n * automatically remove the file when the process is closed.\n * \n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; pass false for none\n * @param perms [optional] The permissions argument to openSync.\n * @param {string} tempDir [optional] The directory in which to create the file. Default\n * is exports.systemTempDir.\n *\n * @returns {object} An object with properties:\n * fd: the open file descriptor\n * filename: the name of the newly-created file\n * toString: method which returns the filename\n * write: method which writes to the file descriptor (like fs.write)\n * writeSync: method which writes to the file descriptor (like fs.writeSync)\n * close: method which closes the file descriptor (like fs.close)\n * closeSync: method which closes the file descriptor (like fs.closeSync)\n * chmod: method which changes the file mode (like fs.fchmod)\n * chmodSync: method which changes the file mode (like fs.fchmodSync)\n * remove: method which remove the file from disk and cleans up the registered\n * process.exit cleanup. Does not close the file descriptor!\n */\nexports.createTempFile = function utils$$createTempFile(basenamePattern, extension, perms, tempDir)\n{\n const inspect = requireNative('util').inspect.custom;\n let f = fs.constants;\n let hnd;\n\n /** Atomically create a file using the permissions in the closed-over environment.\n * Atomic here means that either the file will be created or it won't be, and the\n * check-then-create race condition is handled by the OS.\n */\n function atomicCreateFile(filename) {\n return fs.openSync(filename, f.O_RDWR | f.O_CREAT | f.O_EXCL, perms | 0o600);\n }\n\n const { thing: fd, removeFn, filename } = exports.createTempThing(atomicCreateFile, basenamePattern, extension, tempDir);\n\n hnd = { fd, filename };\n hnd.toString = () => filename;\n hnd.write = function tmp$write() { return fs.write .apply(null, [fd].concat(Array.from(arguments))) };\n hnd.writeSync = function tmp$writeSync() { return fs.writeSync .apply(null, [fd].concat(Array.from(arguments))) };\n hnd.chmod = function tmp$chmod() { return fs.fchmod .apply(null, [fd].concat(Array.from(arguments))) };\n hnd.chmodSync = function tmp$chmodSync() { return fs.fchmodSync.apply(null, [fd].concat(Array.from(arguments))) };\n hnd.close = function tmp$close() { fs.close .apply(null, [fd].concat(Array.from(arguments))); delete hnd.fd; return hnd };\n hnd.closeSync = function tmp$closeSync() { fs.closeSync .apply(null, [fd].concat(Array.from(arguments))); delete hnd.fd; return hnd };\n hnd.remove = removeFn;\n\n hnd[inspect] = () => '[Object utils$$tempFile(' + filename + ')]';\n \n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExit)(removeFn);\n return hnd;\n}\n\n/**\n * Securely create a temporary directory in the system temp directory. A cleanup is registered to\n * automatically remove the directory and its children when the process is closed.\n *\n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; pass false for none\n * @param perms [optional] The mode argument to mkdirSync\n * @param {string} tempDir [optional] The directory in which to create the directory.\n *\n * @returns {object} An object with properties:\n * fd: the open file descriptor\n * dirname: the name of the newly-created directory\n * toString: method which returns dirname\n * remove: method which removes the directory and its children\n * from disk and cleans up the registered process.exit cleanup.\n */\nexports.createTempDir = function utils$$createTempDir(basenamePattern, extension, perms, tempDir)\n{\n const inspect = requireNative('util').inspect.custom;\n\n function createDir(dirname)\n {\n var opts = { recursive: Boolean(tempDir) };\n\n if (typeof perms !== 'undefined')\n opts.mode = perms;\n \n fs.mkdirSync(dirname, opts);\n }\n \n const { thing: fd, removeFn, filename } = exports.createTempThing(createDir, basenamePattern, extension, tempDir);\n const hnd = { fd, dirname: filename };\n hnd.toString = () => filename;\n hnd.remove = removeFn;\n hnd[inspect] = () => '[Object utils$$tempDir(' + hnd.dirname + ')]';\n \n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExit)(removeFn);\n return hnd;\n}\n\n/** \n * Securely create a temporary socket (eg for IPC) in the system temp directory. A cleanup is registered to\n * automatically remove the file when the process is closed.\n * \n * @param {string} basenamePattern A string specifying the file basename pattern. The\n * character X will be replaced with randomly-generated\n * characters, similar to mkstemp(3).\n * @param {string} extension [optional] The filename extension to use; pass false for none\n * @param perms [optional] The permissions argument to openSync. Default is 0600;\n * pass false to use OS default (0666 & umask(2)).\n * @param {function} connectHandler [optional] The callback to invoke when the socket is connected to\n * @returns {object} a socket, created with net.createConnection, using a path argument\n */\nexports.createTempSocket = function utils$$createTempSocket(basenamePattern, extension, connectHandler, perms) {\n const inspect = requireNative('util').inspect.custom;\n var pipeDir;\n \n if (os.platform() === 'win32')\n pipeDir = '\\\\\\\\.\\\\pipe\\\\';\n\n function createSocket(filename)\n {\n const net = requireNative('net');\n var server = net.createServer(connectHandler);\n var socket = server.listen(filename); /* creates the socket (named pipe) */\n\n socket.unref();\n\n return net.createConnection(filename);\n }\n\n const { thing: socket, removeFn, filename } = exports.createTempThing(createSocket, basenamePattern, extension, pipeDir);\n assert(os.platform() !== 'win32' || filename.startsWith('\\\\\\\\.\\\\pipe\\\\'));\n\n if (!socket)\n throw new Error(`Could not create socket ${filename || 'like ' + basenamePattern + '.' + extension}`); /* this should be impossible */\n\n if (filename && perms !== false) {\n try {\n fs.chmodSync(filename, perms || 0o600);\n } catch(error) {\n if (socket.close)\n socket.close();\n console.log(`Could not change permissions on named pipe ${filename} (${error.code || error.message}); closing`);\n throw error;\n }\n }\n\n socket[inspect] = () => '[Object utils$$tempSocket(' + filename + ')]';\n if (os.platform() !== 'win32') /* Pipe namespace automatically cleaned up on win32 */\n {\n socket.on('close', removeFn); \n (__webpack_require__(/*! dcp/node-libs/shutdown */ \"./src/node-libs/shutdown.js\").atExit)(removeFn);\n }\n return socket;\n}\n\n/**\n * Copy the entire source file into the target, appending at the current\n * file offset. If source file has been closed, we will re-open for the\n * the duration of this call in read-only mode. This routine does not attempt\n * to preserve the file position of the input file.\n *\n * @param source {object} The file to copy, handle from createTempFile\n * @param target {object} The file to append to, handle from createTempFile\n * @param target2 {object} An optioanl stream which also receives the same \n * data via its write method. For npm create-hash.\n */ \nexports.catFile = function utils$$catFile(source, target, target2) {\n var buf = Buffer.allocUnsafe(8192 * 64);\n var pos = 0;\n var nRead;\n var sourceFd = source.fd;\n var targetFd = target.fd;\n\n if (!sourceFd && sourceFd !== 0) {\n sourceFd = fs.openSync(source.filename, fs.O_RDONLY, 0o400);\n }\n \n do {\n nRead = fs.readSync(sourceFd, buf, 0, buf.length, pos);\n fs.writeSync(targetFd, buf.slice(0, nRead));\n pos += nRead;\n if (target2)\n target2.write(buf.slice(0, nRead));\n } while(nRead === buf.length);\n\n if (!source.fd && sourceFd !== 0) {\n fs.closeSync(sourceFd);\n }\n}\n\n\n//# sourceURL=webpack://dcp/./src/utils/tmpfiles.js?");
5296
5296
 
5297
5297
  /***/ }),
5298
5298